为什么附加到根记录器的过滤器不传播到子记录器?
在Python日志记录文档的第15.7.4段中提到:
注意,当处理程序发出事件时,会检查附加到处理程序的过滤器,而当事件被记录到处理程序(使用debug()、info()等)时,会检查附加到记录器的过滤器。这意味着由子记录器生成的事件不会被父记录器的过滤器设置过滤,除非这个过滤器也被应用到那些子记录器上。
我不太理解这个设计决定。难道不应该让根记录器的过滤器也适用于子记录器吗?
4 个回答
可以这样理解,日志记录器就像你家里的排水管。给日志记录器加个过滤器,就像是在排水管上装个滤网,这样可以防止一些东西直接流进下水道,但它并不会过滤整个下水道。你在水流中的位置(上游或下游)并不会改变这个过滤的效果。
而处理器就像是管道。管道会收集上游流来的信息。默认的管道行为是“跳过,传递给父级”。如果你想影响上游的信息流,就需要在管道(处理器)上加个过滤器。如果你查看一下日志流向图,你会发现可以添加一个NullHandler(不进行格式化或输出),它可以过滤信息后再传递。
这就是你想要的效果。
过滤器和级别、处理器不一样,它们不会自动传递。不过,你可以把过滤器附加到一个会传递的处理器上。
看看这个由 Elliot 在 SaltyCrane 博客 提供的例子:
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
我同意:这个设计决定有点反直觉,个人觉得。
最简单的解决办法就是把你的过滤器加到每一个可能的处理器上。比如说,你有一个控制台处理器、一个邮件处理器和一个数据库处理器,你应该把你的“根”过滤器加到每一个处理器上。 :-/
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! :-)