Python logging日志库源码简单分析

logging 结构

简单分析

logging.debug/info/warning/error 这些顶层函数,其源代码结构为:(以info为例子,其他类似)

# logging/__init__.py  2140
def info(msg, *args, **kwargs):
    if len(root.handlers) == 0:
        basicConfig()
    root.info(msg, *args, **kwargs)
  • 判断 root.handlers 长度为0来进行初始化 basicConfig
  • 然后调用了 root 对应的方法

这个 root 实际上是一个名称为 rootLogger 子类实例,在 logging 模块导入时便创建存在

  • Logger.__init__() 中对其成员变量进行赋值:self.handlers = []
# logging/__init__.py 1778
class RootLogger(Logger):
    def __init__(self, level):
        """
        Initialize the logger with the name "root".
        """
        Logger.__init__(self, "root", level)

    def __reduce__(self):
        return getLogger, ()

# logging/__init__.py 1935
root = RootLogger(WARNING)
Logger.root = root
Logger.manager = Manager(Logger.root)

basicConfig 函数是用于对 logging的配置

  • 在上面没有传入任何参数,那么 root.handlers 会被初始化为仅包含 StreamHandler 的列表

    stream = kwargs.pop("stream", None)
    h = StreamHandler(stream)
    
  • 如果指定文件名,那么会被初始化为仅包含 FileHandler 的列表:

    filename = kwargs.pop("filename", None)
    mode = kwargs.pop("filemode", 'a')
    if filename:
        if 'b' in mode:
            errors = None
        else:
            encoding = io.text_encoding(encoding)
            h = FileHandler(filename, mode,
                            encoding=encoding, errors=errors)
    
    • 如果指定了 handlers 参数,那么会被初始化为该 handlers

当首次调用 logging.xxx 时,basicConfig 就会被调用;

随后调用 root.xxx,实际上是 Loggger 提供的方法:

# logging/__init__.py#class Logger
def info(self, msg, *args, **kwargs):
    # 检查是否输出 INFO 级别的日志
    if self.isEnabledFor(INFO):
        self._log(INFO, msg, args, **kwargs)

进入 _log 方法:

  • 对传入的参数进行检查处理
  • 生成 LogRecord 实例,然后交由 handle 方法处理
# logging/__init__.py#class Logger 1600
def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False,
         stacklevel=1):
	....
    
    record = self.makeRecord(self.name, level, fn, lno, msg, args,
                             exc_info, func, extra, sinfo)
    self.handle(record)

进入 handle 方法:

# logging/__init__.py#class Logger 1626
def handle(self, record):
    # 该 logger 非禁用状态且过滤后任存在
    if (not self.disabled) and self.filter(record):
        self.callHandlers(record)

进入 callHandlers 方法:

  • while 循环中可以看到,不断向上查找 c = c.parent,每次交由父Logger的 handlers 处理
  • c.propagate 表示是否将日志向上传递
  • 如果不能找到 handler 进行处理,则会交由 lastResort 进行处理
# logging/__init__.py#class Logger 1626
def callHandlers(self, record):
    c = self
    found = 0
    
    while c:
        for hdlr in c.handlers:
            found = found + 1
            if record.levelno >= hdlr.level:
                hdlr.handle(record)
            if not c.propagate:
                c = None    #break out
            else:
                c = c.parent
    if (found == 0):
        if lastResort:
            if record.levelno >= lastResort.level:
                lastResort.handle(record)
            elif raiseExceptions and not self.manager.emittedNoHandlerWarning:
                sys.stderr.write("No handlers could be found for logger"
                                 " \"%s\"\n" % self.name)
                self.manager.emittedNoHandlerWarning = True
# logging/__init__.py 1243
# 如果没有任何的 handler 可以处理,那么会交由该 Handler 处理
_defaultLastResort = _StderrHandler(WARNING)
lastResort = _defaultLastResort

从这部分代码可以意识到 logger 是一个层级结构(树形结构)

logging.getLogger

该方法用于获取 name 对应的 logger 实例

传入一个 name 参数,用于设置 logger 的名称

  • 如果 name == Nonename == "root"name 不是 str 类型时,返回 root 实例(上面提到 root 在模块导入时便创建存在了)
  • 否则交由 Logger.manager 创建
# logging/__init__.py 2071
def getLogger(name=None):
    """
    Return a logger with the specified name, creating it if necessary.

    If no name is specified, return the root logger.
    """
    if not name or isinstance(name, str) and name == root.name:
        return root
    return Logger.manager.getLogger(name)

