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

Yau*_*ich 27 python logging filter

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

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

你可以跳到" 我希望它如何工作 "

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

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

  • 每个python模块都有自己的记录器
  • 每个记录器的名称与定义它的模块相同,例如 logger = logging.getLogger(__name__)
  • 像这样,每个模块中的代码可以使用自己的(本地定义的)记录器将日志消息(logging.LogRecord)发送给处理程序(logging.Handler)
  • 使用logging.config实现日志记录配置的完全灵活性(注意:在下面的代码中我只是从basicConfig开始)

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

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

这一切都很好,但过滤如此处所述

Filter instances are used to perform arbitrary filtering of LogRecords.

Loggers and Handlers can optionally use Filter instances to filter
records as desired. The base filter class only allows events which are
below a certain point in the logger hierarchy. For example, a filter
initialized with "A.B" will allow events logged by loggers "A.B",
"A.B.C", "A.B.C.D", "A.B.D" etc. but not "A.BB", "B.A.B" etc. If
initialized with the empty string, all events are passed.
Run Code Online (Sandbox Code Playgroud)

仍然不适合我.

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

示例代码

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

bar.py:

import logging


logger = logging.getLogger(__name__)


def bar():
    logger.info('hello from ' + __name__)
Run Code Online (Sandbox Code Playgroud)

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()
Run Code Online (Sandbox Code Playgroud)

记录首先是使用logging.basicConfig(root logger,它是import logging通过__main__附加流处理程序创建的,因此我们有一个控制台),启用(相应的Logger.disabled = False)和两个模块记录器barfoo构建的.传播到根记录器(因此我们总共有三个记录器).

print logger
print bar_logger
print logging.root
# Prints 
#<logging.Logger object at 0x7f0cfd520790>
#<logging.Logger object at 0x7f0cfd55d710>
#<logging.RootLogger object at 0x7f0cfd520550>
Run Code Online (Sandbox Code Playgroud)

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

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

# Don't like it
bar_logger.addFilter(logging.Filter('foo'))
# Do some work.
foo()
bar()
Run Code Online (Sandbox Code Playgroud)

仅打印

06-24 14:08 foo                  INFO     hello from foo
Run Code Online (Sandbox Code Playgroud)

我希望它如何工作

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

logging.root.addFilter(logging.Filter('foo'))
Run Code Online (Sandbox Code Playgroud)

版画

06-24 14:17 foo                  INFO     hello from foo
06-24 14:17 bar                  INFO     hello from bar
Run Code Online (Sandbox Code Playgroud)

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

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

unu*_*tbu 36

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

handler.addFilter(logging.Filter('foo'))
Run Code Online (Sandbox Code Playgroud)

说明

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

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

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

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

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

# 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()
Run Code Online (Sandbox Code Playgroud)

产量

06-24 09:17 foo                  INFO     hello from foo
Run Code Online (Sandbox Code Playgroud)

如果您希望允许从名称以foo或开头的记录器进行记录bar,而不是从任何其他记录器记录,则可以创建如下白名单过滤器:

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'
Run Code Online (Sandbox Code Playgroud)

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

class Blacklist(Whitelist):
    def filter(self, record):
        return not Whitelist.filter(self, record)
Run Code Online (Sandbox Code Playgroud)

  • 我真的很喜欢这种方法,但是我想知道*(1.)*它可以通过`logging.conf`文件(单独)完成; 和*(2.)*不应该将此白名单功能内置到日志记录模块中吗? (2认同)