Python logging 库核心机制与实战配置详解
本文深入解析Python标准库logging的工作机制,涵盖六种日志级别、核心组件(Logger、Handler、Formatter等)及执行流程。详细演示如何配置基本日志输出、自定义Logger对象、处理异常信息,以及通过字典或配置文件进行灵活部署。此外,还解决了中文乱码、日志轮转和多进程日志隔离等实战常见问题,帮助开发者构建健壮的日志系统。
在项目部署阶段,将所有信息直接输出至控制台并不现实。将运行信息记录到日志文件中,不仅能便于监控程序运行状态,还能在故障发生时,通过查阅日志快速定位问题根源。
1、日志级别
Python 标准库 logging 内置了六种日志级别,分别对应不同的数值:NOTSET(0)、DEBUG(10)、INFO(20)、WARNING(30)、ERROR(40)和 CRITICAL(50)。自定义日志级别时,应避免与默认数值冲突。logging 模块仅输出大于或等于设定级别的日志信息。例如,若设定级别为 INFO,则 INFO、WARNING、ERROR 和 CRITICAL 级别的日志均会被输出。
2、logging 流程
官方 logging 模块的工作流程如下图所示:
从流程图中可以看到涉及的五种核心 Python 类型:Logger、LogRecord、Filter、Handler 和 Formatter。
各类别说明如下:
Logger:日志记录器,向应用程序暴露接口,依据日志记录器和过滤器的级别判定哪些日志有效。
LogRecord:日志记录对象,负责将日志传递至相应的处理器进行处理。
Handler:处理器,将 LogRecord 产生的日志记录发送至合适的目标位置。
Filter:过滤器,提供更精细的控制粒度,决定哪些日志记录可以被输出。
Formatter:格式化器,定义日志最终输出的布局和格式。

- 首先判断 Logger 对象设置的级别是否可用,若可用则继续执行,否则流程结束。
- 接着创建 LogRecord 对象。若注册在 Logger 中的 Filter 过滤后返回 False,则不记录日志,流程结束;否则继续向下执行。
- LogRecord 对象将 Handler 对象传入当前 Logger 对象(子流程)。若 Handler 的日志级别高于设置的日志级别,则判断注册在 Handler 中的 Filter 过滤后是否返回 True 以放行输出日志信息;若返回 False 则不放行,流程结束。
- 若传入的 Handler 级别大于 Logger 中设置的级别(即 Handler 有效),则继续执行;否则流程结束。
- 判断该 Logger 对象是否存在父 Logger 对象。若不存在(即当前 Logger 为最顶层的 root Logger),流程结束;否则将当前 Logger 对象设置为父 Logger 对象,重复上述第 3、4 步,输出父类 Logger 中的日志,直至到达 root Logger 为止。
3、日志输出格式
日志输出格式可自定义,默认格式如下图所示。

