【译】Python3.8官方Logging文档(完整版)

事件信息会以LogRecord实例的形式在loggers、handlers、filters以及formatters之间传递。
Logging的入口为Logger类(以下统称loggers)的实例方法。每个实例都有一个命名,他们共同存在于一个由点符号分割的命名空间内。举个例子,一个被命名为‘scan’的logger实例是‘scan.text’实例的上层实例。你可以随意命名logger实例,并且可以在日志消息里面显示调用程序的层级。
一个比较好的做法是利用模块层级来组织logger实例的命名,模块里面的命名方式如下:

logger = logging.getLogger(__name__)

这样使得logger的命名可以正确反映包(以及模块)的层级,使得可以通过日志记录里面的logger命名直观的追溯到代码位置。
在层级根部实例化的logger被命名为root。同所有logger实例一样,root实例提供了debug(), info(), warning(), error()以及critical()函数。这些函数共享签名。root实例在打印日志信息时会携带‘root’字符串。

一条日志消息可以有多个流向。logging包提供的处理方式包括:写入文件、发送Get或者Post形式的HTTP报文、SMTP形式的电子邮件、通用的套接字、队列以及不同操作系统的日志处理机制(诸如系统日志、 Windows NT
事件日志)。日志消息的流向由handler类来处理。如果内置的handler类不能满足你的需求,你也可以自定义handler。
缺省状态下并不会设置任何日志流向。你可以通过《基础部分》里面提到的basicConfig()函数设置诸如控制台、文件在内的日志流向。当你调用debug()等日志方法时,这些方法会检查你是否指定了日志流向,如果你没有指定的话,这些方法会默认指定控制台(sys.stderr)为日志流向、使用默认的日志格式,然后才将日志消息传递到logger类的root实例,最终生成你看到的日志消息。
basicConfig()的缺省日志格式为:

severity:logger name:message

你也可以显式的通过format参数显式的的指定日志格式。关于日志格式的构造选项,请参考过于formatter类的文档说明。

Logging包的处理流程

下图是关于一条日志消息在loggers和handlers之间怎样被处理的流程图:

Loggers

logger对象有三部的工作。第一,它对外暴露了若干方法,使得外部程序可以在运行的时候记录日志信息。第二,logger对象可以根据日志级别来决定是否需要过滤掉一条日志消息。第三,logger对象会将日志消息传递给已关联的handlers。
logger对象有两类使用最广泛的方法:配置以及发送日志消息。
下面是最常见的配置方式:

  • Logger.setLevel()可以配置允许生效的最低级别。在内置的日志级别中,DEBUG级别是最低级的,CRITICAL是最高级别。举个例子,配置的级别是INFO,那么logger实例只会处理INFO、WARNING、ERROR以及CRITICAL级别的日志消息,而DEBUG级别的会被过滤掉。
  • Logger.addHandler()和Logger.removerHandler()为logger实例提供了增、删handler对象的途径。稍后会详细介绍handler对象。
  • Logger.addFilter()和Logger.removerFilter()为logger实例提供了增、删filter对象的途径。

你并不需要每次创建logger实例时都调用它们。
对于给定的logger实例,下面的方法会生产一条日志消息:

  • Logger.debug(), Logger.info(), Logger.warning(), Logger.error(), 以及 Logger.critical()都会生成一条日志记录,该记录包含日志消息和方法名对应的日志级别。这个消息实际上是一个格式化的字符串,可能包含标准的字符串格式符符号(比如
    %s, %d, %f等)。剩下的入参可能包含一些在日志消息中预格式化的对象。对于**kwargs形式的关键字入参,日志函数只关心exc_info对应的变量值,它将决定是否记录异常信息。

  • Logger.exception()和Logger.error()生成的日志消息相似,他们的区别在Logger.exception()携带栈信息。确保只在异常处理时调用该函数。
  • Logger.log()需要指定日志级别作为入参。相比上面提到的开箱即用的日志函数,它显得有些繁琐,但是可以适用于需要自定义日志级别的场景。


Handlers

