Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested prefixes accross loggers in Python

Tags:

python

logging

I'm currently working on a project where were use a single root logger. I understand from reading about logging that is a Bad Thing but am struggling to find a nice solution to a nice benefit this gives us.

Something we do (in part, to get around not having different loggers but in part gives us a nice feature) is to have a log_prefix decorator.

e.g.

@log_prefix("Download")
def download_file():
    logging.info("Downloading file..")
    connection = get_connection("127.0.0.1")
    //Do other stuff
    return file

@log_prefix("GetConnection")
def get_connection(url):
    logging.info("Making connection")
    //Do other stuff
    logging.info("Finished making connection")
    return connection

This gives us some nicely formatting logs that might look like:

Download:Downloading file..
Download:GetConnection:Making Connection
Download:GetConnection:Other stuff
Download:GetConnection:Finished making connection
Download:Other stuff

This also means that if we have

@log_prefix("StartTelnetSession")
logging.info("Starting telnet session..")
connection = get_connection("127.0.0.1")

We get the same prefix at the end:

StartTelnetSession:Starting telnet session..
StartTelnetSession:GetConnection:Making Connection
StartTelnetSession:GetConnection:Other stuff
StartTelnetSession:GetConnection:Finished making connection

This has proven to be quite useful for development and support.

I can see plenty of cases where actually just using a separate logger for the action would solve our problem but I can also see cases where throwing away the nesting we have will make things worse.

Are there any patterns or common uses out there for nesting loggers? i.e

logging.getLogger("Download").getLogger("MakingConnection")

Or am I missing something here?

like image 538
Andy Smith Avatar asked Oct 14 '25 14:10

Andy Smith


2 Answers

You could use a LoggerAdapter to add extra contextual information:

utils_logging.py:

import functools

def log_prefix(logger, label, prefix=list()):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            prefix.append(label)
            logger.extra['prefix'] = ':'.join(prefix)
            result = func(*args, **kwargs)
            prefix.pop()
            logger.extra['prefix'] = ':'.join(prefix)
            return result
        return wrapper
    return decorator

foo.py:

import logging
import utils_logging as UL
import bar

logger = logging.LoggerAdapter(logging.getLogger(__name__), {'prefix':''})
@UL.log_prefix(logger, "Download")
def download_file():
    logger.info("Downloading file..")
    connection = bar.get_connection("127.0.0.1")

if __name__ == '__main__':
    logging.basicConfig(
        level=logging.INFO,
        format='%(prefix)s %(name)s %(levelname)s %(message)s')

    download_file()
    bar.get_connection('foo')

bar.py:

import logging
import utils_logging as UL
logger = logging.LoggerAdapter(logging.getLogger(__name__), {'prefix':''})

@UL.log_prefix(logger, "GetConnection")
def get_connection(url):
    logger.info("Making connection")
    logger.info("Finished making connection")

yields

Download __main__ INFO Downloading file..
Download:GetConnection bar INFO Making connection
Download:GetConnection bar INFO Finished making connection
GetConnection bar INFO Making connection
GetConnection bar INFO Finished making connection

Note: I don't think it is a good idea to have a new Logger instance for each prefix because these instances are not garbage collected. All you need is for some prefix variable to take on a different value depending on context. You don't need a new Logger instance for that -- one LoggerAdapter will do.

like image 66
unutbu Avatar answered Oct 17 '25 04:10

unutbu


Logger names are hierarchical.

logger = logging.getLogger("Download.MakingConnection")

This logger would inherit any configuration from logging.getLogger("Download").

Python 2.7 also added a convenience function for accessing descendants of an arbitrary logger.

logger = logging.getLogger("Download.MakingConnection")

parent_logger = logging.getLogger("Download")
child_logger = parent_logger.getChild("MakingConnection")

assert logger is child_logger
like image 31
chepner Avatar answered Oct 17 '25 04:10

chepner



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!