来自python logging documentation的第15.7.4段:
请注意,每当发生事件时,都会查询附加到处理程序的过滤器 由处理程序发出,而附加到记录器的过滤器是 每当事件被记录到处理程序时(使用debug(),请参考) info()等)这意味着已经生成的事件 后代记录器不会被记录器的过滤器设置过滤, 除非过滤器也已应用于那些后代记录器。
我不明白这个设计决定。将根记录器的过滤器应用于后代记录器也没有意义吗?
答案 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")