handler对象负责根据日志级别分配日志消息的最终流向。Logger对象默认不包含handler对象,但是可用通过addHandler()方法添加。拿一个应用场景来举例:假设你的应用程序需要将所有日志消息保存在日志文件中;把ERROR级别及以上的日志打印在标准输出;所有的CIRITCAL级别的日志通过电子邮件发给你。整个场景需要三个handler实例,每个实例都会根据不同的日志级别采取不同的方式处理日志消息。
标准库只内置少量的handler类型;本文档主要拿StreamHandler和FileHandler来举例。
开发人员只需要关心Handler对象的少数几个方法。在内置的handler对象(非自定义的handler)里面,跟开发人员密切相关的配置方法如下所示:

  • setLevel()方法跟logger对象的方法一样,配置handler会处理的最低日志级别。为什么会有两个setLevel()方法呢?logger对象设置的日志级别决定了日志能够被传递到handler对象。而handler对象设置的日志级别决定了日志消息是否会被记录下来。
  • setFormaterr()可以为handler对象配置Formatter对象。
  • addFilter()和removerFilter()可以增删filter对象。

应用程序不应该直接实例化Handler对象。因为Handler对象是一个基类,它定义了所有Handler子类都应该继承或者复写的接口方法。


Formatters

formatter对象决定了一条的日志消息的顺序、结构以及内容。不同于logger.Handler是基类,应用程序需要自己实例化Formatter类。当然如果你有特殊需求,也可以实例化Formatter的子类。它接收三个参数:

  • 一个预格式化的消息字符串
  • 一个预格式化的时间字符串
  • 一个类型符号
logging.Formatter.__init__(fmt=None, datefmt=None, style='%')

如果没有显式的传入消息格式,会使用缺省设置。如果没有显式的传入时间格式,缺省的时间格式如下:

%Y-%m-%d %H:%M:%S

