首页 文章

使用python日志记录过滤不同 Logger 的正确方法是什么?

提问于
浏览
24

我的目的是使用分层过滤进行多模块日志记录

记录作者Vinay Sajip提出的方式,至少据我猜测;-)

你可以跳到“ How I want it to work

不幸的是,我很快就了解到,使用日志工具比使用该语言的大多数其他经验要复杂得多,而且我已经犯了许多常见的(设计)错误,例如:尝试为多个模块甚至是(using Python logger class to generate multiple logs for different log levels)等方案实现集中式单一Logger类日志记录 . 但显然有更好的设计空间,花费时间寻找和学习它可能会更糟糕 . 所以,现在我希望我走在正确的轨道上 . 否则Vinaj将不得不澄清其余的;-)

我安排我的日志记录如下:

  • 每个python模块都有own logger

  • 每个 Logger 的名称与定义它的模块相同,例如 logger = logging.getLogger(__name__)

  • 像这样,每个模块中的代码可以使用自己的(本地定义的) Logger 将日志消息(logging.LogRecord)发送给处理程序(logging.Handler)

  • 使用logging.config实现日志记录配置的完全灵活性(注意:在下面的代码中我只是从basicConfig开始)

这种方法是一种推荐的方法,我同意其可能的优势 . 例如,我可以使用完全限定的模块名称(代码中已存在的命名层次结构)打开/关闭外部库的DEBUG .

现在为了获得更高级别的控制,我想使用logging.Filter类,以便只能过滤(允许) Logger 层次结构中的选定子树 .

这一切都很好,但这里描述的过滤

过滤器实例用于执行LogRecords的任意过滤 .

Logger 和处理程序可以选择使用筛选器实例进行筛选
根据需要记录 . 基本过滤器类仅允许事件
低于 Logger 层次结构中的某个点 . 例如,过滤器
用“A.B”初始化将允许 Logger “A.B”记录的事件,
“A.B.C”,“A.B.C.D”,“A.B.D”等,但不是“A.BB”,“B.A.B”等 . 如果
使用空字符串初始化,传递所有事件 .

仍然不适合我 .

我的猜测是我对LogRecords传播背后的细节缺乏了解是问题的根源 . 在跳转到代码之前,我想在这里显示一个流程图(来自cookbook tutorial,起初我以某种方式无法立即发现):
logging flow-chart

示例代码

我从两个模块示例开始,每个模块都使用自己的命名logger:

bar.py:

import logging


logger = logging.getLogger(__name__)


def bar():
    logger.info('hello from ' + __name__)

foo.py:

import logging
from bar import bar, logger as bar_logger


logger = logging.getLogger('foo')


def foo():
    logger.info('hello from foo')


if __name__ == '__main__':
    # Trivial logging setup.
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s %(name)-20s %(levelname)-8s %(message)s',
        datefmt='%m-%d %H:%M'
    )
    # Do some work.
    foo()
    bar()

记录首先使用logging.basicConfig构建(根 Logger ,它是在 import logging 之后创建的 __main__ 获取了一个附加到它的流处理程序,因此我们有一个控制台),启用(相应的Logger.disabled = False)和两个模块 Logger 栏和foo传播到根 Logger (所以我们总共有三个 Logger ) .

print logger
print bar_logger
print logging.root
# Prints 
#<logging.Logger object at 0x7f0cfd520790>
#<logging.Logger object at 0x7f0cfd55d710>
#<logging.RootLogger object at 0x7f0cfd520550>

实际的用例是当bar是我想要静音的外部库(过滤掉) .

它是如何工作的,但“我”不喜欢它

# Don't like it
bar_logger.addFilter(logging.Filter('foo'))
# Do some work.
foo()
bar()

仅打印

06-24 14:08 foo                  INFO     hello from foo

我希望它如何运作

我想集中过滤它,即在我的根 Logger 中,无需导入所有外部模块的所有 Logger .

logging.root.addFilter(logging.Filter('foo'))

版画

06-24 14:17 foo                  INFO     hello from foo
06-24 14:17 bar                  INFO     hello from bar

必须有一些我错过的明显/愚蠢的错误:我不希望来自 bar logger的任何消息 . 嘿,但是找到它的更好的方法是什么比总结所有的SO,伙计们? ;-)

我会尝试找出bar_logger在发出任何内容之前等待来自根 Logger 的决定的方法 . 我只是希望这确实是首先应该如何运作的 .

1 回答

  • 32

    Solution

    将过滤器添加到处理程序而不是 Logger :

    handler.addFilter(logging.Filter('foo'))
    

    Explanation

    在您发布的流程图中,请注意有两颗钻石:

    • 附加到 logger 的过滤器是否拒绝记录?

    • 附加到 hander 的过滤器是否拒绝记录?

    因此,在拒绝LogRecord时会出现两次波动 . 如果您将过滤器附加到根 Logger ,但通过foo或bar Logger 启动LogRecord,则LogRecord不会被过滤,因为LogRecord会自由地通过foo或bar Logger ,并且根 Logger 过滤器永远不会进入玩 . (再次查看流程图 . )

    相反, basicConfig 定义的StreamHandler能够过滤任何LogRecord传递给它 .

    所以:将过滤器添加到处理程序而不是 Logger :

    # foo.py
    import logging
    import bar
    
    logger = logging.getLogger('foo')
    
    def foo():
        logger.info('hello from foo')
    
    if __name__ == '__main__':
        # Trivial logging setup.
        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s %(name)-20s %(levelname)-8s %(message)s',
            datefmt='%m-%d %H:%M')
        for handler in logging.root.handlers:
            handler.addFilter(logging.Filter('foo'))
    
        foo()
        bar.bar()
    

    产量

    06-24 09:17 foo                  INFO     hello from foo
    

    如果要允许从名称以 foobar 开头的 Logger 进行记录,而不是从任何其他 Logger 进行记录,则可以创建如下白名单过滤器:

    import logging
    foo_logger = logging.getLogger('foo')
    bar_logger = logging.getLogger('bar')
    baz_logger = logging.getLogger('baz')
    
    class Whitelist(logging.Filter):
        def __init__(self, *whitelist):
            self.whitelist = [logging.Filter(name) for name in whitelist]
    
        def filter(self, record):
            return any(f.filter(record) for f in self.whitelist)
    
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s %(name)-20s %(levelname)-8s %(message)s',
        datefmt='%m-%d %H:%M')
    for handler in logging.root.handlers:
        handler.addFilter(Whitelist('foo', 'bar'))
    
    foo_logger.info('hello from foo')
    # 06-24 09:41 foo                  INFO     hello from foo
    bar_logger.info('hello from bar')
    # 06-24 09:41 bar                  INFO     hello from bar
    baz_logger.info('hello from baz')
    # No output since Whitelist filters if record.name not begin with 'foo' or 'bar'
    

    同样,您可以使用以下方法将 Logger 名称列入黑名单:

    class Blacklist(Whitelist):
        def filter(self, record):
            return not Whitelist.filter(self, record)
    

相关问题