logging:报告状态、错误和信息消息
文章目录
- 一、日志系统的组成:Logger / LogRecord / Handler / Formatter
- 二、将日志记入文件:logging.basicConfig()
- 1、参数 level:设置日志的 「最低记录级别」
- 2、参数 format:定义日志的输出格式
- 3、参数 datefmt:指定 %(asctime)s 占位符的时间格式
- 4、参数 filename:指定日志的输出文件路径
- 5、参数 filemode:指定日志文件的打开模式
- 三、自动轮转日志文件:logging.handlers.RotatingFileHandler()
- 1、参数 filename:主日志文件路径
- 2、参数 maxBytes:单个日志文件的最大字节数
- 3、参数 maxBytes:单个日志文件的最大字节数
- 四、日志级别:NOTSET / DEBUG / INFO / WARNING / ERROR / CRITICAL
- 五、对日志记录器实例命名:loggerx = logging.getLogger("name")
- 六、日志树:树形结构,处理器共享,差异化配置
- 七、与 warnings 模块集成:logging.captureWarnings(True)
logging
模块定义了一个标准 API,用来报告错误和状态信息。通过标准库模块来提供日志 API,使得所有的 Python 模块都可以参与日志记录。
import logging
一、日志系统的组成:Logger / LogRecord / Handler / Formatter
日志系统由4种相互作用的对象类型构成。
任何需要记录日志的模块或应用,都可以使用Logger(日志记录器)
实例向日志中添加信息。调用日志记录器时会创建一个LogRecord(日志记录)
,该对象会在内存中暂存日志信息,直至信息被处理。一个Logger
可以配置多个Handler(处理器)
对象,这些处理器负责接收并处理日志记录。而Handler
会借助Formatter(格式化器)
将日志记录转换为可供输出的消息。
为方便理解日志系统逻辑,对上述术语进行补充说明:
- Logger(日志记录器):日志系统的 “入口”,通过调用其方法发起日志记录请求,是开发者直接操作的核心对象。
- LogRecord(日志记录):日志的 “数据载体”,自动封装日志的级别、内容、时间戳、调用位置等信息,是在系统内部传递的核心数据结构。
- Handler(处理器):日志的 “分发器”,决定日志的输出目的地(如控制台、文件、网络),一个
Logger
可配置多个Handler
实现 “多端输出”(如同时打印到控制台并写入文件)。 - Formatter(格式化器):日志的 “美化器”,定义日志输出的字符串格式,使日志内容更易读、易解析。
这四种对象协同工作,形成了 “发起记录→封装数据→分发处理→格式化输出” 的完整日志流程。
二、将日志记入文件:logging.basicConfig()
使用logging
模块的basicConfig()
函数可以建立默认处理器,将日志信息写入一个文件。接下来对该函数中的常用参数进行具体介绍。
1、参数 level:设置日志的 「最低记录级别」
参数level
用于设置日志的 最低记录级别,只有级别大于或等于该值的日志才会被记录。
日志级别「从低到高」的排序为:DEBUG < INFO < WARNING < ERROR < CRITICAL
。
2、参数 format:定义日志的输出格式
参数format
用于定义日志的输出格式,通过占位符指定要包含的日志信息。常用的占位符有:
%(asctime)s
:日志记录的时间,具体格式由参数datefmt
控制%(message)s
:日志的具体内容%(levelname)s
:日志级别,如INFO
%(filename)s
:产生日志的文件名称%(threadName)s
:当前线程的名称
3、参数 datefmt:指定 %(asctime)s 占位符的时间格式
参数datefmt
用于指定%(asctime)s
占位符的时间格式,只有在参数format
中包含%(asctime)s
时,该参数才会生效。
4、参数 filename:指定日志的输出文件路径
参数filename
用于指定日志的输出文件路径,此时日志不会打印到控制台,而是写入该参数指向的文件中。若不指定此参数,日志默认输出到控制台(终端)。
5、参数 filemode:指定日志文件的打开模式
参数filemode
用于指定日志文件的打开模式,只有设置了参数filename
时,该参数才会生效。常用的模式如下:
'a'(append,默认)
:追加模式,新日志会添加到文件末尾,原有内容保留'w'(write)
:覆盖模式,每次运行程序会清空文件原有内容,只保留本次日志
LOG_FILENAME = 'logging_example.out'
logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG,
)logging.debug('This message should go to the log file')with open(LOG_FILENAME, 'rt') as f:body = f.read()# DEBUG:root:This message should go to the log file
print(body)
三、自动轮转日志文件:logging.handlers.RotatingFileHandler()
logging
模块的handlers.RotatingFileHandler()
日志处理器可以基于配置的文件路径,自动创建新的日志文件,同时保留原来的日志文件。
1、参数 filename:主日志文件路径
最新的日志信息会被写入参数filename
指定的主日志文件路径中。
2、参数 maxBytes:单个日志文件的最大字节数
当主日志文件达到参数maxBytes
设置的指定字节大小时,日志处理器会通过「增加.1
后缀」的方式自动将主日志文件重命名为备份文件。对于已存在的备份文件,也会通过后缀递增的方式进行重命名,如.1
变成.2
。
3、参数 maxBytes:单个日志文件的最大字节数
可以通过参数backupCount
规定备份文件的最大数量,避免磁盘空间被过多日志占用。达到最大数量后,最旧的日志文件将会被删除。
import globLOG_FILENAME = 'logging_rotatingfile_example.out'# 创建一个名为 MyLogger 的专属日志记录器,并设置日志记录级别为DEBUG
my_logger = logging.getLogger('MyLogger')
my_logger.setLevel(logging.DEBUG)# 为日志记录器添加一个 RotatingFileHandler,实现日志文件自动轮转
handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, # 日志文件路径maxBytes=20, # 单个日志文件的最大容量为20字节backupCount=5, # 最多保留5个备份日志文件数量
)
my_logger.addHandler(handler) # 将处理器绑定到日志记录器,使日志按处理器规则输出# 产出日志信息
for i in range(20):my_logger.debug('i = %d' % i)# 查找所有以 LOG_FILENAME 为前缀的文件,即主日志文件和所有备份日志文件
# glob.glob() 用于批量查找文件,它按照「Unix 风格的通配符模式」搜索文件路径,返回符合条件的所有文件路径列表
logfiles = glob.glob('%s*' % LOG_FILENAME)
for filename in sorted(logfiles):print(filename)>>> 输出结果:
logging_rotatingfile_example.out
logging_rotatingfile_example.out.1
logging_rotatingfile_example.out.2
logging_rotatingfile_example.out.3
logging_rotatingfile_example.out.4
logging_rotatingfile_example.out.5
四、日志级别:NOTSET / DEBUG / INFO / WARNING / ERROR / CRITICAL
logging
API 能够采用不同的日志级别生成不同的日志信息,即使代码中附带了调试信息,也可以通过适当地设置日志级别,避免在生产系统中出现这些调试信息。下表列出了logging
定义的日志级别。
日志级别 | 数值 |
---|---|
CRITICAL | 50 |
ERROR | 40 |
WARNING | 30 |
INFO | 20 |
DEBUG | 10 |
NOTSET | 0 |
注意:
- 只有当处理器
Handler
和日志记录器Logger
都被配置为可以发布该级别(及更高级别)的消息时,才会显示这个级别的日志消息。 - 日志级别中的
NOTSET
表示未设置具体级别,用于继承上级日志记录器Logger
的级别:- 当一个子模块
Logger
被设置为NOTSET
时,它会向上查找父级Logger
的级别,并使用父级的级别作为自己的有效级别。 - 只有当所有祖先(父级及往上)
Logger
的级别都为NOTSET
时,才会默认记录所有级别的日志(包括 DEBUG 及以上)。
- 当一个子模块
import sysLEVELS = {'debug': logging.DEBUG,'info': logging.INFO,'warning': logging.WARNING,'error': logging.ERROR,'critical': logging.CRITICAL,
}if len(sys.argv) > 1:level_name = sys.argv[1]level = LEVELS.get(level_name, logging.NOTSET)logging.basicConfig(level=level)logging.debug('This is a debug message')
logging.info('This is an info message')
logging.warning('This is a warning message')
logging.error('This is an error message')
logging.critical('This is a critical error message')>>> 输出结果:
$ python /Users/mycomputer/test.py debugDEBUG:root:This is a debug message
INFO:root:This is an info message
WARNING:root:This is a warning message
ERROR:root:This is an error message
CRITICAL:root:This is a critical error message$ python /Users/mycomputer/test.py infoINFO:root:This is an info message
WARNING:root:This is a warning message
ERROR:root:This is an error message
CRITICAL:root:This is a critical error message
五、对日志记录器实例命名:loggerx = logging.getLogger(“name”)
前面所有的日志消息中都包含一个单词root
,这是因为代码均使用了根日志记录器。要区分特定日志消息的来源,一个简单的方法是对每个模块使用一个单独的日志记录器对象,发送到日志记录器的消息会包含该记录器的名称。
logging.basicConfig(level=logging.WARNING)logger1 = logging.getLogger('package1.module1')
logger2 = logging.getLogger('package2.module2')logger1.warning('This message comes from one module')
logger2.warning('This comes from another module')>>> 输出结果:
WARNING:package1.module1:This message comes from one module
WARNING:package2.module2:This comes from another module
六、日志树:树形结构,处理器共享,差异化配置
日志记录器Logger
实例会根据其名称配置成树形结构,如下图所示。通常每个应用或库都会定义一个基础名称,各个模块的日志记录器则被设置为该基础日志记录器的子级。根日志记录器没有名称。
这种树形结构对日志配置非常实用,因为这意味着每个日志记录器无需配置专属的处理器。如果某个日志记录器没有配置任何处理器,那么日志消息会传递给它的父级日志记录器来处理。因此,对于大多数应用而言,只需要在根日志记录器上配置处理器即可,所有的日志信息都会被收集并发送到同一目标(如指定的日志文件或控制台),如下图所示。
树形结构还支持为应用或库的不同部分设置不同的日志级别、处理器和格式化器,从而控制哪些日志消息会被记录,以及这些消息会输出到哪里,如下图所示。
七、与 warnings 模块集成:logging.captureWarnings(True)
logging
模块通过captureWarnings()
函数与warnings
模块实现集成。该函数会配置warnings
模块,让警告信息通过日志系统输出而不是直接输出。
import warningslogging.basicConfig(level=logging.INFO)warnings.warn('This warning is not sent to the logs')logging.captureWarnings(True)# 这条警告信息使用 WARNING 日志级别被发送到一个名为 py.warnings 的日志记录器
warnings.warn('This warning is sent to the logs')>>> 输出结果:
/Users/mycomputer/test.py:8: UserWarning: This warning is not sent to the logswarnings.warn('This warning is not sent to the logs')
WARNING:py.warnings:/Users/mycomputer/test.py:12: UserWarning: This warning is sent to the logswarnings.warn('This warning is sent to the logs')