并且会在后面追加毫秒。类型符号可以选择‘%’、‘{‘或者’$‘。缺省的类型符号为’%’。

  • 如果类型符号为‘%’,日志消息的格式化方式采用 %(<dictionary
     
    key>)s
    的替换方式;可用的键值请单独翻阅LogRecord的说明。

  • 如果类型符号为‘{’,日志消息的格式化方式采用与str.formate()方法兼容的处理(也就是使用关键字)。
  • 如果类型符号为‘$’,日志消息的格式化方式需要与string.Template.substitute()方法保持一致。


3.2版本的改动说明:


增加了style入参。

'%(asctime)s - %(levelname)s - %(message)s'

上面是一个带有时间的可读性高的预格式化方式,日志级别和日志内容被有序的添加在里面。

formatters提供了用户可配置的函数,方便日志生成时间转化为时间元组。默认的是使用time.localtime()。如果想要在formatter实例中自定义,可以通过给
converter
属性赋值的形式改变默认行为,需要注意的是新赋的值需要是同time.localtime()或者time.gmtime()签名一致的函数。假设这么一个场景:你需要是所有的日志时间都展示为GMT时区,你可以将Formatter的  
converter
属性赋值为time.gmtime()的形式,改变所有formatter实例行为。

Logging配置

开发人员可以通过以下三种配置来配置logging:

  1. 在代码中使用前面提到的方式在代码中依次创建loggers、handlers以及formatters对象。
  2. 新建一个配置文件,并通过fileConfig()函数载入配置。
  3. 新建一个配置文件夹,并通过dictConfig()函数载入配置。

关于后两种配置方式更详细的说明,请自行查阅Configuration函数文档。下面是Python代码示例,它包含了一个简单的logger实例、一个控制台handler和一个简单的formatter:

import logging


# create logger logger = logging.getLogger('simple_example') logger.setLevel(logging.DEBUG)
# create console handler and set level to debug ch = logging.StreamHandler() ch.setLevel(logging.DEBUG)
# create formatter formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# add formatter to ch ch.setFormatter(formatter)
# add ch to logger logger.addHandler(ch)
# 'application' code logger.debug('debug message') logger.info('info message') logger.warning('warn message') logger.error('error message') logger.critical('critical message')

运行结果如下:

$ python simple_logging_module.py

2005-03-19 15:10:26,618 - simple_example - DEBUG - debug message

2005-03-19 15:10:26,620 - simple_example - INFO - info message

2005-03-19 15:10:26,695 - simple_example - WARNING - warn message

2005-03-19 15:10:26,697 - simple_example - ERROR - error message

2005-03-19 15:10:26,773 - simple_example - CRITICAL - critical message

下面代码的效果同上面一样,也是新建logger实例、handler实例和formatter实例,唯一不同的地方是对象的命名。

import logging

import logging.config


logging.config.fileConfig('logging.conf')
# create logger logger = logging.getLogger('simpleExample')
# 'application' code logger.debug('debug message') logger.info('info message') logger.warning('warn message') logger.error('error message') logger.critical('critical message')

logging.conf文件的内容如下:

[loggers]

keys=root,simpleExample


[handlers] keys=consoleHandler
[formatters] keys=simpleFormatter
[logger_root] level=DEBUG handlers=consoleHandler
[logger_simpleExample] level=DEBUG handlers=consoleHandler qualname=simpleExample propagate=0
[handler_consoleHandler] class=StreamHandler level=DEBUG formatter=simpleFormatter args=(sys.stdout,)
[formatter_simpleFormatter] format=%(asctime)s - %(name)s - %(levelname)s - %(message)s datefmt=

执行之后的输出信息同不用配置文件的差不多:

$ python simple_logging_config.py

2005-03-19 15:38:55,977 - simpleExample - DEBUG - debug message

2005-03-19 15:38:55,979 - simpleExample - INFO - info message

2005-03-19 15:38:56,054 - simpleExample - WARNING - warn message

2005-03-19 15:38:56,055 - simpleExample - ERROR - error message

2005-03-19 15:38:56,130 - simpleExample - CRITICAL - critical message

能够很明显看到的是,配置文件的内容格式相对Python代码而言有一些简化的地方。像这样将配置和代码分离,能够帮助没有开发经验的用户更好的配置日志行为。

警告:
fileConfig函数有一个特殊的入参——disable_existing_loggers。
为了保持向后兼容,其默认值为True。
这个参数导致的行为可能会让你困惑,它会使得在fileConfig()函数调用之前已存在的非root的logger实例失效,除非某个logger同配置文件中的配置同名。
如果需要关于它的更多细节,可以自行查阅相关说明。
当然你也可以根据实际需要显式的传入False。

需要注意的是配置文件提到的接口引用,要么必须是logging包内部的,要么是可通过import导入的绝对路径。举个例子,你可以使用WatchedFileHandler(logging包内部),你也可以用mypackage.mymodule.MyHandler(一个在mypackage包–mymodule模块定义的类,当然整个路径必须能够被import正确导入)。
从Python3.2开始,引入了一种新的日志配置方式–通过目录组织配置信息。它能够提供上面其他方式更强大的功能,推荐开发人员在新建项目时使用这种方式。因为文件夹的配置方式除了正常的配置之外,还可以根据不同的用途灵活的移动文件夹。举个例子,你可以使用JSON格式添加配置信息,如果你之前接触过YAML程序开发,你也使用YAML格式。当然,你也可以选择Python代码的配置方式、接收套接字的配置方式,或者其他你认为方便的方式。
下面是基于文件夹配置使用YAML格式的配置示例,效果跟之前的示例一样:

version: 1

formatters:

  simple:

    format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'

handlers:

  console:

    class: logging.StreamHandler

    level: DEBUG

    formatter: simple

    stream: ext://sys.stdout

loggers:

  simpleExample:

    level: DEBUG

    handlers: [console]

    propagate: no

root:

  level: DEBUG

  handlers: [console]

如果需要关于文件夹配置的更多资料,可以自行查阅Configration的说明。
如果没有提供配置信息会发生什么
如果没有提供配置信息,大概率会在打印日志事件的时候发现没有handlers实例可用。当然,实际会发生什么跟Python版本有关。
对于Python3.2之前的版本,最后会发生:

  • 如果 logging.raiseExceptions
    选项为False(线上环境),日志会被丢弃。

  • 如果 logging.raiseExceptions
    选项为True(开发环境),会在控制台输出 ‘No handlers could be found for logger X.Y.Z’ 

Python3.2及以后的版本,最后会发生:

  • 日志事件会通过loggin.lastResort对象中的兜底handler处理。
    这个内部的handler并没有被任何logger使用,它的效果跟StreamHandler一样,会将日志消息输出到sys.stderr(所以需要你谨慎的对待可能被改动的重定向)。
    它只会打印出来日志消息,并没有携带任何格式。
    这个handler的默认级别是DEBUG,所以基本上任何消息都会被打印出来。

如果你希望禁用3.2版本之后的默认行为,可以将logging.lastResort显式的的设置为None。