Search code examples
pythonloggingpython-2.7handler

Python logging: propagate messages of level below current logger level


I want to log messages of a specific logger name, of a certain level and higher (say INFO and up) to a specific log handler, say a file handler, while still getting all log messages to the console. Python is version 2.7.

What I tried until now was to create two loggers:

  • A root logger
  • A named logger

For the root logger, I attached a logging.StreamHandler, and set the log level to logging.DEBUG.

Then I attached a handler to the named logger and set level to logging.INFO for that logger.

When I now call my module, which uses the named logger, I do not get DEBUG logs propagated to the root logger any more.

Note: the extraLogger has a StreamHandler here to demonstrate the issue. In my production code I'd use a FileHandler

import logging

def do_logging(turn):
    logger = logging.getLogger('extra')
    logger.info('some info turn %d' % turn) 
    logger.debug('this is debug fudge turn %d' % turn)

rootLogger = logging.getLogger()
handler = logging.StreamHandler()
rootFormatter = logging.Formatter('root - %(levelname)s: %(msg)s')
handler.setFormatter(rootFormatter)
rootLogger.addHandler(handler)
rootLogger.setLevel(logging.DEBUG)

do_logging(1)

extraLogger = logging.getLogger('extra')
extraHandler = logging.StreamHandler()
extraFormatter = logging.Formatter('extra - %(levelname)s: %(msg)s')
extraHandler.setFormatter(extraFormatter)
extraLogger.addHandler(extraHandler)
extraLogger.setLevel(logging.INFO)

do_logging(2)

Actual Output:

root - INFO: some info turn 1
root - DEBUG: this is debug fudge turn 1
extra - INFO: some info turn 2
root - INFO: some info turn 2

Output that I would like to have:

root - INFO: some info turn 1
root - DEBUG: this is debug fudge turn 1
extra - INFO: some info turn 2
root - INFO: some info turn 2
root - DEBUG: this is debug fudge turn 2

I suspect that a custom Filter would be helpful in this case, but I do not know how...


Solution

  • You could use robert's LevelFilter like this:

    # Put the Filter on the Handler so only INFO and higher is handled
    extraHandler.addFilter(LevelFilter(logging.INFO))
    
    # Let the Logger process everything (so it can propagate records to root)
    extraLogger.setLevel(logging.DEBUG)
    

    import logging
    
    class LevelFilter(logging.Filter):
        """
        https://stackoverflow.com/a/7447596/190597 (robert)
        """
        def __init__(self, level):
            self.level = level
    
        def filter(self, record):
            return record.levelno >= self.level
    
    def do_logging(turn):
        logger = logging.getLogger('extra')
        logger.info('some info turn %d' % turn) 
        logger.debug('this is debug fudge turn %d' % turn)
    
    rootLogger = logging.getLogger()
    handler = logging.StreamHandler()
    rootFormatter = logging.Formatter('root - %(levelname)s: %(msg)s')
    handler.setFormatter(rootFormatter)
    rootLogger.addHandler(handler)
    rootLogger.setLevel(logging.DEBUG)
    do_logging(1)
    
    extraLogger = logging.getLogger('extra')
    extraHandler = logging.StreamHandler()
    extraFormatter = logging.Formatter('extra - %(levelname)s: %(msg)s')
    extraHandler.setFormatter(extraFormatter)
    extraLogger.addHandler(extraHandler)
    
    # Put the Filter on the Handler so only INFO and higher is handled
    extraHandler.addFilter(LevelFilter(logging.INFO))
    
    # Handle everything (so it can propagate to root)
    extraLogger.setLevel(logging.DEBUG)
    do_logging(2)