qtils.log_utils module

Logging Utilities module

Logging enhancements

qtils.log_utils.LOG_FORMATS

Pre-defined logging formats for convenience.

Available formats:

Name Example
LOG_FORMATS.SHORT 2019-10-10 11:11:11   INFO    mypackage.mymodule.MyClass:      Hello World
LOG_FORMATS.THREAD 2019-10-10 11:11:11   INFO    [tid:11 (myThread)] mypackage.mymodule.MyClass:      Hello World
LOG_FORMATS.PROCESS 2019-10-10 11:11:11   INFO    [pid:777]   mypackage.mymodule. MyClass:      Hello World
LOG_FORMATS.FULL 2019-10-10 11:11:11   INFO    [pid:777 tid:11(myThread)]  mypackage. mymodule.MyClass:      Hello World
qtils.log_utils.logged(*args, **kwargs)

Decorator to create and assign a logger to a class

Parameters:
  • channel (str) – Name of the logger, defaults to cls.__name__
  • root_channel (str) – Prefix to the name of the logger, helpful to namespace a large ammount of logged objects.
  • attr_name (str) –

    Name of the logger attribute. Defaults to __logger.

    Note

    Variables begining with double underscore are class private variables, meaning they are accessible ONLY to methods within the same class, and not to descendant classes or from the outside. Read more about it in Private Variables documentation.

Examples

Simplest use case, adding a logger to a class:

>>> @logged
... class LoggedFoo():
...     def __init__(self):
...         self.__logger.info("Hello World!")
...
>>> foo = LoggedFoo()
>>> # INFO:LoggedFoo:Hello World!
>>> #
>>> # doctest doesn't allow testing logging output,
>>> # so just believe me on this one. :)
>>> # And also, there are proper test cases in pytest.

Loggers are class-private by default:

>>> @logged
... class LoggedFoo():
...     def __init__(self):
...         self.__logger.info("Hello World from Foo!")
...
>>> class DescendantFoo(LoggedFoo):
...     def __init__(self):
...         super().__init__()
...         # The following line will fail because
...         # it has no access to the parent classes
...         # class-private logger.
...         self.__logger.info("Hello World from Descendant!") #
...
>>> foo = DescendantFoo()
Traceback (most recent call last):
...
AttributeError: 'DescendantFoo' object has no attribute '_DescendantFoo__logger'

The recommended solution is to create loggers for each subclass. This means their channel will always reflect the class name where the log line is comming from. This is helpful in a large project with complicated inheritance trees.

>>> @logged
... class LoggedFoo():
...     def __init__(self):
...         self.__logger.info("Hello World from Foo!")
...
>>> @logged
... class DescendantFoo(LoggedFoo):
...     def __init__(self):
...         super().__init__()
...         self.__logger.info("Hello World from Descendant!") #
...
>>> foo = DescendantFoo()
>>> # INFO:LoggedFoo:Hello World from Foo!
>>> # INFO:DescendantFoo:Hello World from Descendant!
>>> #
>>> # Note the Logger Name difference for the two lines.

The second solution is to create a logger with a non-private attribute name. This could be useful for situations when there is a bunch of small descendant classes which doesn’t need their own logger. This typically happens in marshallers when each type has it’s own little class for serializing/deserializing.

>>> @logged(attr_name="logger")
... class LoggedFoo():
...     def __init__(self):
...         self.logger.info("Hello World from Foo!")
...
>>> class DescendantFoo(LoggedFoo):
...     def __init__(self):
...         super().__init__()
...         self.logger.info("Hello World from Descendant!") #
...
>>> foo = DescendantFoo()
>>> # INFO:LoggedFoo:Hello World from Foo!
>>> # INFO:LoggedFoo:Hello World from Descendant!
>>>
>>> # Both logs seem to come from the same line