Python logging模块源码解析:从基础类到高级用法
本文深入解析Python内置logging模块的源码结构,详细阐述LogRecord、Formatter、Filter、Handler及Logger等核心类的实现原理。通过剖析源码中的关键代码片段,解释日志级别过滤、格式化处理及多Handler分发机制,并介绍LoggerAdapter的扩展用法,帮助开发者掌握日志系统的底层逻辑与高级定制技巧。
简单使用
首先,通过一段极简的代码来演示logging模块的基本工作流程。
import logging
logger = logging.getLogger()
logging.basicConfig()
logger.setLevel('DEBUG')
logger.debug('logsomething')
#输出
out>>DEBG:root:logsomething
操作分为三个步骤:首先调用logging.getLogger函数获取一个Logger实例,此时该实例尚未配置;接着使用logging.basicConfig进行默认配置,设定格式和处理器等参数;最后,通过logger.setLevel将日志级别设置为DEBUG,并调用debug方法输出一条消息,该消息将直接显示在标准输出中。
logging模块在生成日志时引入了日志级别机制,默认包含以下预设级别:
CRITICAL = 50 ERROR = 40 WARNING = 30 INFO 20 DEBUG = 10 NOTEST = 0
每个Logger实例都关联一个日志级别,仅输出高于或等于该级别的日志。例如,若Logger的级别设为INFO,则调用debug方法不会输出任何内容,而调用warning方法则可以正常输出。
通常情况下,上述六个日志级别已足以覆盖日常开发需求。
logging模块的基础类结构
作为Python的标准库,logging模块的源码位于以下路径:
#主干代码 /usr/lib/python2.7/logging/__init__.py #扩展的handler和config /usr/lib/pyhon2.7/logging/config.py /usr/lib/python2.7/loging/handlers.py
构成logging核心功能的几个基础类均定义在__init__.py文件中:
第一个基础类:LogRecord
LogRecord对象代表日志中的一条具体记录,通常包含时间戳、日志级别、消息内容、当前模块名、行号及函数名等信息。可以将LogRecord对象视为一个包含上述字段的大字典:
class LogRecord(object): #代表一条日志的类 def getMessage(self): #获取self.msg def markLogRecord(dict): #这个方法很重要,生成一个空的LogRecord,然后通过一个字典,直接更新LogReocrd中的成员变量 rv = LogRecord(None, None, "", 0, "", (), None, None) rv.__dict__.update(dict) return rv
第二个基础类:Formatter
Formatter对象用于定义日志的输出格式。虽然LogRecord存储了大量信息,但实际打印时通常只需部分字段,Formatter即负责根据指定格式提取并格式化这些字段,其实现依赖于Python的字符串格式化功能:
#通过字典的方式,输出格式化字符串
print('%(name)s:%(num)d'%{'name':'my_name', 'num' : 100})
out >>>my_name:100
关键代码如下:
class Formatter(object): def __init__(self, fmt=None, datefmt = None): if fmt: self._fmt = fmt else: #默认的format self._fmt = "%(message)s" def format(self, record) #使用self._fmt进行格式化 s = self._fmt %record.__dict__ return s
第三个基础类:Filter和Filterer
Filter类的功能较为单一。其filter()方法接收一个LogRecord对象,若记录通过筛选则返回1,否则返回0。从代码逻辑可见,筛选主要基于LogRecord的name属性进行判断。
Filterer类维护了一个Filter对象列表,用于管理一组Filter规则。
关键代码如下:
class Filter(object): def __init__(self, name=''): self.name = name self.nlen = len(name) def filter(self, record): #返回1表示record通过,0表示record不通过 if self.nlen == 0: return 1 elif self.name == record.name: return 1 #record.name不是以filter开头 elif record.name.find(self.name, 0, self.nlen) != 0: return 0 #最后一位是否为 return (record.name[self.nlen] == '.') class Filterer(object): #这个类其实是定义了一个self.filters = []的列表管理多个filter def addFilter(self, filter): def removefilter(self, filter): def filter(self, record): #使用列表中所有的filter进行筛选,任何一个失败都会返回0 #例如: #filter.name = 'A', filter2.name='A.B', filter2.name = 'A, B, C' #此时record.name = 'A,B,C,D'这样的record才能通过所有filter的筛选
logging模块的高级类
基于上述基础类,可以构建出实现logging核心功能的高级类。
Handler——日志输出过程的抽象。Handler类继承自Filterer,封装了日志输出的具体逻辑。Handler类包含一个成员变量self.level,日志级别的过滤机制正是在Handler层面实现的。此外,Handler类定义了一个emit(record)方法,负责实际输出日志,该方法必须在Handler的子类中具体实现。
关键代码如下:
class Handler(Filterer): def __init__(self, level = NOTEST) #handler必须有level属性 self.level = _checkLevel(level) def format(self, record): #使用self.formatter, formattercord def handler(self, record): #如果通过filter的筛选,则emit这条log rv = self.filter(record) self.emit(record) def emit(self, record): #等待子类去实现
接下来介绍两个简单的Handler子类。在logging源码中,handler.py文件专门定义了许多复杂的Handler实现,例如支持内存缓存的Handler或支持日志轮转(rotation)的Handler。
StreamHandler 这是最简单的Handler实现,将日志写入一个流对象,默认输出目标为sys.stderr。
关键代码如下:
class StreamHandler(Handler): def __init__(self, stream = None): if stream is None: stream = sys.stderr self.stream = stream def emit(self, record): #将record的信息写入流 #处理一些编码的异常 fs = '%s\n' #每条日志都有换行 stream = self.stream stream.write(fs%msg)
FileHandler
用于将日志输出到文件的Handler,它继承自StreamHandler。
关键代码如下:
class FileHandler(StreamHandler): def __init__(self, filename, mode='a') #append方式打开一个文件 StreamHandler.__init__(self, self._open()) def emit(self, record): #和streamhandler保持一致 StreamHandler.emit(self, record)
Logger——独立的日志管道
什么是Logger?
+ Logger类继承自Filterer,
+ Logger对象拥有logger.level日志级别属性,
+ Logger对象管理多个Handler:logger.handlers = [],
+ Logger对象之间存在父子层级关系。
简而言之,Logger类整合了前述的LogRecord、Filter、Formatter和Handler类。首先,Logger根据输入生成LogRecord对象,经过Filter过滤和Formatter格式化后,再通过self.handlers列表中的所有Handler将日志发送出去。
一个Logger可以关联多个Handler,从而实现将同一条日志分发到任意多个目标位置。
class Logger(Filterer):
def __init__(self, name, level=NOTEST)
#handler列表
self.handlers = []
self.level = _checklevel(level)
def addHandler(self, hdlr):
def removeHandler(self, hdlr):
def _log(self, level, msg, args, exc_info=None, extra=None):
#在_log函数中创建了一个LogRecord对象
record = self.makeRecord(self.name, level, fn, lno, msg, args, exc_info, func, extra)
#交给handle函数
self.handle(record)
def handle(self, reord):
#进行filter,然后调用callHandlers
if(not self.disabled) and self.filter(record):
self.callHandlers(record)
def callHandlers(self, record):
#从当前logger到所有的父logger,递归的handl传入的record
c = self
while c:
for hdlr in c.handlers:
hdlr.handle(record) #进入handler的emit函数发送log
....
c = c.parent
LoggerAdapter——对标准Logger的扩展
虽然LogRecord字典已提供丰富的成员变量,但若希望在日志输出时附加自定义信息(例如调用特定函数获取额外上下文),则可以使用LoggerAdapter。该类的作用即在于此。
LoggerAdapter的设计颇具巧思。若不进行任何修改,LoggerAdapter与Logger在行为上并无二致,它本质上是对Logger类的包装。
LoggerAdapter的实际用法已在成员函数process()的注释中说明:
def process(self, msg, kwargs): ''' Normally,you'll only need to overwrite this one method in a LoggerAdapter subclass for your specific needs. '''
即通过重写process函数实现自定义逻辑,以下是一个示例:
import logging
import random
L=logging.getLogger('name')
#定义一个函数,生成0~1000的随机数
def func():
return random.randint(1,1000)
class myLogger(logging.LoggerAdapter):
#继承LoggerAdapter,重写process,生成随机数添加到msg前面
def process(self,msg,kwargs):
return '(%d),%s' % (self.extra['name'](),msg) ,kwargs
#函数对象放入字典中传入
LA=myLogger(L,{'name':func})
#now,do some logging
LA.debug('some_loging_messsage')
out>>DEBUG:name:(167),some_loging_messsage
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













