【问题标题】:How can you make logging module functions use a different logger?如何使日志记录模块功能使用不同的记录器?
【发布时间】:2013-07-30 14:04:49
【问题描述】:

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

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

例如:

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)

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

【问题讨论】:

    标签: python logging


    【解决方案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()

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

    【讨论】:

    • 似乎很自恋地接受我自己的答案,但我这样做是因为这就是我正在使用的解决方案。仍然对更好的解决方案持开放态度。
    【解决方案2】:

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

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

    【讨论】:

    • 你能用一个例子扩展这个答案吗?
    • 也许有一些特殊的用例,但 99% 的情况下,这只是添加处理程序的问题。
    • 另外,这会设置子记录器的类,但不允许您控制根记录器。因此,这不会影响诸如logging.info("hello") 之类的调用,因为它们将使用根记录器。
    【解决方案3】:

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

    阅读logging docs,它就在那里。

    (编辑)

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

    【讨论】:

    • 添加处理程序如何使您无法控制的其他模块以您指定的格式记录?
    • @agf,假设一个模块登录“mighty.module”。您创建一个处理程序,向它添加一个格式化程序,然后将处理程序添加到记录器。通常,您在根记录器 (logging.getLogger()) 上执行此操作,但您可以在任何地方执行此操作 (logging.getLogger("mighty.module")。
    • @agf,logging howto 有很好的例子。
    • 我认为这个答案可能没有抓住重点。如果我有两个函数,一个使用子记录器 X,另一个使用子记录器 Y,并且两个函数都调用 do_more_stuff(),我怎样才能让 do_more_stuff() 使用调用者设置的记录器而不更改 do_more_stuff 本身?将根记录器上的格式设置为具有硬编码值而不是 %(name)s 比我希望的要尴尬一些,
    • @SpoonMeiser - 这是一个很难解决的问题,取决于你的代码是如何工作的。但是,如果一个任务是在单个线程上完成的,您可以记录“thread-6 start task ABC”的等价物,...一堆其他日志,包括线程 id...,“thread-6 end task ABC " 然后您可以对日志文件进行后处理,以提取该线程开始和结束之间的内容。
    【解决方案4】:

    logging.{info,warning,…} 方法只是在名为rootLogger 对象上调用相应的方法(参见logging module source),所以如果您知道other 模块仅调用由@ 导出的函数987654326@ 模块,你可以用你的logger 对象覆盖others 命名空间中的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)
    

    【讨论】:

    • 不,这不是一个好的解决方案。无需覆盖模块记录器或花时间找出模块存储它们的位置。
    猜你喜欢
    • 2014-04-04
    • 2020-11-07
    • 1970-01-01
    • 1970-01-01
    • 2011-10-21
    • 1970-01-01
    • 2020-08-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多