我们知道一个良好的系统,日志模块是必不可少的,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
函数将对各种参数进行配置,如果不传入参数,则进行默认配置:
forma
t配置handler
配置level
配置
参考文章: