我可以创建一个命名的子记录器,这样该记录器输出的所有日志都标有它的名称。我可以在我的函数/类/任何东西中专门使用该记录器。
然而,如果该代码调用了另一个模块中的函数,该模块仅使用日志记录模块函数(根记录器的代理)来使用日志记录,我如何确保这些日志消息通过同一个记录器(或者至少以相同的方式记录)?
例如:
主.py
import logging
import other
def do_stuff(logger):
logger.info("doing stuff")
other.do_more_stuff()
if __name__ == '__main__':
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("stuff")
do_stuff(logger)
其他.py
import logging
def do_more_stuff():
logging.info("doing other stuff")
输出:
$ python main.py
INFO:stuff:doing stuff
INFO:root:doing other stuff
我希望能够使两个日志行都用名称"stuff"标记,并且我希望能够只更改main.py.
如何在不更改模块的情况下使other.py中的日志记录调用使用不同的记录器?
这是我提出的解决方案:
使用线程本地数据来存储上下文信息,并在根记录器处理程序上使用Filter将这些信息在发出之前添加到LogRecords。
context = threading.local()
context.name = None
class ContextFilter(logging.Filter):
def filter(self, record):
if context.name is not None:
record.name = "%s.%s" % (context.name, record.name)
return True
这对我来说很好,因为我使用记录器名称来指示记录此消息时正在执行的任务。
然后,我可以使用上下文管理器或装饰器,使来自特定代码段的日志记录看起来都像是从特定的子记录器记录的。
@contextlib.contextmanager
def logname(name):
old_name = context.name
if old_name is None:
context.name = name
else:
context.name = "%s.%s" % (old_name, name)
try:
yield
finally:
context.name = old_name
def as_logname(name):
def decorator(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
with logname(name):
return f(*args, **kwargs)
return wrapper
return decorator
那么,我可以做:
with logname("stuff"):
logging.info("I'm doing stuff!")
do_more_stuff()
或:
@as_logname("things")
def do_things():
logging.info("Starting to do things")
do_more_stuff()
关键是,do_more_stuff()
所做的任何日志记录都将被记录,就像它是用"东西"或"东西"子记录器记录的一样,而不必更改do_more_stuff()
。
如果您要在不同的子记录器上使用不同的处理程序,则此解决方案会出现问题。
使用logging.setLoggerClass
,以便其他模块使用的所有记录器都使用您的记录器子类(emphasis mine):
告诉日志记录系统在实例化记录器时使用klass类。类应该定义
__init__()
,这样只需要一个名称参数,而__init__()
应该调用Logger.__init__()
此函数通常在需要使用自定义记录器行为的应用程序实例化任何记录器之前调用。
这就是logging.handlers(或日志模块中的处理程序)的作用。除了创建记录器之外,还可以创建一个或多个处理程序,将日志信息发送到各个位置,并将它们添加到根记录器中。大多数进行日志记录的模块都会创建一个记录器,用于自己的目的,但依赖于控制脚本来创建处理程序。一些框架决定非常有用,并为您添加处理程序。
阅读日志文档,一切都在那里。
(编辑)
logging.basicConfig()是一个向根记录器添加单个处理程序的辅助函数。您可以通过"format="参数控制它使用的格式字符串。如果您只想让所有模块显示"stuff",那么使用logging.basicConfig(level=logging.INFO, format="%(levelname)s:stuff:%(message)s")
。
logging.{info,warning,…}
方法只调用名为root
的Logger
对象上的相应方法(参见日志模块源代码),因此,如果您知道other
模块只调用logging
模块导出的函数,则可以使用logger
对象覆盖other
命名空间中的logging
模块
import logging
import other
def do_stuff(logger):
logger.info("doing stuff")
other.do_more_stuff()
if __name__ == '__main__':
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("stuff")
# Overwrite other.logging with your just-generated logger object:
other.logging = logger
do_stuff(logger)