logging 详解第三期:Logging 不为人知的二三事

我们在之前的两篇文章中介绍了配置 Logging 的流程,然而除此之外,Logging 还有一些高级用法,如果不了解的话,可能会产生意想不到的 Bug。 今天的文章,我们就来详细解析一下这些高级用法。

OpenMMLab:logging 详解第一期:是谁偷偷动了我的 logger​zhuanlan.zhihu.com/p/481383590?utm_source=zhihu&utm_medium=social&utm_oi=889589031917219840正在上传…重新上传取消

OpenMMLab:三句话,让 logger 言听计从22 赞同 · 3 评论文章正在上传…重新上传取消


loglevel 的继承

我们在第一篇文章中介绍了 Handler 的继承规则。然而除 Handler 外,loglevel 也会受到继承关系影响,导致其不符合第二篇文章中提到的:“只要 message 的 loglevel 大于等于 logger 和 handler 的 loglevel,日志就能被正常输出” 的原则,我们不妨来看以下例子:

import logging 
 
logging.root.setLevel(logging.WARNING) 
logger = logging.getLogger('name') 
logger.addHandler(logging.StreamHandler()) 
logger.setLevel(logging.NOTSET) 
 
logger.info('Good job!') 

此处的 logger 的日志等级为最低的 NOTSET,按理说是能够输出日志的。然而结果却大相径庭,并没有日志输出。但是如果我们把日志等级调整到 DEBUG。

# 续之前的代码 
logger.setLevel(logging.DEBUG) 
logger.info('Good job!') 
# Good job! 

此时日志竟然正常输出了。事实上,logging.Logger 是根据 logging.Logger.getEffectiveLevel 返回的结果,来决定是否输出一条日志。

    def getEffectiveLevel(self): 
 """ 
     Get the effective level for this logger. 
 
     Loop through this logger and its parents in the logger hierarchy, 
     looking for a non-zero logging level. Return the first one found. 
     """ 
  logger = self 
        while logger: 
            if logger.level: 
                return logger.level 
            logger = logger.parent 
        return NOTSET 

那么上一篇文章中 loglevel 的流程图就发生了变化。

既然 logger 有 effective loglevel 的概念,那 handler 有没有呢?答案是没有。由于 handler 没有继承逻辑,因此只要被 logger 过滤后的 message 的 loglevel 大于 handler 的 loglevel,日志就能被输出:

import logging 
 
logging.root.setLevel(logging.WARNING) 
logger = logging.getLogger('name') 
# Handler 的日志等级为 NOTSET 
handler = logging.StreamHandler() 
handler.setLevel(logging.NOTSET) 
logger.addHandler(handler) 
 
logger.setLevel(logging.DEBUG) 
logger.info('Good job!') 
# Good job! 

INFO 等级的日志大于 DEBUG 和 NOTSET,日志等级能够被正常输出。

Handler 和 Logger 的基类 - Filterer

走进 Logging 的源码,我们不难发现 Logger 和 Handler 均继承自 Filterer,而其中 filter 方法为 Handler 和 Logger 提供了过滤日志的功能:

    # method of Filterer 
    def filter(self, record): 
 """ 
     Determine if a record is loggable by consulting all the filters. 
 
     The default is to allow the record to be logged; any filter can veto 
     this and the record is then dropped. Returns a zero value if a record 
     is to be dropped, else non-zero. 
 
     .. versionchanged:: 3.2 
 
        Allow filters to be just callables. 
     """ 
  rv = True 
        for f in self.filters: 
            if hasattr(f, 'filter'): 
 result = f.filter(record) # 如果 filter 是类,需要实现 filter 方法 
            else: 
 result = f(record) # filter 也可以是函数 
            if not result: 
                rv = False 
                break 
        return rv 

我们可以通过给 logger 或 handler 配置 filter 来过滤日志。filter 可以是函数,也可以是实现了 filter 方法的类,filter 通过判断 record 的内容返回 True 或者 False,来决定日志是否应该被输出。
尽管 Filter 的使用频次相对较少,但是我们可以借助它很轻松地实现一些功能:

  • 屏蔽某个 module,或者某个 function 的所有日志
  • 只输出某个日志等级的日志
  • 屏蔽某些包含字段的日志
