在上篇文章里说完了日志实例Logger
和日志管理Manager
,现在该提到Handler
了。Handler
是日志信息的消费者,单个Logger
实例注册多个Handler
,每生成一个LogRecord
,就会被合法的Handler
消费,在不同地方打印出日志信息。
要研究Handler
,首先需要看下基类的实现:
1 | class Handler(Filterer): |
Handler
会通过自带的重入锁限制日志记录被串行处理。Handler
也是继承Filterer
,首先会通过filter
过滤日志是否满足Handler
的要求,如果合法,然后调用emit
方法处理日志。
emit
方法在基类是NotImplemented
,需要子类加以实现。因此接下来我们具体抽几个例子来看。
StreamHandler
StreamHandler
打印日志的实现比较简单:
1 | class StreamHandler(Handler): |
StreamHandler
默认的stream
是sys.stderr
。只要我们在basicConfig
中给到的stream
实例实现了write
跟flush
方法,就可以被放到StreamHandler
里头处理日志。日志首先通过format
方法被Handler
实例的formatter
格式化,然后再被输出。
以sys.stderr
为例,我们来看标准错误流在python
中的源码实现。首先需要追溯sys.stderr
的来源,我们节选一部分代码:
1 | // pylifecycle.c |
在pylifecycle.c
中,sys.stderr
被设置,最终调用了create_stdio
设置了stream
实例。其中,会调用io
模块的open
以及TextIOWrapper
方法:
1 | // _iomodule.c |
所以总的来看,在windows
中,stderr
实例的创建流程大致是这样:
- 创建了一个
PyWindowsConsoleIO_Type
的实例PyWindowsConsoleIO_Type
实质是封装了和windows
命令行终端交互的方法
- 创建了一个
PyBufferedWriter_Type
实例去实现缓冲输出流- 该实例的
raw
属性即为PyWindowsConsoleIO_Type
的实例
- 该实例的
- 创建了一个
TextIOWrapper
实例封装PyBufferedWriter_Type
实例,得到stderr
的stream
实例
当调用stderr.write
和stderr.flush
时,最终会调用BufferedWriter
的write
和flush
。在write
过程中会将数据写入缓冲区,而在write
跟flush
过程中都会调用raw.write
方法尝试将缓冲区数据刷掉,并写入windows
命令行。有兴趣的同学,可以深入研究bufferedio.c
里面的内容。
FileHandler
FileHandler
继承了StreamHandler
,可以将日志记录打印到文件里面,其代码如下:
1 | class FileHandler(StreamHandler): |
FileHandler
为StreamHandler
底层提供了一个内置open
方法文件流。我们知道这样的文件流也会有write
以及flush
方法,所以显而易见日志会输出到对应的文件当中。
TimedRotatingFileHandler
TimedRotatingFileHandler
也是常用的日志Handler
之一,它可以实现按规定的时间间隔打印各个时段的日志到不同文件中。其继承链如下:
1 | # pprint.pprint(TimedRotatingFileHandler.__mro__) |
首先我们来看BaseRotatingHandler
,它继承了FileHandler
,其实现如下:
1 | class BaseRotatingHandler(logging.FileHandler): |
BaseRotatingHandler
在打印日志记录前,首先会通过shouldRollover
方法根据日志记录信息判断是否要翻篇,如果需要的话就调用doRollOver
方法翻篇,最后再调用FileHandler
的emit
方法。shouldRollover
跟doRollover
方法都需要子类自己实现。
同时,BaseRotatingHandler
也提供了rotate
方法调用rotator
或者采用默认重命名的方式执行翻篇操作。
接下来我们再看TimedRotatingFileHandler
的具体实现:
1 | class TimedRotatingFileHandler(BaseRotatingHandler): |
在初始化的过程中,首先会解析时间设定计算时间间隔interval
,然后通过computeRollover
初始化rolloverAt
,也就是翻篇的时间点。在emit
的时候,也会直接通过当前时间判断是否超过翻篇时间,超过的话,就会执行doRollover
。
doRollover
总共做了这几件事情:
- 关闭当前文件流
- 根据当前时间生成需要归档的日志文件名
- 执行翻篇,将当前日志文件重命名,归档标注为特定时间
- 删除冗余的旧文件
- 如果不是lazy-load,直接调用
_open
生成新的文件 - 计算新的翻篇时间点
可以看到,baseFilename
其实是个中间态的文件名,实质翻篇操作会是重命名文件,修改为带特定时间信息的文件名。
总结
在logging.handlers
里,除了上述三种handler
外,还有许许多多的handler
定义。有兴趣的同学可以自行挖掘!