4、基本使用
logging 模块使用简单,调用 basicConfig() 方法即可满足基本需求。若未传入参数,将根据默认配置创建 Logger 对象,默认日志级别设为 WARNING,默认输出格式如上所示。该函数可选参数如下表所示。
| 参数名称 | 参数描述 |
|---|---|
| filename | 日志输出到文件的文件名 |
| filemode | 文件模式,r[+]、w[+]、a[+] |
| format | 日志输出的格式 |
| datefat | 日志附带日期时间的格式 |
| style | 格式占位符,默认为 "%" 和 “{}” |
| level | 设置日志输出级别 |
| stream | 定义输出流,用来初始化 StreamHandler 对象,不能 filename 参数一起使用,否则会ValueError 异常 |
| handles | 定义处理器,用来创建 Handler 对象,不能和 filename 、stream 参数一起使用,否则也会抛出 ValueError 异常 |
示例代码如下:
import logging
logging.basicConfig()
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 message')输出结果如下:
WARNING:root:This is a warning message ERROR:root:This is an error message CRITICAL:root:This is a critical message
传入常用参数的示例代码如下(日志格式占位符中的变量将在后文介绍):
import logging
logging.basicConfig(filename="test.log", filemode="w", format="%(asctime)s %(name)s:%(levelname)s:%(message)s", datefmt="%d-%m-%Y %H:%M:%S", level=logging.DEBUG)
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 message')生成的日志文件 test.log,内容如下:
13-10-18 21:10:32 root:DEBUG:This is a debug message 13-10-18 21:10:32 root:INFO:This is an info message 13-10-18 21:10:32 root:WARNING:This is a warning message 13-10-18 21:10:32 root:ERROR:This is an error message 13-10-18 21:10:32 root:CRITICAL:This is a critical message
然而,当发生异常时,直接调用无参数的 debug()、info()、warning()、error()、critical() 方法无法记录异常堆栈信息。需设置 exc_info 参数为 True,或使用 exception() 方法;也可使用 log() 方法,但需同时指定日志级别和 exc_info 参数。
import logging
logging.basicConfig(filename="test.log", filemode="w", format="%(asctime)s %(name)s:%(levelname)s:%(message)s", datefmt="%d-%M-%Y %H:%M:%S", level=logging.DEBUG)
a = 5
b = 0
try:
c = a / b
except Exception as e:
# 下面三种方式三选一,推荐使用第一种
logging.exception("Exception occurred")
logging.error("Exception occurred", exc_info=True)
logging.log(level=logging.DEBUG, msg="Exception occurred", exc_info=True)
5、自定义 Logger
上述基本使用方式虽能快速上手 logging 模块,但通常无法满足实际项目需求,因此需要自定义 Logger。
系统中仅存在一个 Logger 对象,且该对象不可直接实例化,此处应用了单例模式。获取 Logger 对象的方法为 getLogger。
注意:这里的单例模式并非指系统中只有一个 Logger 对象,而是指整个系统仅有一个根 Logger 对象。Logger 对象在执行 info()、error() 等方法时,实际上调用的是根 Logger 对象对应的方法。
我们可以创建多个 Logger 对象,但真正输出日志的是根 Logger 对象。每个 Logger 对象均可设置名称,若设置logger = logging.getLogger(__name__),__name__ 是 Python 中的特殊内置变量,代表当前模块名称(默认为 __main__)。因此建议 Logger 对象的 name 使用以点号分隔的命名空间层级结构。
Logger 对象可配置多个 Handler 和 Filter 对象,Handler 对象又可设置 Formatter 对象。Formatter 用于设定具体的输出格式,常用变量格式如下表所示,所有参数详见 Python(3.7) 官方文档:
| 变量 | 格式 | 变量描述 |
|---|---|---|
| asctime | %(asctime)s | 将日志的时间构造成可读的形式,默认情况下是精确到毫秒,如 2018-10-13 23:24:57,832,可以额外指定 datefmt 参数来指定该变量的格式 |
| name | %(name) | 日志对象的名称 |
| filename | %(filename)s | 不包含路径的文件名 |
| pathname | %(pathname)s | 包含路径的文件名 |
| funcName | %(funcName)s | 日志记录所在的函数名 |
| levelname | %(levelname)s | 日志的级别名称 |
| message | %(message)s | 具体的日志信息 |
| lineno | %(lineno)d | 日志记录所在的行号 |
| pathname | %(pathname)s | 完整路径 |
| process | %(process)d | 当前进程ID |
| processName | %(processName)s | 当前进程名称 |
| thread | %(thread)d | 当前线程ID |
| threadName | %threadName)s | 当前线程名称 |
Logger 和 Handler 对象均可设置级别,默认 Logger 级别为 30(即 WARNING),默认 Handler 级别为 0(即 NOTSET)。logging 模块如此设计旨在提供更高的灵活性。例如,有时既希望在控制台输出 DEBUG 级别日志,又希望在文件中仅输出 WARNING 级别日志。此时只需设置一个最低级别的 Logger 对象,并配置两个不同级别的 Handler 对象即可,示例代码如下:
import logging
import logging.handlers
logger = logging.getLogger("logger")
handler1 = logging.StreamHandler()
handler2 = logging.FileHandler(filename="test.log")
logger.setLevel(logging.DEBUG)
handler1.setLevel(logging.WARNING)
handler2.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s %(name)s %(levelname)s %(message)s")
handler1.setFormatter(formatter)
handler2.setFormatter(formatter)
logger.addHandler(handler1)
logger.addHandler(handler2)
# 分别为 10、30、30
# print(handler1.level)
# print(handler2.level)
# print(logger.level)
logger.debug('This is a customer debug message')
logger.info('This is an customer info message')
logger.warning('This is a customer warning message')
logger.error('This is an customer error message')
logger.critical('This is a customer critical message')
控制台输出结果为:
2018-10-13 23:24:57,832 logger WARNING This is a customer warning message 2018-10-13 23:24:57,832 logger ERROR This is an customer error message 2018-10-13 23:24:57,832 logger CRITICAL This is a customer critical message
文件中输出内容为:
2018-10-13 23:44:59,817 logger DEBUG This is a customer debug message 2018-10-13 23:44:59,817 logger INFO This is an customer info message 2018-10-13 23:44:59,817 logger WARNING This is a customer warning message 2018-10-13 23:44:59,817 logger ERROR This is an customer error message 2018-10-13 23:44:59,817 logger CRITICAL This is a customer critical message
创建了自定义 Logger 对象后,不应再使用 logging 模块中的日志输出方法,因为这些方法使用的是默认配置的 Logger 对象,否则会导致日志信息重复输出。
import logging
import logging.handlers
logger = logging.getLogger("logger")
handler = logging.StreamHandler()
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s %(name)s %(levelname)s %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.debug('This is a customer debug message')
logging.info('This is an customer info message')
logger.warning('This is a customer warning message')
logger.error('This is an customer error message')
logger.critical('This is a customer critical message')输出结果如下(可见日志信息被输出了两遍):
2018-10-13 22:21:35,873 logger WARNING This is a customer warning message WARNING:logger:This is a customer warning message 2018-10-13 22:21:35,873 logger ERROR This is an customer error message ERROR:logger:This is an customer error message 2018-10-13 22:21:35,873 logger CRITICAL This is a customer critical message CRITICAL:logger:This is a customer critical message
说明:在引入包含日志输出的 Python 文件时,如 import test.py,当满足大于当前设置的日志级别时,便会输出导入文件中的日志。
6、Logger 配置
通过上述示例,我们了解了创建 Logger 对象所需的配置。前例中配置直接硬编码在程序中,实际上配置还可以从字典类型对象或配置文件中获取。打开 logging.config Python 文件,可以看到其中的配置解析转换函数。
从字典中获取配置信息:
import logging.config
config = {
'version': 1,
'formatters': {
'simple': {
'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
},
# 其他的 formatter
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'level': 'DEBUG',
'formatter': 'simple'
},
'file': {
'class': 'logging.FileHandler',
'filename': 'logging.log',
'level': 'DEBUG',
'formatter': 'simple'
},
# 其他的 handler
},
'loggers':{
'StreamLogger': {
'handlers': ['console'],
'level': 'DEBUG',
},
'FileLogger': {
# 既有 console Handler,还有 file Handler
'handlers': ['console', 'file'],
'level': 'DEBUG',
},
# 其他的 Logger
}
}
logging.config.dictConfig(config)
StreamLogger = logging.getLogger("StreamLogger")
FileLogger = logging.getLogger("FileLogger")
# 省略日志输出
从配置文件中获取配置信息:
常见的配置文件格式包括 ini、yaml 和 JSON,甚至可以从网络获取,只要具备相应的文件解析器即可。下文仅展示 ini 格式和 yaml 格式的配置示例。
test.ini 文件
[loggers]
keys=root,sampleLogger
[handlers]
keys=consoleHandler
[formatters]
keys=sampleFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler
[logger_sampleLogger]
level=DEBUG
handlers=consoleHandler
qualname=sampleLogger
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=sampleFormatter
args=(sys.stdout,)
[formatter_sampleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)stestinit.py 文件
import logging.config
logging.config.fileConfig(fname='test.ini', disable_existing_loggers=False)
logger = logging.getLogger("sampleLogger")
# 省略日志输出test.yaml 文件
version: 1
formatters:
simple:
format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
handlers:
console:
class: logging.StreamHandler
level: DEBUG
formatter: simple
loggers:
simpleExample:
handlers: [console]
propagate: no
root:
level: DEBUG
handlers: [console]testyaml.py 文件
import logging.config
# 需要安装 pyymal 库
import yaml
with open('test.yaml', 'r') as f:
config = yaml.safe_load(f.read())
logging.config.dictConfig(config)
logger = logging.getLogger("sampleLogger")
# 省略日志输出
7、实战中的问题
1、中文乱码
上述示例中日志输出均为英文,未显现将日志写入文件时的中文乱码问题。如何解决此问题?FileHandler 创建对象时可设置文件编码,若将文件编码设置为 “utf-8”(utf-8 与 utf8 等价),即可解决中文乱码。一种方法是自定义 Logger 对象,需编写大量配置;另一种方法是使用默认配置方法 basicConfig(),传入 handlers 处理器列表对象,并在其中的 handler 设置文件编码。网上许多方法无效,关键参考代码如下:
# 自定义 Logger 配置
handler = logging.FileHandler(filename="test.log", encoding="utf-8")
# 使用默认的 Logger 配置
logging.basicConfig(handlers=[logging.FileHandler("test.log", encoding="utf-8")], level=logging.DEBUG)
2、临时禁用日志输出
有时我们希望暂时不输出日志,随后又希望恢复输出。若使用 print() 方法打印信息,需注释所有 print() 语句;而使用 logging 后,便拥有了一键开关日志的“魔法”。一种方法是在使用默认配置时,给 logging.disable() 方法传入禁用的日志级别,即可禁止输出设定级别以下的日志;另一种方法是在自定义 Logger 时,将 Logger 对象的 disable 属性设为 True(默认值为 False,即不禁用)。
logging.disable(logging.INFO)
logger.disabled = True3、日志文件按照时间划分或者按照大小划分
若将日志保存在单个文件中,随着时间推移或日志增多,单个文件体积会变得巨大,既不利于备份也不便于查看。能否按时间或大小对日志文件进行划分?答案是可以,且实现简单。logging 模块已考虑到此需求,logging.handlers 文件中提供了 TimedRotatingFileHandler 和 RotatingFileHandler 类,分别实现按时间和大小划分。打开该 handlers 文件,可见其他功能的 Handler 类,它们均继承自基类 BaseRotatingHandler。
# TimedRotatingFileHandler 类构造函数
def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None):
# RotatingFileHandler 类的构造函数
def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False)
示例代码如下:
# 每隔 1000 Byte 划分一个日志文件,备份文件为 3 个
file_handler = logging.handlers.RotatingFileHandler("test.log", mode="w", maxBytes=1000, backupCount=3, encoding="utf-8")# 每隔 1小时 划分一个日志文件,interval 是时间间隔,备份文件为 10 个
handler2 = logging.handlers.TimedRotatingFileHandler("test.log", when="H", interval=1, backupCount=10)Python 官网声称 logging 库是线程安全的,但在多进程、多线程或多进程多线程环境中,仍有值得考虑的问题,例如如何将日志按进程(或线程)划分为不同的日志文件,即一个进程(或线程)对应一个文件。
总结:Python logging 库设计非常灵活,若有特殊需求,还可基于此基础 logging 库进行改进,创建新的 Handler 类以解决实际开发中的问题。
到此这篇关于2022最新Python日志库logging总结的文章就介绍到这了,更多相关Python日志库logging内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













