python语言内置了一个强大的日志模块logging
,也是python内部最为复杂的功能模块之一,通过这个模块我们能够实现不同样式的日志打印。关于logging
模块的官方文档也非常完备:
- logging的用法
- 日志常用指引
- 日志操作手册
- logging API
- logging日志记录工具
- logging config
- logging handlers
为此,本文起我们对python的logging
模块进行深入剖析,从而让大家能够更好地掌握python的logging
模块。
基础配置
我们首先从一段最基本的代码入手:
1 | import logging |
从语义上看,代码的含义是:设置日志基础配置为打印到标准输出流,只接受INFO
级别以上的日志。很明显,打印出来的结果是:
1 | INFO:root:helloworld |
我们来看下在源代码里面具体做了什么。首先从logging.basicConfig
看起,该方法为logging
系统设定了基础配置。其代码如下:
1 | # logging/__init__.py |
传入的参数是unpacked mapping
式的kwargs
,会在代码里面被pop
出来然后根据实际配置值进行配置设定。我们先来看看其中具体由执行哪些逻辑:
- 如果
force
参数被启用,会调用root.removeHandler
方法清空handlers
handlers
/stream
/filename
的共存检查- 可以推测到,这三个概念实际都指代日志的输出目标
- 如果指定
filename
,就创建一个FileHandler
实例- 写入模式、编码错误处理等参数也会从
kwargs
里取出,实际就是open
里面的参数
- 写入模式、编码错误处理等参数也会从
- 如果指定
stream
,就创建一个StreamHandler
实例 - 根据
datefmt
、style
、format
配置,创建一个Formatter
实例,给到所有的handler
实例 - 根据
level
配置,设置root
的level
属性
总的来看,这个流程出现了3个新概念:
root
,实际是一个RootLogger(WARNING)
实例handler
formatter
:是handler
的属性之一,用来格式化日志信息
本篇文章里面我们首先看root
,也就是Logger
相关的内容。后面再看handler
之类的例子。
Logger的实现
要看Logger
,我们可以从RootLogger
看起,其代码如下:
1 | root = RootLogger(WARNING) |
RootLogger
继承了Logger
,__init__
中实际上是创建了名为root
的Logger
。此外,RootLogger
还定义了__reduce__
函数用于在序列化/反序列化时支持实例的解析。
这里我们能够推测到,Logger.manager
实例会对Logger
实例进行管理。因此首先我们看下Logger
本身,然后再看下Manager
里面。
我们从打印日志的逻辑看起。假使我们调用Logger
实例的info
方法打印日志,实际会涉及到这些代码:
1 | class Logger(Filterer): |
在info
逻辑开头,会调用isEnabledFor
方法判断当前设置的日志级别level
是否合法。判断的依据是先看manager
的disable
设定,然后再看自己是否有设定level
,如果还没有的话就一直在父logger
里面找,直到看到设定的level
,然后进行大小比较。
从这里我们也可以看到,logger
实例之间是会存在层级关系的。具体后面慢慢说。
之后就会调用_log
方法真正打印日志,其实现如下:
1 | class Logger(Filterer): |
_log
执行了三个步骤:
- 通过
findCaller
,找到是哪个文件哪一行代码调用了打印日志- 方法是遍历栈帧,检查到第一个非
logging
源代码的调用,从而判断到为实际调用打印日志逻辑的一行
- 方法是遍历栈帧,检查到第一个非
- 通过
makeRecord
,为当前需要打印的日志创建了一个LogRecord
实例LogRecord
实例存储了一行日志所需的所有信息,详细实现可以看类定义
handle
创建的LogRecord
实例- 首先通过
filter
方法看这个日志record
能否被打印filter
的详细实现可以看Filter
类定义
- 通过
callHandlers
调用名下所有handlers
处理LogRecord
实例,将日志打印到不同的地方- 在遍历所有
handler
时也检查handler
是否能够打印对应等级的日志 - 如果没有
handler
能打印日志,用特殊的Logger
实例lastResort
来兜底
- 在遍历所有
- 首先通过
所以我们总结一下Logger
实例打印日志时的重点:
- 日志记录的生产和消费是分离的
- 生产日志记录需要判断是否符合
Logger
及Manager
实例的日志级别配置,可能会追溯到上层的Logger
实例配置 - 消费日志记录首先看是否被
Logger
实例自己过滤掉,然后遍历所有消费者,如果满足日志级别条件即可开始打印操作
Manager的实现
Logger
的差不多看完了,接下来我们看一下Manager
,也就是Logger
实例的管理器。
1 | class Manager(object): |
Manager在日志系统内是以单例的形式存在的,其包含如下属性:
root
:根Logger
disable
:禁用日志级别loggerDict
:存储logger实例及对应名称的字典loggerClass
:Logger实例的类logRecordFactory
:日志记录的创建方法,前面有提
Manager
最大的作用是管理Logger
实例。通过getLogger
方法,我们可以获得一个Logger
实例:
1 | class Manager(object): |
如果Logger
名字没有注册,会触发懒加载Logger
实例。在懒加载的过程最终,会设定该Logger
实例所属的父级Logger
实例。从_fixupParents
方法可以清楚看到,通过Logger
名字的.符号能够分隔Logger
实例所在的层级。如果上一层Logger
实例不存在,会用一个PlaceHolder
实例代替,否则会直接赋值rv
,break
,然后设置parent
。
如果Logger
名字已经注册,且注册的只是一个PlaceHolder
实例的话,还要额外通过_fixupChildren
方法,将已定义的子Logger
绑定到自己身上。
通过这样的实现,就实现了Logger
实例的管理。