Logging 不为人知的二三事

简介: 在之前的两篇文章中,我们介绍了配置 Logging 的流程。(是谁偷偷动了我的 logger、三句话,让 logger 言听计从)然而除此之外,Logging 还有一些高级用法,如果不了解的话,可能会产生意想不到的 Bug。今天的文章,我们就来详细解析一下这些高级用法。

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


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 的流程图就发生了变化。

640.jpg

既然 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!

NFO 等级的日志大于 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 类,感兴趣的同学可以前往官方文档一探究竟。


文章来源:【OpenMMLab

2022-04-21 18:1



相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
目录
相关文章
|
机器学习/深度学习 编解码 自然语言处理
Vision Transformer 必读系列之图像分类综述(二): Attention-based(上)
Transformer 结构是 Google 在 2017 年为解决机器翻译任务(例如英文翻译为中文)而提出,从题目中可以看出主要是靠 Attention 注意力机制,其最大特点是抛弃了传统的 CNN 和 RNN,整个网络结构完全是由 Attention 机制组成。为此需要先解释何为注意力机制,然后再分析模型结构。
1324 0
Vision Transformer 必读系列之图像分类综述(二): Attention-based(上)
|
6月前
|
存储 Web App开发 缓存
清理C盘空间的6种方法,附详细操作步骤
释放C盘空间并不难。只要掌握合适的方法,哪怕你是电脑小白,也能轻松清理出几十GB空间。下面就为大家介绍6种实用、安全、细致的清理方法,并附上操作步骤。
|
8月前
|
传感器 人工智能 算法
傅利叶开源人形机器人,提供完整的开源套件!Fourier N1:具备23个自由度和3.5米/秒运动能力
傅利叶推出的开源人形机器人N1搭载自研动力系统与多模态交互模块,具备23个自由度和3.5米/秒运动能力,提供完整开源套件助力开发者验证算法。
695 3
傅利叶开源人形机器人,提供完整的开源套件!Fourier N1:具备23个自由度和3.5米/秒运动能力
|
缓存 自然语言处理 并行计算
Python 提速大杀器之 numba 篇
你是不是曾经有这样的苦恼,python 真的太好用了,但是它真的好慢啊(哭死) ; C++ 很快,但是真的好难写啊,此生能不碰它就不碰它。老天啊,有没有什么两全其美的办法呢?俗话说的好:办法总是比困难多,大家都有这个问题,自然也就有大佬来试着解决这个问题,这就请出我们今天的主角: numba
1269 0
Python 提速大杀器之 numba 篇
|
算法
雪花算法反思:订单ID生成的痛点与解决方案
雪花算法(Snowflake Algorithm)因其生成唯一ID的能力而被广泛应用于分布式系统中。然而,随着业务的发展和系统规模的扩大,一些隐藏的问题逐渐浮现。本文将探讨使用雪花算法生成订单ID后可能遇到的挑战,并提供相应的解决方案。
619 2
|
前端开发
css特性
css特性 1.继承性:子级继承父级文字控制属性 ps:子级拥有自己的样式则不会继承父级。 2.层叠性:相同的属性后面覆盖前面,不同的属性叠加 3.优先级:选择器优先级高的样式生效 公式:通配符<标签<类<id<行内样式<!important(选中范围越大,优先级越低) 叠加计算规则:存在复合选择器时,从左向右依次比较个数,同一级个数多的优先级高,如果个数相同,则向后比较;!important权重最高;继承权重最低。
|
存储 人工智能 缓存
[AI Kimi] Context Caching 正式公测,推动长文本模型降本 90%
Kimi 的上下文缓存(Context Caching)技术正式公测。该技术通过预先存储数据,显著降低了计算成本和延迟,适用于长文本模型,帮助节省高达 90% 的费用,并将首 Token 延迟降低 83%。
|
Python
【报错】Could not install packages due to an OSError: [Errno 2] No such file or directory: ‘/METADATA‘
【报错】Could not install packages due to an OSError: [Errno 2] No such file or directory: ‘/METADATA‘
4014 1
|
机器学习/深度学习 算法 大数据
Vision Transformer 必读系列之图像分类综述(三): MLP、ConvMixer 和架构分析(下)
在 Vision Transformer 大行其道碾压万物的同时,也有人在尝试非注意力的 Transformer 架构(如果没有注意力模块,那还能称为 Transformer 吗)。这是一个好的现象,总有人要去开拓新方向。相比 Attention-based 结构,MLP-based 顾名思义就是不需要注意力了,将 Transformer 内部的注意力计算模块简单替换为 MLP 全连接结构,也可以达到同样性能。典型代表是 MLP-Mixer 和后续的 ResMLP。
1463 0
Vision Transformer 必读系列之图像分类综述(三): MLP、ConvMixer 和架构分析(下)
|
数据可视化 算法 C++
C++ cmake工程引入qt6和Quick 教程
C++ cmake工程引入qt6和Quick 教程
1063 0