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

svi*_*tus 7 python logging

python日志记录文档的第15.7.4节:

注意每当一个事件被记录到该处理程序(使用调试(),信息(),等等),只要一个事件是由处理机发出的附接至处理滤波器进行协商,而附连到记录器的过滤器进行协商这意味着该事件已经由后代记录器生成的记录器不会被记录器的过滤器设置过滤,除非过滤器也已应用于这些后代记录器.

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

Min*_*ark 2

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

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

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

如果有很多处理程序,您可能希望以编程方式而不是手动将根过滤器附加到每个处理程序。我建议您直接在配置字典(或文件,取决于您加载日志配置的方式)上执行此操作,而不是在加载配置执行此操作,因为似乎没有记录的方法来获取所有处理程序的列表。我找到了 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!  :-)
Run Code Online (Sandbox Code Playgroud)