Logging in Python

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 中。

filenamefilemode参数

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 也是无效的。

格式化输出

由上文可知, basicConfigformat 参数可以调整输出的格式。这里再介绍几种 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 的内容输出到多个位置,并自定义输出的格式和敏感级别。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容