Kurt Bourbaki
Kurt Bourbaki

Reputation: 12596

Different instance repr based on logging level

I'd like to log information about some instance. However, I want to show different levels of detail depending on the log level I'm using. For example:

LOGGER.level
# => 20
LOGGER.info('Car info: %s', Car())
# => [INFO]: Car info: Celica
LOGGER.setLevel(logging.DEBUG)
LOGGER.info('Car info: %s', Car())
# => [INFO]: Car info: {'name': 'Celica', 'year': 1998, 'model': 'GT-Four'}

So far, I came up with something like the following:

import logging

LOGGER = logging.getLogger()
logging.basicConfig(
    format='[%(levelname)s]: %(message)s',
    level=logging.INFO)
    
class Car:
    def __init__(self):
        self.name = 'Celica'
        self.year = 1998
        self.model = 'GT-Four'

    def __repr__(self):
        if LOGGER.level == logging.DEBUG:
            return str(self.__dict__)
            
        return f'{self.name}'

I have the feeling that this is a bit too "custom" and hardcoded, somehow. Using LOGGER or logging in __repr__ feels a bit odd. Is this the correct way to proceed, or is there a better way?

Upvotes: 1

Views: 929

Answers (1)

Vinay Sajip
Vinay Sajip

Reputation: 99365

Yes, that would be unusual logic to have in a __repr__ - perhaps a code smell, as you've identified. Normally presentation logic would be placed in a Formatter subclass, but you could also do it in a filter. Here's a simple working example; you should be able to adapt the basic idea into a more general scheme:

import logging

LOGGER = logging.getLogger()

class Car:
    def __init__(self):
        self.name = 'Celica'
        self.year = 1998
        self.model = 'GT-Four'

def filter(record):
    if record.args:
        args = []
        for arg in record.args:
            if isinstance(arg, Car):
                if record.levelno == logging.DEBUG:
                    arg = str(arg.__dict__)
                else:
                    arg = arg.name
            args.append(arg)
        record.args = tuple(args)
    return True

def main():
    LOGGER.setLevel(logging.DEBUG)
    f = logging.Formatter('[%(levelname)-5s]: %(message)s')
    h = logging.StreamHandler()
    h.setFormatter(f)
    LOGGER.addHandler(h)
    LOGGER.addFilter(filter)
    car = Car()
    LOGGER.info('basic : %s', car)
    LOGGER.debug('detail: %s', car)
    LOGGER.debug('no car in this %s', 'message')
    LOGGER.debug('nor in this one')

if __name__ == '__main__':
    main()

When run, the above script prints:

[INFO ]: basic : Celica                                              
[DEBUG]: detail: {'name': 'Celica', 'year': 1998, 'model': 'GT-Four'}
[DEBUG]: no car in this message                                      
[DEBUG]: nor in this one                                             

Upvotes: 1

Related Questions