3

我可以创建一个命名的子记录器,以便该记录器输出的所有日志都标有它的名称。我可以在我的函数/类/任何东西中专门使用该记录器。

但是,如果该代码调用另一个模块中的函数,该模块仅使用日志记录模块函数(根记录器的代理)使用日志记录,我如何确保这些日志消息通过相同的记录器(或至少是以相同的方式登录)?

例如:

主文件

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 中的日志记录调用使用不同的记录器?

4

4 回答 4

1

这是我想出的解决方案:

使用线程本地数据存储上下文信息,并在根记录器处理程序上使用过滤器在发出之前将此信息添加到 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()都将被记录,就好像它是使用“stuff”或“things”子记录器记录的一样,而无需进行任何更改do_more_stuff()

如果您要在不同的子记录器上使用不同的处理程序,则此解决方案会出现问题。

于 2013-11-04T15:57:04.520 回答
0

使用logging.setLoggerClass以便其他模块使用的所有记录器都使用您的记录器子类(强调我的):

告诉记录系统在实例化记录器时使用类 klass。该类应该定义__init__()为只需要一个 name 参数,并且__init__()应该调用Logger.__init__(). 此函数通常在需要使用自定义记录器行为的应用程序实例化任何记录器之前调用

于 2013-07-30T14:13:56.927 回答
0

这就是 logging.handlers(或日志模块中的处理程序)的用途。除了创建记录器之外,您还可以创建一个或多个处理程序来将记录信息发送到各个位置并将它们添加到根记录器。大多数进行日志记录的模块都会创建一个记录器,它们用于自己的目的,但依赖于控制脚本来创建处理程序。一些框架决定超级有用并为您添加处理程序。

阅读日志记录文档,它就在那里。

(编辑)

logging.basicConfig() 是一个辅助函数,可将单个处理程序添加到根记录器。您可以使用 'format=' 参数控制它使用的格式字符串。如果您只想让所有模块显示“东西”,那么使用logging.basicConfig(level=logging.INFO, format="%(levelname)s:stuff:%(message)s").

于 2013-07-30T16:56:13.913 回答
-1

这些logging.{info,warning,…}方法只是在一个名为的Logger对象上调用相应的方法root(参见logging 模块 source),因此如果您知道该other模块仅调用该模块导出的函数,您可以用您的对象logging覆盖s 命名空间logging中的模块:otherlogger

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)
于 2013-07-30T14:23:30.820 回答