本文全面深入地介绍了 Python 的日志记录功能,从基础概念、何时使用日志,到如何进行基础日志操作(如记录到文件、记录变量数据、更改消息格式等),再到进阶的日志组件(记录器、处理器、过滤器和格式器)解析、多种配置方式讲解,以及日志使用中的特殊情况处理和优化建议等内容,旨在帮助读者系统掌握 Python 日志记录技术,编写出更健壮、易维护的代码。
目录
一、日志基础教程
(一)什么时候使用日志
日志记录用于追踪软件执行时发生的事件,通过不同的日志级别来区分事件的重要性。在开发过程中,不同的任务适合使用不同的工具。例如,对于命令行或程序结果显示在控制台的场景,print()
函数更为合适;而在记录程序运行时事件报告(如状态监控和错误调查),应使用日志记录器的info()
方法(诊断时用debug()
方法) 。在处理特殊运行时事件时,若事件可避免,代码库中使用warnings.warn()
,若客户端应用无法干预但事件需关注,则用日志记录器的warning()
方法。报告错误时,一般情况引发异常,若不想引发异常(如长时间运行的服务端进程错误处理),则使用日志记录器的error()
、exception()
或critical()
方法 。
任务 | 适合工具 |
---|---|
命令行或程序结果显示在控制台 | print() |
记录程序运行时事件报告(普通操作) | 日志记录器的info() (诊断用debug() ) |
基于特殊运行时事件提出警告(可避免,需修改客户端应用) | warnings.warn() |
基于特殊运行时事件提出警告(客户端无法干预) | 日志记录器的warning() |
报告错误 | 引发异常 |
报告错误但不引发异常(如服务端进程错误处理) | 日志记录器的error() 、exception() 或critical() |
日志记录器方法按事件严重程度命名,标准级别从低到高分别为DEBUG 、INFO 、WARNING 、ERROR 、CRITICAL ,默认级别是WARNING ,意味着只追踪该级别及以上的事件 。 |
|
级别 | 何时使用 |
---- | ---- |
DEBUG |
诊断问题时,记录细节信息 |
INFO |
确认程序按预期运行 |
WARNING |
表明有意外情况(如磁盘空间不足),但程序仍正常运行 |
ERROR |
程序某些功能因严重问题无法正常执行 |
CRITICAL |
严重错误,程序无法继续执行 |
(二)一个简单的例子
直接使用logging
模块函数(如logging.warning()
)是在根日志记录器上操作,这些函数在未配置时会调用basicConfig()
发挥作用。
import logging logging.warning('Watch out!') # 将打印一条消息到控制台 logging.info('I told you so') # 不会打印任何消息,因为默认级别为WARNING
上述代码中,WARNING
级别的消息会被打印,而INFO
级别的消息因低于默认级别不会显示。在实际开发中,建议创建日志记录器并调用其方法,以便更好地控制日志配置 。
(三)记录日志到文件
使用basicConfig()
函数可以将日志记录到文件中。例如:
import logging logger = logging.getLogger(__name__) logging.basicConfig(filename='example.log', encoding='utf-8', level=logging.DEBUG) logger.debug('This message should go to the log file') logger.info('So should this') logger.warning('And this, too') logger.error('And non-ASCII stuff, too, like Øresund and Malmö')
通过设置level
参数为logging.DEBUG
,可以记录所有级别的消息。若要从命令行设置日志级别,可以使用如下方式:
import logging import sys # 假定log级别从命令行参数获取 loglevel = sys.argv[1] if len(sys.argv) > 1 else 'INFO' numeric_level = getattr(logging, loglevel.upper(), None) if not isinstance(numeric_level, int): raise ValueError('Invalid log level: %s' % loglevel) logging.basicConfig(level=numeric_level)
如果多次运行脚本,日志消息默认会追加到文件中。若希望每次运行重新开始记录,可以设置filemode='w'
。
(四)记录变量数据
记录变量数据时,使用%-s
形式的字符串格式化方式将变量数据合并到事件描述消息中 。例如:
import logging logging.warning('%s before you %s', 'Look', 'leap!')
这种方式是为了向后兼容,新的格式化选项(如str.format()
和string.Template
)也受支持,但本文未深入探讨 。
(五)更改显示消息的格式
通过在basicConfig()
中设置format
参数,可以更改消息的显示格式 。例如:
import logging logging.basicConfig(format='%(levelname)s :%(message)s', level=logging.DEBUG) logging.debug('This message should appear on the console') logging.info('So should this') logging.warning('And this, too')
上述代码将消息格式设置为 “级别:消息”,并显示了不同级别的日志消息。更多可用于格式字符串的内容可参考LogRecord
属性 。
(六)在消息中显示日期 / 时间
在格式字符串中使用%(asctime)s
可以显示事件的日期和时间 。例如:
import logging logging.basicConfig(format='%(asctime)s %(message)s') logging.warning('is when this event was logged.')
若需要更多控制日期 / 时间的格式,可以为basicConfig
提供datefmt
参数,其格式与time.strftime()
支持的格式相同 。例如:
import logging logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d /%Y %I:%M:%S %p') logging.warning('is when this event was logged.')
二、进阶日志教程
(一)日志库组件
日志库采用模块化设计,包含记录器、处理器、过滤器和格式器 。
记录器:暴露应用程序代码使用的接口,负责确定要处理的日志消息,并将相关消息传递给感兴趣的日志处理器 。
处理器:将日志记录发送到适当的目标,如文件、控制台、邮件等 。
过滤器:提供更细粒度的功能,用于确定要输出的日志记录 。
格式器:指定最终输出中日志记录的样式 。 日志事件信息在
LogRecord
实例中在这些组件之间传递 。
(二)记录器
记录器对象有配置和发送消息两类主要方法 。配置方法包括setLevel()
(指定处理的最低严重性级别)、addHandler()
和removeHandler()
(添加和删除处理器对象)、addFilter()
和removeFilter()
(添加和移除过滤器) 。发送消息的方法有debug()
、info()
、warning()
、error()
、critical()
,这些方法创建对应级别的日志记录;exception()
用于在异常处理程序中记录异常信息和堆栈追踪;log()
用于使用自定义日志级别 。 记录器名称具有层次结构,通过getLogger()
获取记录器实例,多次调用相同名称返回同一实例 。子记录器会将消息传播到父记录器的处理器,可通过设置propagate=False
关闭传播 。记录器还有有效等级的概念,若未显式设置级别,将使用父记录器的级别 。
import logging
# 获取记录器
logger = logging.getLogger('my_module')
logger.setLevel(logging.DEBUG)
# 创建处理器
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
# 创建格式器
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
# 添加处理器到记录器
logger.addHandler(ch)
# 记录不同级别的消息
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')
(三)处理器
Handler
对象负责将日志消息分派到指定目标 。标准库提供了多种处理器类型,如StreamHandler
(发送消息到流)、FileHandler
(发送消息到文件)等 。应用程序开发人员常用的处理器配置方法有setLevel()
(设置处理器处理的最低严重性级别)、setFormatter()
(选择使用的格式器对象)、addFilter()
和removeFilter()
(配置和取消配置过滤器对象) 。应用程序代码不应直接实例化Handler
,而应使用其子类 。
import logging # 创建文件处理器 file_handler = logging.FileHandler('example.log') file_handler.setLevel(logging.INFO) # 创建格式器并设置给处理器 formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') file_handler.setFormatter(formatter) # 获取记录器并添加处理器 logger = logging.getLogger('my_module') logger.addHandler(file_handler) # 记录消息 logger.info('This message will be logged to the file')
(四)格式器
格式化器对象用于配置日志消息的最终格式 。构造函数有fmt
(消息格式字符串)、datefmt
(日期格式字符串)和style
(样式指示符)三个可选参数 。样式指示符有'%'
、'{'
、'$'
,分别对应不同的格式化方式 。例如:
import logging
# 创建格式器
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S', style='%')
# 创建处理器并设置格式器
ch = logging.StreamHandler()
ch.setFormatter(formatter)
# 获取记录器并添加处理器
logger = logging.getLogger('my_module')
logger.addHandler(ch)
# 记录消息
logger.info('This is a formatted log message')
(五)配置日志记录
配置日志记录有三种方式:
使用 Python 代码显式创建组件:通过代码创建记录器、处理器和格式器,并进行相应配置 。
使用配置文件:创建日志配置文件(如
logging.conf
),使用fileConfig()
函数读取 。配置文件中需定义loggers
、handlers
、formatters
等部分 。
[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
使用字典配置:创建配置信息字典并传递给
dictConfig()
函数,这种方式是新应用程序和部署的推荐配置方法,支持更多配置选项,如可以使用 JSON 或 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]
需要注意的是,fileConfig()
和dictConfig()
中的disable_existing_loggers
参数默认为True
,可能会禁用已存在的非根记录器,使用时需根据需求进行设置 。
(六)如果没有提供配置会发生什么
若未提供日志记录配置,日志事件将使用lastResort
处理器输出。该处理器类似StreamHandler
,将事件描述消息写入sys.stderr
,且不进行格式化,级别设为WARNING
,会输出严重性在此级别以上的所有事件 。在 Python 3.2 之前,行为有所不同,可通过将lastResort
设为None
获取之前的行为 。
(七)为库配置日志
开发带日志的库时,应在文档中说明库的日志使用方式,如记录器名称 。若不希望在无日志配置时打印消息,可以将NullHandler
附加到库的顶级记录器 。同时,强烈建议不要将日志记录到根记录器,也不要添加除NullHandler
以外的其他处理器,以免干扰应用程序开发人员的日志配置 。
import logging # 为库配置日志 logging.getLogger('my_library').addHandler(logging.NullHandler())
(八)日志级别
日志记录级别有对应的数值,如CRITICAL
为 50、ERROR
为 40 等 。记录器和处理器都可以设置级别,记录器根据自身级别和调用方法的级别决定是否生成记录消息,处理器根据自身级别决定是否调度事件 。
级别 | 数值 |
---|---|
CRITICAL |
50 |
ERROR |
40 |
WARNING |
30 |
INFO |
20 |
DEBUG |
10 |
NOTSET |
0 |
(九)自定义级别
虽然可以定义自定义级别,但一般不建议在开发库时使用,因为多个库定义的自定义级别可能导致日志记录输出难以控制和解释 。
(十)有用的处理器
标准库提供了多种有用的处理器子类,如:
处理器类 | 功能 |
---|---|
StreamHandler |
发送消息到流(类似文件对象) |
FileHandler |
将消息发送到硬盘文件 |
RotatingFileHandler |
发送消息到硬盘文件,支持最大日志文件大小和日志文件轮换 |
TimedRotatingFileHandler |
发送消息到硬盘文件,按特定时间间隔轮换日志文件 |
SocketHandler |
将消息发送到 TCP/IP 套接字(从 3.4 开始支持 Unix 域套接字) |
DatagramHandler |
将消息发送到 UDP 套接字(从 3.4 开始支持 Unix 域套接字) |
SMTPHandler |
将消息发送到指定的电子邮件地址 |
SysLogHandler |
将消息发送到 Unix syslog 守护程序(可能在远程计算机上) |
NTEventLogHandler |
将消息发送到 Windows NT/2000/XP 事件日志 |
MemoryHandler |
将消息发送到内存中的缓冲区,满足特定条件时刷新 |
HTTPHandler |
使用GET 或POST 方法将消息发送到 HTTP 服务器 |
WatchedFileHandler |
监视要写入日志的文件,文件更改时重新打开(仅在类 Unix 系统上有用) |
QueueHandler |
将消息发送到队列(如queue 或multiprocessing 模块中的队列) |
NullHandler |
不对错误消息执行任何操作,用于避免 “找不到日志记录器处理器” 消息 |
NullHandler
、StreamHandler
和FileHandler
在核心日志包中定义,其他处理器在logging.handlers
中定义 。
(十一)记录日志时引发的异常
日志包在生产环境下会忽略记录日志时发生的异常(SystemExit
和KeyboardInterrupt
除外),Handler
子类的emit()
方法中发生的其他异常会传递给handleError()
方法 。handleError()
默认检查raiseExceptions
变量,开发时建议设为True
以便收到异常通知,生产环境设为False
。
(十二)使用任意对象作为消息
日志记录时可以传递任意对象作为消息,日志系统会在需要时调用对象的__str__()
方法将其转换为字符串 。例如,SocketHandler
会用 pickle 处理事件后通过网络发送 。
(十三)优化
为避免不必要的资源消耗,可以使用isEnabledFor()
方法判断记录器是否会处理某个级别的事件,从而决定是否执行一些开销较大的操作 。例如:
import logging
logger = logging.getLogger(__name__)
def expensive_func1():
print('Executing expensive_func1')
return 'result1'
def expensive_func2():
print('Executing expensive_func2')
return 'result2'
if logger.isEnabledFor(logging.DEBUG):
logger.debug('Message with %s, %s', expensive_func1(), expensive_func2())