33

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

日志作者 Vinay Sajip提出的方式,至少就我而言 ;-)

您可以跳至“我希望它如何工作

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

我这样安排我的日志记录:

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

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

现在要拥有更高级别的控制,我想使用 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.

仍然不适合我。

我的猜测是,我对 LogRecords 传播背后的细节缺乏了解是问题的根源。在跳转到代码之前,我想在这里展示一个流程图(来自食谱教程,起初我不知何故未能立即发现): 记录流程图

示例代码

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

酒吧.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 构建(根记录器,它是在获取附加到它的流处理程序之后创建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>

实际用例是当 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

我希望它如何工作

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

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 等待根记录器做出决定的方式。我只是希望这确实是它应该首先工作的方式。

4

1 回答 1

46

解决方案

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

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

解释

在您发布的流程图中,请注意有两个菱形:

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

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

相反,由 定义的 StreamHandlerbasicConfig能够过滤任何传递给它的 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()

产量

06-24 09:17 foo                  INFO     hello from foo

如果您想允许从名称以fooor开头的记录器进行记录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'

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

class Blacklist(Whitelist):
    def filter(self, record):
        return not Whitelist.filter(self, record)
于 2013-06-24T13:16:37.117 回答