Source code for message_ix_models.util.logging

"""Logging utilities."""

import logging
import logging.config
from contextlib import contextmanager
from copy import deepcopy
from time import process_time

__all__ = [
    "Formatter",
    "make_formatter",
    "setup",
    "silence_log",
]


[docs]@contextmanager def silence_log(): """Context manager to temporarily silence log output. Examples -------- >>> with silence_log(): >>> log.warning("This message is not recorded.") """ # Get the top-level logger for the package containing this file main_log = logging.getLogger(__name__.split(".")[0]) try: level = main_log.getEffectiveLevel() main_log.setLevel(100) yield finally: main_log.setLevel(level)
[docs]class Formatter(logging.Formatter): """Formatter for log records. Parameters ---------- colorama : module If provided, :mod:`colorama` is used to colour log messages printed to stdout. """ CYAN = "" DIM = "" RESET_ALL = "" _short_name = None def __init__(self, colorama): super().__init__() if colorama: self.CYAN = colorama.Fore.CYAN self.DIM = colorama.Style.DIM self.RESET_ALL = colorama.Style.RESET_ALL
[docs] def format(self, record): """Format `record`. Records are formatted like:: model.transport.data.add_par_data 220 rows in 'input' ...add_par_data: further messages …with the calling function name (e.g. 'add_par_data') coloured for legibility on first occurrence, then dimmed when repeated. """ # Remove the leading 'message_data.' from the module name name_parts = record.name.split(".") if name_parts[0] in ("message_ix_models", "message_data"): short_name = ".".join(["—"] + name_parts[1:]) else: short_name = record.name if self._short_name != short_name: self._short_name = short_name prefix = f"{self.CYAN}{short_name}." else: prefix = f"{self.DIM}..." return f"{prefix}{record.funcName}{self.RESET_ALL} {record.getMessage()}"
[docs]def make_formatter(): """Return a :class:`Formatter` instance for the ``message_ix_models`` logger. See also -------- setup """ try: # Initialize colorama import colorama colorama.init() except ImportError: # pragma: no cover # Colorama not installed colorama = None return Formatter(colorama)
_TIMES = [] def mark_time(quiet=False): """Record and log (if `quiet` is :obj:`True`) a time mark.""" _TIMES.append(process_time()) if not quiet and len(_TIMES) > 1: logging.getLogger(__name__).info( f" +{_TIMES[-1] - _TIMES[-2]:.1f} = {_TIMES[-1]:.1f} seconds" ) CONFIG = dict( version=1, disable_existing_loggers=False, formatters=dict(simple={"()": "message_ix_models.util.logging.make_formatter"}), handlers=dict( console={ "class": "logging.StreamHandler", "level": "NOTSET", "formatter": "simple", "stream": "ext://sys.stdout", }, # commented: needs code in setup() to choose an appropriate file path # file_handler={ # "class": "logging.handlers.RotatingFileHandler", # "level": "DEBUG", # "formatter": "simple", # "backupCount": "100", # "delay": True, # }, ), loggers=dict( message_ix_models=dict( level="NOTSET", # propagate=False, # handlers=[], ), message_data=dict( level="NOTSET", # propagate=False, # handlers=[], ), ), root=dict( handlers=[], ), )
[docs]def setup( level="NOTSET", console=True, # file=False, ): """Initialize logging. Parameters ---------- level : str, optional Log level for :mod:`message_ix_models` and :mod:`message_data`. console : bool, optional If :obj:`True`, print all messages to console using a :class:`Formatter`. """ # Copy to avoid modifying with the operations below config = deepcopy(CONFIG) config["root"].setdefault("level", level) if console: config["root"]["handlers"].append("console") # if file: # config["loggers"]["message_data"]["handlers"].append("file") # Apply the configuration logging.config.dictConfig(config)