Logger.managerlogging.Manager 的实例:

  • 根据文档描述:通过一个指定的 name (用 . 划分的层级结构)来获得 logger 实例

    • 层级结构:举个例子 a.b.ca.b 的子级,而后者是前者的父级
    • 对于每层的 name 获取对应的实例 logger[name]
      • 如果是没有创建而有 PlaceHolder 占位(这种情况属于子级已经创建,但是父级没有创建)则将其创建
      • 如果没有创建也没有 PlaceHolder 则直接创建
  • PlaceHolder:维护着其所有子级的 name,当创建 logger 时会将子级的 logger.parent 指向该实例.

    # logging/__init__.py 1249
    class PlaceHolder(object):
        def __init__(self, alogger):
            self.loggerMap = { alogger : None }
    
        def append(self, alogger):
            if alogger not in self.loggerMap:
                self.loggerMap[alogger] = None
    
# logging/__init__.py#Manager 1315
def getLogger(self, name):
    rv = None
    if not isinstance(name, str):
        raise TypeError('A logger name must be a string')
    _acquireLock()
    try:
        # self.looggerDict类似缓存,记录着所有已经创建的Logger/PlaceHolder实例
        if name in self.loggerDict:
            rv = self.loggerDict[name]
            # PlaceHodler占位需要创建一个logger实例替换
            if isinstance(rv, PlaceHolder):
                ph = rv
                rv = (self.loggerClass or _loggerClass)(name)
                rv.manager = self
                self.loggerDict[name] = rv
                self._fixupChildren(ph, rv)
                self._fixupParents(rv)
        else:
            # 缓存中不存在对应实例,创建一个
            rv = (self.loggerClass or _loggerClass)(name)
            rv.manager = self
            self.loggerDict[name] = rv
            self._fixupParents(rv)
    finally:
        _releaseLock()
    return rv

_fixupParents 用于创建当前子级的父级logger

  • 逐层向上查找:比如 a.b.c 先查找 a.b,然后是 a
def _fixupParents(self, alogger):
    name = alogger.name
    i = name.rfind(".")
    rv = None
    while (i > 0) and not rv:
        substr = name[:i]
        # 父级 logger 尚未创建且没有对应的PlaceHolder,使用 PlaceHolder 占位
        if substr not in self.loggerDict:
            self.loggerDict[substr] = PlaceHolder(alogger)
        else:
            obj = self.loggerDict[substr]
            # 父级已经创建
        	if isinstance(obj, Logger):
                rv = obj
            else:
                # 父级尚未创建
                assert isinstance(obj, PlaceHolder)
                obj.append(alogger)
        i = name.rfind(".", 0, i - 1)
    # 该子级没有已经创建好的 logger,则设置为 root 
    if not rv:
        rv = self.root
    alogger.parent = rv

总结

  1. 每个 Logger 实例维护着一个 parent 实例,对应着其父级的 logger,类似一个树形结构

    • 如果对应父级尚未创建则会用 PlaceHolder 占位

    • 所有 logger 向上查找最终会到 root

parent
parent
parent
parent
Logger-root
a
a.b
a.c
a.b.e
  1. 每个 name对应的 logger 都是单例,在 Logging.manager.loggerDict 中存放;

  2. 在输出日志时,先将日志信息封装成一个 LogRecord 实例,然后交由所有 Logger 实例中的 handlers 进行处理;

  3. 模块初始化流程:

    • 模块在载入时会实例化一个name为 root 的 Logger 实例,此时 root.handlers 是没有任何 Handler 实例;
    • 在 第一次logging.xxx 前如果没有手动执行 basicConfig 函数,那么会执行一次,执行后 root.handlers 有一个 StreamHandler 实例,将日志输出至 sys.stderr
    • 如果已经执行过 basicConfig 函数,那么会根据传参进行设置
      • 指定了 filename,则 root.handlers = [FileHandler]
      • 指定了 handlers,则 root.handlers = handlers
      • 上面都没有指定,则 root.handlers = [StreamHandler]
  4. 日志输出流程:

    先封装为 LogRecord 实例,然后向上查找父级并交由其 handlers 处理

    (从当前 logger 到 root 的路径上所有 handlers 都会处理一遍)

    如果找不到 handlers 处理,那么最后会交给 lastResort 处理

    注意保证输出到控制台的Handler只有一个,否则就会产生多次输出,就比如下面的例子:

    logger = logging.getLogger('example')
    logger.addHandler(logging.StreamHandler())
    logging.basicConfig()
    # 此时 logging.root有一个StreamHandler,而 logger也有一个StreamHandler,就会导致输出两次
    logger.warning("warn“)
    

    LogRecord经过 Handler处理时:

    1. 先判断是否大于等于其日志等级
    2. 经过 Handler#filter 过滤
    3. Handler#format 将 LogRecord 实例按照指定的格式转化为字符串
    4. Hander#emit 将格式化后的字符串输出到指定的位置(StreamHandlersys.stderrFileHandler 则为指定的 filename
参数形式
参数形式
LogRecord
LogRecord
str
logging.xxx
logging._log
Logger#makeRecoder
Logger#handle -> callHandlers
Handler#handle -> filter -> format ->emit
输出

  • 18
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值