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)实例handlerformatter:是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:根Loggerdisable:禁用日志级别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实例的管理。