为什么附加到根记录器的过滤器不会传播到后代记录器?

时间:2011-07-27 20:26:43

标签: python logging

来自python logging documentation的第15.7.4段:

  

请注意,每当发生事件时,都会查询附加到处理程序的过滤器   由处理程序发出,而附加到记录器的过滤器是   每当事件被记录到处理程序时(使用debug(),请参考)   info()等)这意味着已经生成的事件   后代记录器不会被记录器的过滤器设置过滤,   除非过滤器也已应用于那些后代记录器。

我不明白这个设计决定。将根记录器的过滤器应用于后代记录器也没有意义吗?

4 个答案:

答案 0 :(得分:3)

我同意:这是一个反直觉的设计决定,恕我直言。

最简单的解决方案是将过滤器附加到每个可能的处理程序。例如,假设您有一个控制台处理程序,一个邮件处理程序和一个数据库处理程序,您应该将“根”过滤器附加到它们中的每一个。 : - /

import logging
import logging.config

class MyRootFilter(logging.Filter):
    def filter(self, record):
        # filter out log messages that include "secret"
        if "secret" in record.msg:
            return False
        else:
            return True

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'filters': {
        'my_root_filter': {
            '()': MyRootFilter,
        },
    },
    'handlers': {
        'stderr': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'filters': ['my_root_filter'],
        },
        'mail_admins': {
            'level': 'ERROR',
            'class': 'some.kind.of.EmailHandler',
            'filters': ['my_root_filter'],
        },
        'database': {
            'level': 'ERROR',
            'class': 'some.kind.of.DatabaseHandler',
            'filters': ['my_root_filter'],
        },
    },
    'loggers': {
        'some.sub.project': {
            'handlers': ['stderr'],
            'level': 'ERROR',
        },
    },
}

logging.config.dictConfig(LOGGING)
logging.getLogger("some.sub.project").error("hello")        # logs 'hello'
logging.getLogger("some.sub.project").error("hello secret") # filtered out!  :-)

如果有很多处理程序,您可能希望以编程方式而不是手动将根过滤器附加到每个处理程序。我建议您直接在配置字典(或文件,取决于您加载日志记录配置的方式)上执行此操作,而不是在加载配置后执行此操作,因为似乎没有记录的方式获取所有处理程序的列表。我找到了logger.handlers和logging._handlers,但由于它们没有记录,它们将来可能会中断。另外,不能保证它们是线程安全的。

之前的解决方案(在配置中直接将根过滤器附加到配置中的每个处理程序之前)假定您在加载之前可以控制日志记录配置,并且还不会动态添加处理程序(使用Logger# addHandler操作())。如果不是这样,那么你可能想要修补记录模块(祝你好运!)。

修改

我拍摄了猴子修补Logger#addHandler,只是为了好玩。它实际上工作正常,并简化了配置,但我不确定我会建议这样做(我讨厌猴子修补,它使得在出现问题时很难调试)。使用风险自负......

import logging
import logging.config

class MyRootFilter(logging.Filter):
   [...] # same as above

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'stderr': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            # it's shorter: there's no explicit reference to the root filter
        },
        [...]  # other handlers go here
    },
    'loggers': {
        'some.sub.project': {
            'handlers': ['stderr'],
            'level': 'ERROR',
        },
    },
}

def monkey_patched_addHandler(self, handler):
    result = self.old_addHandler(handler)
    self.addFilter(MyRootFilter())
    return result

logging.Logger.old_addHandler = logging.Logger.addHandler
logging.Logger.addHandler = monkey_patched_addHandler

logging.config.dictConfig(LOGGING)
logging.getLogger("some.sub.project").error("hello")        # logs 'hello'
logging.getLogger("some.sub.project").error("hello secret") # filtered out!  :-)

答案 1 :(得分:1)

与级别和处理程序不同,过滤器不会传播。但是,您可以将其附加到传播的处理程序。

通过Elliot at SaltyCrane Blog查看此示例:

import logging

class MyFilter(logging.Filter):
    def filter(self, record):
        record.msg = 'MY FILTER: ' + record.msg
        return 1

myfilter = MyFilter()

myformatter = logging.Formatter("MY HANDLER: %(name)s - %(message)s")

myhandler = logging.StreamHandler()
myhandler.setFormatter(myformatter)
myhandler.addFilter(myfilter)

foo_logger = logging.getLogger('foo')
foo_logger.addHandler(myhandler)

foo_bar_logger = logging.getLogger('foo.bar')

foo_logger.error('asdfasdf')
foo_bar_logger.error('zxcvzxcv')
MY HANDLER: foo - MY FILTER: asdfasdf
MY HANDLER: foo.bar - MY FILTER: zxcvzxcv

答案 2 :(得分:0)

以这种方式思考。伐木工人就像你家里的排水管。记录仪上的过滤器可防止您将物料倾倒入下水道,但不会过滤整个下水道。您在流程中的位置(上游,下游)不会改变此行为。

处理程序是管道。管道累积上游流量。默认管道是“skip,pass to parent”。如果要影响上游流,则需要将过滤器放入管道(处理程序)。如果查看logging flowchart,应该可以添加NullHandler(无格式化或输出)过滤器,然后传播消息。

这是你想要的行为。

答案 3 :(得分:0)

这里模拟 MiniQuark 是一种 imho 优雅的猴子修补方式(嵌入在上下文管理器中):

from typing import Iterable, Iterator
from contextlib import contextmanager


@contextmanager
def apply_filter_for_new_handlers(filters: Iterable[logging.Filter]) -> Iterator[None]:
    originial_addHandler = logging.Logger.addHandler

    def monkey_patched_addHandler(self: logging.Logger, hdlr: logging.Handler) -> None: # noqa:C0103
        for log_filter in filters:
            hdlr.addFilter(log_filter)
        return originial_addHandler(self, hdlr)

    logging.Logger.addHandler = monkey_patched_addHandler
    yield

    logging.Logger.addHandler = originial_addHandler


class MyRootFilter(logging.Filter):
   [...] # same as above


with apply_filter_for_new_handlers(filters=[MyRootFilter]):
    logging.getLogger("some.sub.project").error("hello")