logging 模块实现了python的日志能力。本文通过几个示例展示一些重点概念与用法。
一、线程安全介绍
logging 模块的目标是使客户端不必执行任何特殊操作即可确保线程安全。 它通过使用线程锁来达成这个目标;用一个锁来序列化对模块共享数据的访问,并且每个处理程序也会创建一个锁来序列化对其下层 I/O 的访问。
如果你要使用 signal 模块来实现异步信号处理程序,则可能无法在这些处理程序中使用 logging。 这是因为 threading 模块中的锁实现并非总是可重入的,所以无法从此类信号处理程序发起调用。
二、快速使用
2.1 基本流程
python的日志功能由以下三个模块(module)构成(在import的时候,会碰到导入这三个模块):
- logging:模块提供主要的面向客户端的API。
- logging.config:模块提供API来配置客户端中的日志记录。
- logging.handlers:模块提供不同的处理程序,涵盖常见的处理方式并分发日志记录。
具体的,主要由下面5个类构成:
Logger
:日志器,暴露函数给应用程序,基于日志记录器和过滤器级别决定哪些日志有效。
LogRecord
:日志记录器,将日志传到相应的处理器处理。
Handler
:处理器,将(日志记录器产生的)日志记录发送至合适的目的地。
Filter
:过滤器,提供了更好的粒度控制,它可以决定输出哪些日志记录。
Formatter
:格式化器,指明了最终输出中日志记录的布局。
其中Logger
日志器是分层级的,根部的为根日志器,子日志器会向上调用父日志器的handle(参数propagate
确定,该参数默认为True
):
注意: 博主根据经验推测:A、B和root之间仅是拷贝了root的handler配置,不存在propagate为True时的调用关系,所以用虚线表示(见示例2)
具体流程图如下:
logger为上图中的某一级创建的一个实例,可以是logger
、logger1
、logger2
,为了方便,都写成了logger
看图即可理解流程,这里不赘述。
注意:
logger.propagate=true
时不是调用父记录器Logger
,只调用其处理程序(handlers)。这意味着记录器类中的过滤器和其他代码不会在父级上执行。这是向记录器添加过滤器时的常见陷阱。- 可以看到,过滤器有两个,一个值在
Logger
中,一个是在Handler
中,在设置时一定要注意。 - 参数
propagate
确默认为True
,所以父日志器的handler默认全部调用,这也就是为什么通常子日志器能调用根日志器handler输出的愿意。(注意,验证中发现一个例外的点:如果子日志器配置了hanlder,而根日志器的默认handler不做处理,则根日志器的默认handler不再调用,见示例2) - 一个日志器可配置多个handler,不同的handler可以配置不同的输出源,比如h1输出到文件,h2通过http输出到某个发送短信的服务提醒运维。
日志级别:
级别 | 数值 | 何种含义 / 何时使用 |
---|---|---|
logging.NOTSET | 0 | 当在日志记录器上设置时,表示将查询上级日志记录器以确定生效的级别。 如果仍被解析为 NOTSET,则会记录所有事件。 在处理器上设置时,所有事件都将被处理。 |
logging.DEBUG | 10 | 详细的信息,通常只有试图诊断问题的开发人员才会感兴趣。 |
logging.INFO | 20 | 确认程序按预期运行。 |
logging.WARNING | 30 | 表明发生了意外情况,或近期有可能发生问题(例如‘磁盘空间不足’)。 软件仍会按预期工作。 |
logging.ERROR | 40 | 由于严重的问题,程序的某些功能已经不能正常执行 |
logging.CRITICAL | 50 | 严重的错误,表明程序已不能继续执行 |
若设置了一个level,则只有高于或等于这个level的日志才能展示。比如,logger.setLevel(logging.ERROR)
,则只有logging.error('日志信息')
和logging.critical('日志信息')
才能输出。
默认值是WARNING
,即当等级大于等于WARNING
才输出。
2.2 使用示例
示例1: 一步配置根日志器
一步配置,在根日志器上配置,默认输出到控制台, 本示例输出到指定生成的filename文件中。
根日志器初始化:logging.basicConfig(**kwargs)
# myapp.py
import logging
import time
logger = logging.getLogger(__name__)
def main():
logging.basicConfig(filename = time.strftime('my-%Y-%m-%d.log'), level=logging.INFO,format = '%(asctime)s %(levelname)-10s %(processName)s %(name)s %(message)s', datefmt = '%Y-%m-%d-%H-%M-%S')
logger.info('Started') # 本陈旭创建的__name__ 日志器
logging.debug('debug') # 根日志器
logging.info('info')
logging.warning('warning')
logging.error('error')
logging.critical('critical')
logging.log(logging.WARNING, 'another warning')
logging.log(40, 'another error')
if __name__ == '__main__':
main()
输出到my-2024-07-27.log
文件
2024-07-27-14-55-05 INFO MainProcess __main__ Started
2024-07-27-14-55-05 INFO MainProcess root info
2024-07-27-14-55-05 WARNING MainProcess root warning
2024-07-27-14-55-05 WARNING MainProcess __main__ warning
2024-07-27-14-55-05 ERROR MainProcess root error
2024-07-27-14-55-05 CRITICAL MainProcess root critical
2024-07-27-14-55-05 WARNING MainProcess root another warning
2024-07-27-14-55-05 ERROR MainProcess root another error
更多格式输出可参考:format格式说明
注意:
- 对
basicConfig()
设置的是根日志器的属性,调用应该在debug()
,info()
等的前面。因为它被设计为一次性的配置,只有第一次调用会进行操作,随后的调用不会产生有效操作。
示例2:多步配置子日志器
在子日志器上配置,常用配置如下:
# 日志文件固定大小
impo