Python: logging模块 - 全局使用

80 投票
5 回答
120222 浏览
提问于 2025-04-17 03:29

我在想怎么实现一个全局的日志记录器,这样我就可以在任何地方使用它,并且可以根据自己的需求进行设置:

我现在有一个自定义的日志记录器类:

class customLogger(logging.Logger):
   ...

这个类放在一个单独的文件里,还有一些格式化工具和其他东西。这个日志记录器单独使用的时候效果很好。

我在我的主Python文件中导入了这个模块,并像这样创建了一个对象:

self.log = logModule.customLogger(arguments)

但是显然,我无法在代码的其他部分访问这个对象。我这样做是不是不对?有没有更好的方法?

5 个回答

13

在你的日志模块中创建一个customLogger的实例,并把它当作单例使用——也就是说,直接使用导入的那个实例,而不是每次都去创建一个新的类。

97

因为我没有找到一个令人满意的答案,所以我想稍微详细说明一下关于Python标准库中logging库的工作原理和意图。

与提问者的做法不同,这个库清楚地区分了记录器的接口和记录器本身的配置。

处理程序的配置是使用你库的应用程序开发者的特权。

这意味着你不应该创建一个自定义的记录器类,并在这个类中配置记录器,添加任何配置或其他内容。

logging库引入了四个组件:记录器处理程序过滤器格式化器

  • 记录器提供了应用程序代码直接使用的接口。
  • 处理程序将记录器创建的日志记录发送到合适的目的地。
  • 过滤器提供了更细致的功能来决定哪些日志记录需要输出。
  • 格式化器指定了日志记录在最终输出中的布局。

一个常见的项目结构看起来像这样:

Project/
|-- .../
|   |-- ...
|
|-- project/
|   |-- package/
|   |   |-- __init__.py
|   |   |-- module.py
|   |   
|   |-- __init__.py
|   |-- project.py
|
|-- ...
|-- ...

在你的代码中(比如在module.py里),你会引用模块的记录器实例,以在特定级别记录事件。

命名记录器时一个好的约定是在每个使用日志记录的模块中使用模块级别的记录器,命名方式如下:

logger = logging.getLogger(__name__)

特殊变量__name__指的是你的模块名称,具体看起来像project.package.module,这取决于你应用程序的代码结构。

module.py(以及任何其他类)基本上可以看成这样:

import logging
...
log = logging.getLogger(__name__)

class ModuleClass:
    def do_something(self):
        log.debug('do_something() has been called!')

每个模块中的记录器会将任何事件传递给父记录器,而父记录器则将信息传递给它附加的处理程序!类似于Python的包/模块结构,父记录器是通过“点状模块名称”来确定的。因此,用特殊的__name__变量初始化记录器是有意义的(在上面的例子中,name与字符串"project.package.module"匹配)。

有两种方法可以全局配置记录器:

  • project.py中实例化一个名为__package__的记录器,在这个例子中它等于"project",因此是所有子模块记录器的父记录器。只需要为这个记录器添加一个合适的处理程序和格式化器。

  • 在执行脚本中(比如main.py)设置一个带有处理程序和格式化器的记录器,名称为最顶层的包。

当开发一个使用日志记录的库时,你应该注意记录库如何使用日志记录——例如,使用的记录器名称。

执行脚本,比如main.py,最后可能看起来像这样:

import logging
from project import App

def setup_logger():
    # create logger
    logger = logging.getLogger('project')
    logger.setLevel(logging.DEBUG)

    # create console handler and set level to debug
    ch = logging.StreamHandler()
    ch.setLevel(level)

    # create formatter
    formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(name)s: %(message)s')

    # add formatter to ch
    ch.setFormatter(formatter)

    # add ch to logger
    logger.addHandler(ch)

if __name__ == '__main__' and __package__ is None:
     setup_logger()
     app = App()
     app.do_some_funny_stuff()

方法调用log.setLevel(...)指定了记录器将处理的最低严重性日志消息,但不一定会输出!这仅意味着只要消息的严重性级别高于(或等于)设置的级别,消息就会被传递给处理程序。但处理程序负责处理日志消息(例如打印或存储它)。

因此,logging库提供了一种结构化和模块化的方法,只需根据自己的需要加以利用。

日志记录文档

156

使用 logging.getLogger(name) 来创建一个有名字的全局日志记录器。

main.py

import log
logger = log.setup_custom_logger('root')
logger.debug('main message')

import submodule

log.py

import logging

def setup_custom_logger(name):
    formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(module)s - %(message)s')

    handler = logging.StreamHandler()
    handler.setFormatter(formatter)

    logger = logging.getLogger(name)
    logger.setLevel(logging.DEBUG)
    logger.addHandler(handler)
    return logger

submodule.py

import logging

logger = logging.getLogger('root')
logger.debug('submodule message')

输出结果

2011-10-01 20:08:40,049 - DEBUG - main - main message
2011-10-01 20:08:40,050 - DEBUG - submodule - submodule message

撰写回答