diff options
Diffstat (limited to 'tests/topotests/munet/mulog.py')
-rw-r--r-- | tests/topotests/munet/mulog.py | 122 |
1 files changed, 122 insertions, 0 deletions
diff --git a/tests/topotests/munet/mulog.py b/tests/topotests/munet/mulog.py new file mode 100644 index 0000000..f840eae --- /dev/null +++ b/tests/topotests/munet/mulog.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 eval: (blacken-mode 1) -*- +# SPDX-License-Identifier: GPL-2.0-or-later +# +# December 4 2022, Christian Hopps <chopps@labn.net> +# +# Copyright (c) 2022, LabN Consulting, L.L.C. +# +"""Utilities for logging in munet.""" + +import logging + +from pathlib import Path + + +class MultiFileHandler(logging.FileHandler): + """A logging handler that logs to new files based on the logger name. + + The MultiFileHandler operates as a FileHandler with additional functionality. In + addition to logging to the specified logging file MultiFileHandler also creates new + FileHandlers for child loggers based on a root logging name path. + + The ``root_path`` determines when to create a new FileHandler. For each received log + record, ``root_path`` is removed from the logger name of the record if present, and + the resulting channel path (if any) determines the directory for a new log file to + also emit the record to. The new file path is constructed by starting with the + directory ``filename`` resides in, then joining the path determined above after + converting "." to "/" and finally by adding back the basename of ``filename``. + + record logger path => mutest.output.testingfoo + root_path => mutest.output + base filename => /tmp/mutest/mutest-exec.log + new logfile => /tmp/mutest/testingfoo/mutest-exec.log + + All messages are also emitted to the common FileLogger for ``filename``. + + If a log record is from a logger that does not start with ``root_path`` no file is + created and the normal emit occurs. + + Args: + root_path: the logging path of the root level for this handler. + new_handler_level: logging level for newly created handlers + log_dir: the log directory to put log files in. + filename: the base log file. + """ + + def __init__(self, root_path, filename=None, **kwargs): + self.__root_path = root_path + self.__basename = Path(filename).name + if root_path[-1] != ".": + self.__root_path += "." + self.__root_pathlen = len(self.__root_path) + self.__kwargs = kwargs + self.__log_dir = Path(filename).absolute().parent + self.__log_dir.mkdir(parents=True, exist_ok=True) + self.__filenames = {} + self.__added = set() + + if "new_handler_level" not in kwargs: + self.__new_handler_level = logging.NOTSET + else: + new_handler_level = kwargs["new_handler_level"] + del kwargs["new_handler_level"] + self.__new_handler_level = new_handler_level + + super().__init__(filename=filename, **kwargs) + + if self.__new_handler_level is None: + self.__new_handler_level = self.level + + def __log_filename(self, name): + if name in self.__filenames: + return self.__filenames[name] + + if not name.startswith(self.__root_path): + newname = None + else: + newname = name[self.__root_pathlen :] + newname = Path(newname.replace(".", "/")) + newname = self.__log_dir.joinpath(newname) + newname = newname.joinpath(self.__basename) + self.__filenames[name] = newname + + self.__filenames[name] = newname + return newname + + def emit(self, record): + newname = self.__log_filename(record.name) + if newname: + if newname not in self.__added: + self.__added.add(newname) + h = logging.FileHandler(filename=newname, **self.__kwargs) + h.setLevel(self.__new_handler_level) + h.setFormatter(self.formatter) + logging.getLogger(record.name).addHandler(h) + h.emit(record) + super().emit(record) + + +class ColorFormatter(logging.Formatter): + """A formatter that adds color sequences based on level.""" + + def __init__(self, fmt=None, datefmt=None, style="%", **kwargs): + grey = "\x1b[90m" + yellow = "\x1b[33m" + red = "\x1b[31m" + bold_red = "\x1b[31;1m" + reset = "\x1b[0m" + # basefmt = " ------| %(message)s " + + self.formatters = { + logging.DEBUG: logging.Formatter(grey + fmt + reset), + logging.INFO: logging.Formatter(grey + fmt + reset), + logging.WARNING: logging.Formatter(yellow + fmt + reset), + logging.ERROR: logging.Formatter(red + fmt + reset), + logging.CRITICAL: logging.Formatter(bold_red + fmt + reset), + } + # Why are we even bothering? + super().__init__(fmt, datefmt, style, **kwargs) + + def format(self, record): + formatter = self.formatters.get(record.levelno) + return formatter.format(record) |