首页 文章

如何使记录模块功能使用不同的 Logger ?

提问于
浏览
3

我可以创建一个命名的子 Logger ,以便该 Logger 输出的所有日志都用它的名称标记 . 我可以在我的函数/类/中使用该 Logger .

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

例如:

main.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)

other.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中使用日志记录调用来使用不同的 Logger 而不更改该模块?

4 回答

  • 1

    这是我提出的解决方案:

    使用线程本地数据存储上下文信息,并使用根 Logger 处理程序上的过滤器在发出之前将此信息添加到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
    

    这对我来说很好,因为我使用 Logger 名称来指示记录此消息时正在执行的任务 .

    然后,我可以使用上下文管理器或装饰器从特定的代码段中进行日志记录,就好像它是从特定的子 Logger 记录一样 .

    @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"子 Logger 记录的,而根本不需要更改 do_more_stuff() .

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

  • -1

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

    告诉日志记录系统在实例化 Logger 时使用类klass . 该类应该定义__init __(),这样只需要一个name参数,而__init ()应该调用Logger . init __() . 通常在需要使用自定义 Logger 行为的应用程序实例化任何 Logger 之前调用此函数 .

  • 0

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

    阅读logging docs,它就在那里 .

    (编辑)

    logging.basicConfig()是一个辅助函数,它向根 Logger 添加一个处理程序 . 您可以使用'format='参数控制它使用的格式字符串 . 如果你想要做的就是让所有模块显示"stuff",那么使用 logging.basicConfig(level=logging.INFO, format="%(levelname)s:stuff:%(message)s") .

  • 0

    logging.{info,warning,…} 方法只调用名为 rootLogger 对象上的相应方法(参见logging module source),因此如果您知道 other 模块只调用 logging 模块导出的函数,则可以覆盖 other 命名空间中的 logging 模块 . 你的 logger 对象:

    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)
    

相关问题