import logging 
from logging import Filter 
 
# 屏蔽某个函数的日志 
class ClassModuleFilter(Filter): 
    def __init__(self, func_name): 
        super().__init__() 
        self.func_name = func_name 
 
    def filter(self, record): 
        return not record.funcName == self.func_name 
 
# 只输出 debug 等级的日志 
def function_loglevel_filter(record): 
    return record.levelname == 'DEBUG' 
 
# 不输出包含 Bad 字段的日志 
def function_message_filter(record): 
    return 'Bad' not in record.msg 
 
# 测试函数,要求 function_a 内不会输出日志 
def function_a(): 
    logger = logging.getLogger('name') 
    logger.info("ModuleA message will not be logged") 
 
 
logging.root.setLevel(logging.WARNING) 
logger = logging.getLogger('name') 
handler = logging.StreamHandler() 
handler.setLevel(logging.NOTSET) 
logger.addHandler(handler) 
logger.setLevel(logging.DEBUG) 
 
# 配置 filter,不输出 function_a 内产生的日志 
filter1 = ClassModuleFilter('function_a') 
logger.addFilter(filter1) 
function_a() 
# 没有日志输出 
 
# 配置 filter 只输出 debug 等级的日志 
logger.addFilter(function_loglevel_filter) 
logger.info("Only debug message will be logger") 
# 无日志输出 
logger.debug("Only debug message will be logger") 
# Only debug message will be logger 
logger.addFilter(function_message_filter) 
logger.debug('Bad message will not be output') 
# 带 Bad 字段,日志被过滤 
logger.debug('Good message will be output') 
# Good message will be output 

上例中,我们通过给 logger 配置不同类型的 filter 实现了日志的过滤。handler 的过滤功能和 logger 类似,只不过 handler 的 filter 只作用自身,而 logger 的 filter 作用于所有 handler。

logging.root.setLevel(logging.WARNING) 
logger = logging.getLogger('name') 
 
# 配置 filter 的 handler 
handler = logging.StreamHandler() 
handler.addFilter(function_message_filter) 
logger.addHandler(handler) 
# 未配置 filter 的 handler 
logger.addHandler(logging.StreamHandler()) 
 
logger.setLevel(logging.DEBUG) 
logger.info('Bad guy') 
# Bad guy 2 个 handler 输出一条信息,未增加过滤器的 Handler 输出了日志 
logger.addFilter(function_message_filter) 
logger.info('Bad guy') 
# 无日志输出,logger 的 filter 过滤了所有日志 

此外, handler 的过滤功能是可以被继承的,而 logger 的则不行:

import logging 
 
 
def function_message_filter(record): 
    return 'Bad' not in record.msg 
 
 
logging.root.setLevel(logging.WARNING) 
logger = logging.getLogger('name') 
 
# 配置 filter 的 handler 
handler = logging.StreamHandler() 
handler.addFilter(function_message_filter) 
logger.addHandler(handler) 
# 未配置 filter 的 handler 
logger.addHandler(logging.StreamHandler()) 
logger.setLevel(logging.DEBUG) 
 
child = logger.getChild('child') 
child.info('Bad guy') 
# Bad guy  Handler 的过滤功能被继承,只会输出一条日志。 
logger.addFilter(function_message_filter) 
child.info('Bad guy') 
# Bad guy logger 的过滤功能不会被继承,仍然只会输出一条日志,不会过滤全部 

至此,相信大家对 logging 模块有了更深刻的理解。在缕清 Logger、Handler、Filter 和 Formatter 之间的关系后,能够写出更加好看、符合需求的 logger。至于 Logging 模块的 LogRecorder、Manager 类,感兴趣的同学可以前往官方文档一探究竟。


MMCV 的 logging 模块为大家提供了简单接口,能够获取在本地、终端输出统一日志的 logger,但也有很多可以继续完善的地方:例如不同等级的日志可以有不同颜色、不同格式,导出分布式情况下的多卡日志等,有兴趣的小伙伴可以踊跃 PR,期待你的加入~

GitHub - open-mmlab/mmcv: OpenMMLab Computer Vision Foundation​github.com/open-mmlab/mmcv正在上传…重新上传取消

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值