Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change log-level via mocking

Tags:

python

logging

I want to change the log-level temporarily.

My current strategy is to use mocking.

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

All logging.info() calls inside the method should get ignored and not logged to the console.

How to implement the ... to make logs of level INFO get ignored?

The code is single-process and single-thread and executed during testing only.

like image 993
guettli Avatar asked Mar 27 '18 10:03

guettli


People also ask

How do you change the log level?

To enable debug logging, run the following command: /subsystem=logging/root-logger=ROOT:change-root-log-level(level=DEBUG) To disable debug logging, run the following command: /subsystem=logging/root-logger=ROOT:change-root-log-level(level=INFO)

How do I change the log level in console?

The console opens in a new browser window. Click Troubleshooting > Logs and Trace. In the Logging and Tracing page, click the name of the Dashboard Application Services Hub (for example, server1). Click Change Log Detail Levels.

How do I change the logger level in spring boot?

If you only want to change the log levels of various packages, you can configure it in the application. properties. For example, the following entries change the log level of all spring framework classes to ERROR and all the classes under the package com. quickprogrammingtips to DEBUG.


2 Answers

I want to change the log-level temporarily.

A way to do this without mocking is logging.disable

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

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

This example would only show messages of level WARNING and above for each test in the TestSomething class. (You call disable at the start and end of each test as needed. This seems a bit cleaner.)

To unset this temporary throttling, call logging.disable(logging.NOTSET):

If logging.disable(logging.NOTSET) is called, it effectively removes this overriding level, so that logging output again depends on the effective levels of individual loggers.

like image 131
serv-inc Avatar answered Oct 11 '22 20:10

serv-inc


I don't think mocking is going to do what you want. The loggers are presumably already instantiated in this scenario, and level is an instance variable for each of the loggers (and also any of the handlers that each logger has).

You can create a custom context manager. That would look something like this:

Context Manager

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])

Test Code

# 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")

Results

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

Notes

  • The code would need to be enhanced to support multithreading; for example, logging.Logger.manager.loggerDict is a shared variable that's guarded by locks in the logging code.
like image 40
cryptoplex Avatar answered Oct 11 '22 19:10

cryptoplex