Skip to content

Instantly share code, notes, and snippets.

@vladak
Last active December 16, 2022 19:42
Show Gist options
  • Save vladak/15788891bdc2d4742dd106d65667b5a9 to your computer and use it in GitHub Desktop.
Save vladak/15788891bdc2d4742dd106d65667b5a9 to your computer and use it in GitHub Desktop.
CircuitPython logging vs CPython logging

CircuitPython logging vs CPython logging

Note: this was written before support for multiple handlers per logger was added to CircuitPython logging.

Logger hierarchy

In CPython, there is a hierarchy of loggers. Each logger except the root logger has a parent. When a log record is handled for particular logger, the hierarchy is traversed until either the propagate flag of given logger is found to be False, or root logger is encountered.

During this traversal, for each logger, the log record is passed to all its handlers.

This schema is useful for module hierarchy, where a function in given module calls:

logger = logging.getLogger(__name__)

where __name__ can be something in the form of a.b.c, which makes it possible to set loggers for various points in the module hierarchy.

CircuitPython does not have this hierarchy.

When getLogger() is called with no arguments, the logger name in CircuitPython will be empty string, while in CPython it will be root.

Default vs. last resort logger

In CircuitPython, getLogger() automatically assigns the newly created logger the StreamHandler handler.

In CPython, the created logger does not have any handler. There is a concept of global last resort handler, that is set to _StderrHandler(WARNING). If there is no handler found after traversing the hierarchy of loggers, this last resort handler is used. If the last resort handler is disabled, there are no handlers to be used, and a warning message is emitted to stderr:

> import logging
> logger = logging.getLogger("foo")
> logger.hasHandlers()
False
> logger.handlers
[]
> logger.parent
<RootLogger root (WARNING)>
> logger.parent.handlers
[]
> logger.warning("foo")
foo
> logging.lastResort
<_StderrHandler <stderr> (WARNING)>
> logging.lastResort = None
> logger.warning("foo")
No handlers could be found for logger "foo"

Note: this 'No handlers could be found...' message is emitted just once.

Currently in CircuitPython it is not possible to remove the handler from the logger (at least using the public functions of the Logger class), so no special handling of non-existing handler is necessary.

Filters

CPython logging has a concept of per handler filters, that allow to perform matching on log records. If the record is not matched, it will not be passed to a logger. This filtering action happens in the handle() function before the emit() function, that performs the action in given handler, is called.

CircuitPython logging lacks the concept of filters, hence emit() is used directly.

In fact, the Handler class in CPython extends the Filterer class. In CicruitPython the Handler class is standalone.

Object lifecycle

In CPython, the logging related objects are released upon logging.shutdown(). It also uses weak references.

CircuitPython logging has no concept of object lifecycle.

Handler log level

Log handlers CPython have a log level that is checked in handle(). When logging a message, 2 level checks are performed - once for the logger level and another one for each handler in that logger.

In CicruitPython logging, the handle() function is not part of the Handler API and the handlers do not have log levels.

Exception handling during handler's emit()

In CPython, Handler subclasses should call handleError() defined in the base class, if they hit an exception during emit(). The sub-classes can obvisouly override the handleError() function. By default it will print some detailed information about the exception (by walking the exception stack) to stderr. In some rare circumstanes it will rethrow the exception (recursion). There is a global tunable raiseExceptions that controls the behavior of exception handling; also it is checked by handleError(), however there it merely controls whether to print the warning or not (with the recursion corner case caveat). The default value of the tunable is True, which in the case of emit() means that the warning will be printed. Most of the handlers in logging.handlers heed the recommendation of calling handleError on exception in emit().

CircuitPython logging does not have this sort of support for handlers, even though individual handlers can surely implement exception handling of their own.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment