通过模拟更改日志级别

时间:2018-03-27 10:25:04

标签: python logging

我想暂时更改日志级别。

我目前的策略是使用模拟。

with mock.patch(...):
    my_method_which_does_log()

方法中的所有logging.info()调用都应该被忽略,并且不会记录到控制台。

如何实现...以使级别INFO的日志被忽略?

代码是单进程和单线程,仅在测试期间执行。

5 个答案:

答案 0 :(得分:4)

  

我想暂时更改日志级别。

无需嘲笑的方法是logging.disable

class TestSomething(unittest.TestCase):
    def setUp(self):
        logging.disable(logging.WARNING)

    def tearDown(self):
        logging.disable(logging.NOTSET)

此示例仅显示WARNING类中每个测试的级别TestSomething及更高级别的消息。 (您可以根据需要在每个测试的开始和结束时调用disable。这看起来更清晰。)

要取消设置此临时限制,请致电logging.disable(logging.NOTSET)

  

如果调用logging.disable(logging.NOTSET),它会有效地删除此覆盖级别,以便再次记录输出取决于各个记录器的有效级别。

答案 1 :(得分:3)

我认为嘲笑不会做你想做的事。记录器可能已在此方案中实例化,level是每个记录器(以及每个记录器具有的任何处理程序)的实例变量。

您可以创建自定义上下文管理器。这看起来像这样:

上下文管理器

import logging

class override_logging_level():

    "A context manager for temporarily setting the logging level"

    def __init__(self, level, process_handlers=True):
        self.saved_level      = {}
        self.level            = level
        self.process_handlers = process_handlers

    def __enter__(self):

        # Save the root logger
        self.save_logger('', logging.getLogger())

        # Iterate over the other loggers
        for name, logger in logging.Logger.manager.loggerDict.items():

            self.save_logger(name, logger)

    def __exit__(self, exception_type, exception_value, traceback):

        # Restore the root logger
        self.restore_logger('', logging.getLogger())

        # Iterate over the loggers
        for name, logger in logging.Logger.manager.loggerDict.items():

            self.restore_logger(name, logger)

    def save_logger(self, name, logger):

        # Save off the level
        self.saved_level[name] = logger.level

        # Override the level
        logger.setLevel(self.level)

        if not self.process_handlers:
            return

        # Iterate over the handlers for this logger
        for handler in logger.handlers:

            # No reliable name. Just use the id of the object
            self.saved_level[id(handler)] = handler.level

    def restore_logger(self, name, logger):

        # It's possible that some intervening code added one or more loggers...
        if name not in self.saved_level:
            return

        # Restore the level for the logger
        logger.setLevel(self.saved_level[name])

        if not self.process_handlers:
            return

        # Iterate over the handlers for this logger
        for handler in logger.handlers:

            # Reconstruct the key for this handler
            key = id(handler)

            # Again, we could have possibly added more handlers
            if key not in self.saved_level:
                continue

            # Restore the level for the handler
            handler.setLevel(self.saved_level[key])

测试代码

# Setup for basic logging
logging.basicConfig(level=logging.ERROR)

# Create some loggers - the root logger and a couple others
lr = logging.getLogger()
l1 = logging.getLogger('L1')
l2 = logging.getLogger('L2') 

# Won't see this message due to the level
lr.info("lr - msg 1")
l1.info("l1 - msg 1")
l2.info("l2 - msg 1")

# Temporarily override the level
with override_logging_level(logging.INFO):

    # Will see
    lr.info("lr - msg 2")
    l1.info("l1 - msg 2")
    l2.info("l2 - msg 2")

# Won't see, again...
lr.info("lr - msg 3")
l1.info("l1 - msg 3")
l2.info("l2 - msg 3")

结果

$ python ./main.py
INFO:root:lr - msg 2
INFO:L1:l1 - msg 2
INFO:L2:l2 - msg 2

注释

  • 需要增强代码以支持多线程;例如,logging.Logger.manager.loggerDict是一个共享变量,由logging代码中的锁保护。

答案 2 :(得分:1)

使用@cryptoplex使用上下文管理器的方法,这里是official version from the logging cookbook

import logging
import sys

class LoggingContext(object):
    def __init__(self, logger, level=None, handler=None, close=True):
        self.logger = logger
        self.level = level
        self.handler = handler
        self.close = close

    def __enter__(self):
        if self.level is not None:
            self.old_level = self.logger.level
            self.logger.setLevel(self.level)
        if self.handler:
            self.logger.addHandler(self.handler)

    def __exit__(self, et, ev, tb):
        if self.level is not None:
            self.logger.setLevel(self.old_level)
        if self.handler:
            self.logger.removeHandler(self.handler)
        if self.handler and self.close:
            self.handler.close()
        # implicit return of None => don't swallow exceptions

答案 3 :(得分:0)

您可以使用dependency injection将记录器实例传递给您正在测试的方法。虽然你稍微改变一下你的方法,但它更具侵略性,但是它给你更多的灵活性。

将logger参数添加到方法签名中,类似于:

def my_method( your_other_params, logger):
    pass

在您的单元测试文件中:

if __name__ == "__main__":
    # define the logger you want to use:
    logging.basicConfig( stream=sys.stderr )
    logging.getLogger( "MyTests.test_my_method" ).setLevel( logging.DEBUG )

...

def test_my_method(self):
    test_logger = logging.getLogger( "MyTests.test_my_method" )
    # pass your logger to your method    
    my_method(your_normal_parameters, test_logger)

python logger docs:https://docs.python.org/3/library/logging.html

答案 4 :(得分:0)

我使用此模式将所有日志写入列表。它会忽略INFO级或更小的日志。

logs=[]

import logging
def my_log(logger_self, level, *args, **kwargs):
    if level>logging.INFO:
        logs.append((args, kwargs))

with mock.patch('logging.Logger._log', my_log):
    my_method_which_does_log()