Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use different formatters with the same logging handler in python

Tags:

python

logging

Is it possible to log to a single destination (i.e. using one FileHandler) with multiple loggers (i.e. logging.getLogger("base.foo") and logging.getLogger("base.bar")), and use different formatters for each of the loggers.

To my understanding it's only possible to assign one formatter to each handle. Maybe it's possible to associate the formatter with a logger rather than the handler?

like image 311
sris Avatar asked Nov 16 '09 12:11

sris


People also ask

Can I have two loggers in Python?

if you transform the second module in a class, you can simply: declare the logger in the first modulue. create the new class passing the 2 logger as parameters. use the logger in the new class.

How do I change the format of a log file in Python?

setLevel(logging.INFO) # set a format which is simpler for console use formatter = logging. Formatter('%(name)-12s: %(levelname)-8s %(message)s') # tell the handler to use this format console. setFormatter(formatter) # add the handler to the root logger logging. getLogger('').

What are the five levels of logging in Python?

In Python, the built-in logging module can be used to log events. Log messages can have 5 levels - DEBUG, INGO, WARNING, ERROR and CRITICAL. They can also include traceback information for exceptions. Logs can be especially useful in case of errors to help identify their cause.


2 Answers

It's easy to dispatch to different formatters based on record.name. Below is prove-of-concept sample code:

import logging


class DispatchingFormatter:

    def __init__(self, formatters, default_formatter):
        self._formatters = formatters
        self._default_formatter = default_formatter

    def format(self, record):
        formatter = self._formatters.get(record.name, self._default_formatter)
        return formatter.format(record)


handler = logging.StreamHandler()
handler.setFormatter(DispatchingFormatter({
        'base.foo': logging.Formatter('FOO: %(message)s'),
        'base.bar': logging.Formatter('BAR: %(message)s'),
    },
    logging.Formatter('%(message)s'),
))
logging.getLogger().addHandler(handler)

logging.getLogger('base.foo').error('Log from foo')
logging.getLogger('base.bar').error('Log from bar')
logging.getLogger('base.baz').error('Log from baz')

Another way is to open file manually and create two stream handlers from it with different formatters.

like image 145
Denis Otkidach Avatar answered Oct 31 '22 17:10

Denis Otkidach


Little fix to excellent Denis's solution.

Logging name system based on hierarchical structure:

The name is potentially a period-separated hierarchical value, like foo.bar.baz (though it could also be just plain foo, for example). Loggers that are further down in the hierarchical list are children of loggers higher up in the list. For example, given a logger with a name of foo, loggers with names of foo.bar, foo.bar.baz, and foo.bam are all descendants of foo.

For example, when you setLevel() for some logger, this level will be also applied to child loggers. That's why you might want your formatter will be used for logger and it's child loggers too. For example, 'one.two' formatter should also be applied to 'one.two.three' logger (if no formatter for 'one.two.three' set). Here's version of DispatchingFormatter that do job (Python 3 code):

class DispatchingFormatter:
    """Dispatch formatter for logger and it's sub logger."""
    def __init__(self, formatters, default_formatter):
        self._formatters = formatters
        self._default_formatter = default_formatter

    def format(self, record):
        # Search from record's logger up to it's parents:
        logger = logging.getLogger(record.name)
        while logger:
            # Check if suitable formatter for current logger exists:
            if logger.name in self._formatters:
                formatter = self._formatters[logger.name]
                break
            else:
                logger = logger.parent
        else:
            # If no formatter found, just use default:
            formatter = self._default_formatter
        return formatter.format(record)

Example:

handler = logging.StreamHandler()
handler.setFormatter(DispatchingFormatter({
        'one': logging.Formatter('%(message)s -> one'),
        'one.two': logging.Formatter('%(message)s -> one.two'),
    },
    logging.Formatter('%(message)s -> <default>'),
))
logging.getLogger().addHandler(handler)

print('Logger used -> formatter used:')
logging.getLogger('one').error('one')
logging.getLogger('one.two').error('one.two')
logging.getLogger('one.two.three').error('one.two.three')  # parent formatter 'one.two' will be used here
logging.getLogger('other').error('other')

# OUTPUT:
# Logger used -> formatter used:
# one -> one
# one.two -> one.two
# one.two.three -> one.two
# other -> <default>
like image 25
Mikhail Gerasimov Avatar answered Oct 31 '22 16:10

Mikhail Gerasimov