Advanced use

Get a children tree

As we said in the Basics part, all Logging objects can own a list of children and a parent, and is part of a Logging tree like this:

../../../../../../_images/tree.png

Here is a snippet presenting the creation of the tree seen above:

# level 1
logger = gLogger.getSubLogger("logger")
# level 2
sublogger1 = logger.getSubLogger("sublogger1")
sublogger2 = logger.getSubLogger("sublogger2")
# level 3
subsublogger = sublogger1.getSubLogger("subsublogger")

Set a child level

The level propagation

As every Logging object is part of a tree, the level of a parent can be propagated to its children. Thus, we do not have to set all the children levels:

# gLogger level: NOTICE
logger = gLogger.getSubLogger("logger")
print logger.getLevel()
# > NOTICE

While the children levels are not defined by the user, they are modified according to the parent level:

logger = gLogger.getSubLogger("logger")
sublogger = logger.getSubLogger("sublogger")
print logger.getLevel()
print sublogger.getLevel()
# > NOTICE
# > NOTICE
logger.setLevel("error")
print logger.getLevel()
print sublogger.getLevel()
# > ERROR
# > ERROR

The only way to stop the propagation is to use the setLevel method on a Logging. For instance, in the previous example, logger has now its own level, and it can not be changed by its parent:

logger = gLogger.getSubLogger("logger")
print logger.getLevel()
# > NOTICE
logger.setLevel("error")
print logger.getLevel()
# > ERROR
gLogger.setLevel("debug")
print logger.getLevel()
# > ERROR

Nevertheless, the propagation is still existing for the children of logger:

logger = gLogger.getSubLogger("logger")
sublogger = logger.getSubLogger("sublogger")
print logger.getLevel()
print sublogger.getLevel()
# > NOTICE
# > NOTICE
logger.setLevel("error")
print logger.getLevel()
print sublogger.getLevel()
# > ERROR
# > ERROR
gLogger.setLevel("debug")
print gLogger.getLevel()
print logger.getLevel()
print sublogger.getLevel()
# > DEBUG
# > ERROR
# > ERROR
logger.setLevel("verbose")
print gLogger.getLevel()
print logger.getLevel()
print sublogger.getLevel()
# > DEBUG
# > VERBOSE
# > VERBOSE

To summarize, a Logging receives its parent level until the user sets its level with the setLevel method.

Add a Backend object on a child Logging

registerBackend presentation

Now, it is possible to add some Backend objects to any Logging via the registerBackend method. This method takes two parameters, a name of a Backend objects, and a dictionary of attribute names and their values associated. Here is an example of use:

logger = gLogger.getSubLogger("logger")
logger.registerBackend('stdout')
logger.registerBackend('file', {'FileName': 'file.log'})

This, will create stdout and file Backend objects in logger.

Log records propagation

Obviously, each log record created by a child Logging goes up in its parent if the true Logging level allowed it, but as it is always at debug, it goes up anyway. The log record goes up until gLogger and it is displayed in all the Backend objects encounter in the parents if the level allowed it.

In this way, gLogger display every log records of every Logging object, even if you add Backend objects in a child, the log record will appears multiple times in this case. Here is an example:

# gLogger has a stdout Backend
logger = gLogger.getSubLogger("logger")
logger.registerBackend('stdout')
logger.verbose("message")
# > 2017-04-25 15:51:01 UTC Framework/Atom/logger VERBOSE: message
# > 2017-04-25 15:51:01 UTC Framework/Atom/logger VERBOSE: message
gLogger.info("message")
# > 2017-04-25 15:51:01 UTC Framework/Atom/logger INFO: message

We can also notice that the log records do not go down in the tree.

The registerBackend(s) utility

This functionality gives the possibility to isolate some log records from a specific Logging or isolate log records above a specific level. For example, we want only, at minimum, error log records providing by a specific child named logger in a file named file.log. Here is a snippet of this example:

# gLogger: stdout Backend, NOTICE level
logger = gLogger.getSubLogger("logger")
logger.registerBackend('file', {'FileName': 'file.log'})
logger.setLevel("error")
logger.verbose("appears only in stdout")
logger.notice("appears only in stdout")
logger.error("appears in stdout and in file.log")
# in stdout:
# > ... UTC Framework/Atom/logger VERBOSE: appears only in stdout
# > ... UTC Framework/Atom/logger NOTICE: appears only in stdout
# > ... UTC Framework/Atom/logger ERROR: appears in stdout, in file.log
# in file.log:
# > ... UTC Framework/Atom/logger ERROR: appears in stdout, in file.log

Modify a display for different Logging objects

showHeaders/TimeStamps/Contexts/ThreadIDs propagation

We also have the possibility to modify the log format by calling the methods from any existing Logging object methods.

Thus, these methods function exactly as the setLevel method, so they can be propagated in the children if the options are not modified by the user.

showHeaders/TimeStamps/Contexts/ThreadIDs utility

Here, the utility is to modify the display format of the isolate log records from a specific Logging to not be embarrassed with extra information that we do not want for example:

# gLogger: stdout Backend, NOTICE level, showHeaders at True
logger = gLogger.getSubLogger("logger")
logger.setLevel("error")
logger.showHeaders(True)
logger.showTimeStamps(False)
logger.showContexts(False)
logger.verbose("appears only in stdout")
# in stdout:
# > VERBOSE: appears only in stdout

The LogColor case

This option can not be modified in the children of gLogger, even by gLogger itself after the configuration, so the children receive the gLogger configuration.

Add variables to different Logging objects depending on the context

In complex cases, it can be useful to have loggers that change depending on the execution context, without having to pass logger instances explicitly through multiple layers of function calls.

Python’s contextvars module provides context-local storage, which can be used to store and retrieve context-specific data, such as logger instances.

gLogger supports the use of context variables to manage loggers in a flexible way.

Provide a Context Logger

When you have a Logging instance that you want to use in a specific context, you can set it in the context variable:

# Create a logger instance
logger = gLogger.getSubLogger("MyContextLogger")

# Set it in the context variable
contextLogger.set(logger)

Then, the instances within the context block will use the shared Logging object set in the context variable:

with setContextLogger(contextualLogger):
    # Any logging within this block will use contextualLogger
    obj = MyClass()
    obj.do_something()  # This will use contextualLogger

Consume a Context Logger

In functions or classes that need to log messages, you can retrieve the logger from the context variable:

class MyClass:
    def __init__(self):
        # Get the default logger if no context logger is set
        self._defaultLogger = gLogger.getSubLogger("MyClass")

    @property
    def log(self):
        # Return the context logger if set, otherwise the default logger
        return contextLogger.get() or self._defaultLogger

    @log.setter
    def log(self, value):
        # Optionally, allow setting a new default logger
        self._defaultLogger = value

    def do_something(self):
        self.log.notice("Doing something")

Some examples and summaries

Summary diagram

Here is a diagram showing the complete path of a log record from its creation to its emission in an output:

../../../../../../_images/summary.png