Python 中的 logging 一直是我的知识盲区,今天看代码的过程中再次遇到,虽然和代码主体功能没有什么关系,但不想再绕过这块,遂花了一点时间学习了一下 logging 的用法并在此记录。
本文参考了这篇博客:Logging in Python
5 种事件级别
按照事件的严重程度,logging 模块划分了五种事件级别:
- DEBUG
- INFO
- WARNING
- ERROR
- CRITICAL
严重程度由上到下递增。如下代码片段展示了logging 最基础的用法,以及默认情况下,五种事件等级的不同表现。
import logging
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
可以看到,默认情况下 shell 输出了 WARNING 以及以上级别的 event,而忽略了 DEBUG,INFO 级别的 event。其输出格式为:[levelname]:[name]:[message]。
基础设置
通过基础设置,我们可以设置 log 输出的等级、格式、目标文件、目标文件的写入方式等等。这里用到了 logging.basicConfig(**kwargs),它有如下几个常用的参数:
- level :被记录的 event 的最小级别
- filename :输出的文件名
- filemode :输出文件的写入方式,'a' (默认) 或者 'w',分别代表接续旧 log 文件,还是抹除旧记录重写
- format :每行记录的格式
首先研究 level 参数:
import logging
logging.basicConfig(level=logging.DEBUG) # DEBUG 级别之上(包含 DEBUG)的 event 都会被记录
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')
shell 输出为:
DEBUG: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 message
和上面没有设置 level=logging.DEBUG 的对比,我们发现输出的 event 更多了,所有的 event 都展示在了 shell 中。
filename 和 filemode参数
import logging
logging.basicConfig(filename='app.log', filemode='w') # 将 log 保存到 app.log 文件中,写入方式为重写
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')
执行完上述代码,打开 app.log 文件,可以看到记录了 warning、error 和 critical,如果想记录 debug 和 info,还需要在 basicConfig 中设置 level='logging.DEBUG'。
format 参数
默认情况下,每条 log 的记录格式均为:[levelname]:[name]:[message]。通过设置 format 参数,可以自定义 log 的格式。
import logging
logging.basicConfig(filename='app.log', filemode='w', format='%(name)s - %(levelname)s - %(message)s')
logging.warning('This is a warning')
# app.log 文件中记录的 log 为:
# root - WARNING - This is a warning
需要注意的是,basicConfig 设置 root logger 时只能被调用一次,之后的调用无法改变 root logger,如果在显式调用 basicConfig 之前,调用了 logging.warning() 之类的方法,那么 basicConfig 也会被隐式地在被调用,这之后再显式调用 basicConfig 也是无效的。
格式化输出
由上文可知, basicConfig 的 format 参数可以调整输出的格式。这里再介绍几种 log 条目中常见的元素:进程 ID 和时间。
process
import logging
logging.basicConfig(format='%(process)d - %(levelname)s - %(message)s')
logging.warning('This is a warning')
# 输出
# 19801 - WARNING - This is a warning
asctime
import logging
logging.basicConfig(format='%(asctime)s - %(message)s', level=logging.INFO)
logging.info('Admin logged in')
# 输出
# 2021-05-24 17:11:53,621 - Admin logged in
还可以设置时间的格式
import logging
logging.basicConfig(format='%(asctime)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S')
logging.warning('Admin logged in')
# 输出
# 24-May-21 17:17:00 - Admin logged in
# 个人感觉 datefmt='%Y-%m-%d %H:%M:%S' 会比较不容易产生歧义,同时比默认的格式简洁
# 2021-05-24 17:18:38 - Admin logged in
记录变量数据
记录变量数据也很简单,就是普通的字符串格式化那一套:
import logging
name = 'John'
logging.error('%s raised an error', name)
logging.error(f'{name} raised an error') # 也可以使用 python 的 f-string
捕获错误
通过设置 exc_info=True 可以捕获 exception 错误,将其加入 log。
import logging
a = 5
b = 0
try:
c = a / b
except Exception as e:
logging.error("Exception occurred", exc_info=True)
# 如果不设置,将不会出现 ZeroDivisionError,只会出现含义不明的 Exception occured
Classes and Functions
上面使用 logging 的方式是不规范的,在代码中应该自己定义一个 logger,而不是使用默认的 root logger,采用类似 logging.debug() 的方式生成 event 记录。
import logging
logger = logging.getLogger('example_logger')
logger.warning('This is a warning')
logging.warning('This is another warning')
# 输出
# This is a warning
# WARNING:root:This is another warning
注意自定义 logger 和 默认 root logger 的不同之处:默认 logger 的输出自带了格式,而自定义logger 的格式必须显式指定:
import logging
logging.basicConfig(format='%(levelname)s:%(name)s:%(message)s')
logger = logging.getLogger('example_logger')
logger.warning('This is a warning')
# 输出
# WARNING:example_logger:This is a warning
使用 Handlers
Handlers 主要用在把 logger 的内容以不同格式输出到多个位置,例如同时输出到 shell 终端和文件中,输出到 shell 的 log 只展示 warning 级别以上的信息,而输出到文件的 log 记录所有 log。
import logging
# Create a custom logger
logger = logging.getLogger(__name__)
# Create handlers
c_handler = logging.StreamHandler()
f_handler = logging.FileHandler('file.log')
c_handler.setLevel(logging.WARNING)
f_handler.setLevel(logging.ERROR)
# Create formatters and add it to handlers
c_format = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
f_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
c_handler.setFormatter(c_format)
f_handler.setFormatter(f_format)
# Add handlers to the logger
logger.addHandler(c_handler)
logger.addHandler(f_handler)
logger.warning('This is a warning')
logger.error('This is an error')
上述代码首先自定义了一个 logger
然后定义了两个 handler,分别控制输出到文件和输出到 shell
通过 setLevel,输出到 shell 的 handler 敏感级别设置为 warning,输出到文件的 handler 敏感级别设置为 error
定义了两个 formatter,输出到文件的 formatter 比输出到 shell 的formatter 多了一个时间
将两个 formatter 通过 setFormatter 分别绑定给两个 handler
通过 addHandler 为前面定义的 logger 添加 handler
通过上述步骤,既可以将 logger 的内容输出到多个位置,并自定义输出的格式和敏感级别。