čtvrtek 5. října 2017

Python logger by class

Common Python logging practice is to have one logger per module (e.s. see the Fang's blog):
import logging
logger = logging.getLogger(__name__)
However, the general habit in other OOP programming languages (Java, C#, ...) is to have one (private static) logger per class:
public class MyObject {
    private final static Logger LOG = Logger.getLogger(MyObject.class.getName());
}
In Python, a logger per class may be created, too:
import logging
class Foo(object):
    __log = logging.getLogger(__name__ + '.Foo')

    def foo(self):
        self.__log.info('foo called')


class Bar(Foo):
    __log = logging.getLogger(__name__ + '.Bar')

    def bar(self):
        self.__log.info('bar called')


bar = Bar()
bar.foo()
bar.bar()

>>> INFO:__main__.Foo:foo called
>>> INFO:__main__.Bar:bar called

Note the double leading underscore __log to exploit the Python name mangling to simulate a private attribute. However, it is not very convenient to write the name of the class as a string. One way to avoid that is to init the logger after the class has been created:
class Bar(Foo):
    def bar(self):
        self.__log.info('bar called')


Bar._Bar__log = logging.getLogger(__name__ + Bar.__name__) # manual name mangling
Still not very nice. We had to type the name of the class manually again. So let's exploit a decorator black magic to do it automatically:
def addlogger(cls: type):
    aname = '_{}__log'.format(cls.__name__)
    setattr(cls, aname, logging.getLogger(cls.__module__ + '.' + cls.__name__))
    return cls


@addlogger
class Foo(object):
    def foo(self):
        self.__log.info('foo called')


@addlogger
class Bar(Foo):
    def bar(self):
        self.__log.info('bar called')


bar = Bar()
bar.foo()
bar.bar()

>>> INFO:__main__.Foo:foo called
>>> INFO:__main__.Bar:bar called
And voilà, we have a private static logger for each class with just one simple line or code.