Source code for gain.utils.log_levels

"""Custom logging levels for the GAIn package.

TRACE (5): below DEBUG, for the finest-grained diagnostic output.
USER_INFO (25): between INFO and WARNING, for messages directed at end users.

Importing this module registers both level names and makes ``trace`` /
``user_info`` available on loggers, two ways:

- ``GainLogger`` becomes the class ``logging.getLogger`` instantiates, so
  every logger created afterwards carries the two methods as its own. A
  later assignment to ``logging.Logger.trace`` by another package (bokeh
  does this on import, with a different level number and no ``stacklevel``
  handling) does not reach them: the subclass attribute shadows it.
- The same functions are also assigned onto ``logging.Logger`` itself, so
  a logger created before this module was imported -- the root logger,
  or a third-party module logger -- has them too, until such a foreign
  assignment replaces them.

Two things this module does not own:

- The *name* -> level direction of the stdlib tables.
  ``getLevelName(5) == "TRACE"`` holds for the life of the process, but if
  another package registers the name ``TRACE`` for a different number,
  ``getLevelName("TRACE")`` and ``getLevelNamesMapping()["TRACE"]`` follow
  the later registration. Resolve gain's levels through ``TRACE`` /
  ``USER_INFO`` here, never by name.
- The logger class of a host that called ``logging.setLoggerClass`` before
  importing gain: the registration here replaces it, so loggers created
  after the import are ``GainLogger`` and not the host's class.
"""
from __future__ import annotations

import logging
from typing import Any

TRACE = 5
USER_INFO = 25

logging.addLevelName(TRACE, "TRACE")
logging.addLevelName(USER_INFO, "USER_INFO")


[docs] class GainLogger(logging.Logger): """A ``Logger`` whose ``trace`` and ``user_info`` are its own methods."""
[docs] def trace( self, msg: object, *args: object, **kwargs: Any, ) -> None: """Log ``msg`` at TRACE, attributed to the caller.""" if self.isEnabledFor(TRACE): self._log( TRACE, msg, args, stacklevel=kwargs.pop("stacklevel", 1) + 1, **kwargs)
[docs] def user_info( self, msg: object, *args: object, **kwargs: Any, ) -> None: """Log ``msg`` at USER_INFO, attributed to the caller.""" if self.isEnabledFor(USER_INFO): self._log( USER_INFO, msg, args, stacklevel=kwargs.pop("stacklevel", 1) + 1, **kwargs)
logging.setLoggerClass(GainLogger) # The same methods for loggers that predate this import. logging.Logger.trace = GainLogger.trace # type: ignore[attr-defined] logging.Logger.user_info = GainLogger.user_info # type: ignore[attr-defined]