我们知道一个良好的系统,日志模块是必不可少的,Python的基础日志模块便是logging模块。
这一篇文章,我们从源码去看下logging模块的组成。
1. 开始
最开始,我们用最短的代码体验一下logging的基本功能
1 | In [30]: import logging |
第一步:通过logging.getLoger函数,获得一个loger对象,但这个对象暂时是无法使用的。
第二步:logging.basicConfig函数,进行一系列默认的配置,包括format、handler等。
第三步:loger调用setLevel函数定义日志级别为DEBUG
最后:logger调用debug函数,输出一条debug级别的message,显示在了标准输出上
2. logging中的日志级别
logging在生成日志的时候,有一个日志级别的机制,默认有以下几个日志级别:
1 | CRITICAL = 50 |
每一个logger对象,都有一个日志级别,它只会输出大于等于它level的日志。如果一个logger的level是INFO,那么调用logger.debug()是无法输出日志的,而logger.warning()能够输出。
一般来说,以上的6个日志级别完全满足我们日常使用了。
3. logging中的基础类
logging是python中的一个基础模块,它在python中的源码位置如下:
1 | python3.7/logging/__init__.py # 主干代码 |
组成logging的主干的几个基础类都在__init__.py中
3.1 第一个基础类 LogRecord
一个LogRecord对象,对应了日志中的一行数据。通常包含:时间、日志级别、message信息、当前执行的模块、行号、函数名……这些信息都包含在一个LogRecord对象里。
LogRecord对象可以想象成一个大字典
1 | class LogRecord(object): |
3.2 第二个基础类 Formatter
Formatter对象是用来定义日志格式的,LogRecord保存了很多信息,但是打日志的时候我们只需要其中几个,Formatter就提供了这样的功能,它依赖于python的字符串的格式化:
1 | # 通过字典的方式,输出格式化字符串 |
如果说LogRecord是后面的那个字典,那么Formatter就是前面的那个格式字符串……的抽象
重要的代码:
1 | class PercentStyle(object): |
3.3 第三个基础类 Filter和Filterer
Filter类,功能很简单。Flter.filter()函数传入一个LogRecord对象,通过筛选返回True,否则返回False。从代码中可以看到,其实是对LogRecord.name的筛选。
Filterer类中有一个Filter对象的列表,它是一组Filter的抽象。
重要的代码如下:
1 | class Filter(object): |
针对return (record.name[self.nlen] == ".")这句话可以举例:
1 | "A.B"能够通过的是"A.B." |
1 | class Filterer(object): |
4. logging中的高级类
有了以上三个基础的类,就可以拼凑一些更重要的高级类了,高级类可以实现logging的重要功能。
4.1 Handler——抽象了log的输出过程
Handler类继承自Filterer。Handler类是log输出这个过程的抽象- 同时
Handler类具有一个成员变量self.level,在第二节讨论的日志级别的机制,就是在Handler中实现的。 Handler有一个emit(record)函数,这个函数负责输出log,必须在Handler的子类中实现。
重要代码如下:
1 |
|
接下来看两个简单的handler的子类,其实在logging源码中,有一个handler.py专门定义了很多更复杂的handler,有的可以将log缓存在内存中,有的可以将log做rotation等.
4.1.1 StreamHandler
最简单的handler实现,将log写入一个流中,默认的stream是sys.stderr
重要的代码如下:
1 | class StreamHandler(Handler): |
4.1.2 FileHandler
将log输出到文件的handler,继承自StreamHandler
重要代码如下:
1 | class FileHandler(StreamHandler): |
4.2 Logger —— 一个独立的log管道
什么是logger?
logger类继承自Filterer,logger对象有logger.level日志级别logger对象控制多个handler:logger.handlers=[]logger对象之间存在父子关系
简单的来说,logger这个类,集中了我们以上所有的LogRecord类、Filter类、Formatter类、handler类。首先,logger根据输入生成一个LogRecord对象,经过Filter和Formatter之后,再通过self.handlers列表中的所有handler,把log发送出去。一个logger中可能有多个handler,可以实现把一份log放到多个任意的位置。
重要代码:
1 | class Logger(Filterer): |
4.3 Manager —— 管理logger的类
Manager这个类,对用户其实是不可见的,如果生成了Logger,Manager就会自动存在,Manager对象负责管理所有的Logger。
logger和manager的关系,总结了一下几条:
Logger是输出log的对象,Manager类提供了管理多个Logger的功能。- 一个程序中只能有一个
manager对象,生成manager时,必定也会生成RootLogger,manager对象中的self.root指向了RootLogger manager对象中的self.loggerDict,这个字典保存了当前所有的logger对象(不包含rootlogger)- 如果使用
logging.getLogger的name为空,那么默认指向了name为root的RootLogger - 如果使用
logging.getLogger的name不为空,生成的logger会自动挂载到RootLogger下,除非指定其他的父logger - 其他的
logger通过name建立父子关系
父子关系实例
1 | loger1=logging.getLogger('A') |
这些关系都在manager中进行管理
重要的代码:
1 | class Manager(object): |
4.4 LoggerAdapter —— 对标准logger的一个扩展
LogRecord这个大字典中提供的成员变量已经很多,但是,如果在输出log时候仍然希望能够夹带一些自己想要看到的更多信息,例如产生这个log的时候,调用某些函数去获得其他信息,那么就可以把这些添加到Logger中,LoggerAdapter这个类就起到这个作用。
LoggerAdapter这个类很有意思,如果不做什么改动,那么LoggerAdapter类和Logger并没有什么区别。LoggerAdapter只是对Logger类进行了一下包装。
LoggerAdapter的用法其实是在它的成员函数process()的注释中已经说明了:
1 | def process(self, msg, kwargs): |
也就是说重写process函数,以下是一个例子:
1 | import logging |
5. logging中的config函数
5.1 def basicConfig(**kwargs)
basicConfig函数将对各种参数进行配置,如果不传入参数,则进行默认配置:
format配置handler配置level配置
参考文章: