1179d7059SJuraj Linkeš# SPDX-License-Identifier: BSD-3-Clause 2179d7059SJuraj Linkeš# Copyright(c) 2010-2014 Intel Corporation 378534506SJuraj Linkeš# Copyright(c) 2022-2023 PANTHEON.tech s.r.o. 478534506SJuraj Linkeš# Copyright(c) 2022-2023 University of New Hampshire 5179d7059SJuraj Linkeš 66ef07151SJuraj Linkeš"""DTS logger module. 76ef07151SJuraj Linkeš 804f5a5a6SJuraj LinkešThe module provides several additional features: 904f5a5a6SJuraj Linkeš 1004f5a5a6SJuraj Linkeš * The storage of DTS execution stages, 1104f5a5a6SJuraj Linkeš * Logging to console, a human-readable log file and a machine-readable log file, 1204f5a5a6SJuraj Linkeš * Optional log files for specific stages. 13179d7059SJuraj Linkeš""" 14179d7059SJuraj Linkeš 15179d7059SJuraj Linkešimport logging 1604f5a5a6SJuraj Linkešfrom enum import auto 1704f5a5a6SJuraj Linkešfrom logging import FileHandler, StreamHandler 1804f5a5a6SJuraj Linkešfrom pathlib import Path 1904f5a5a6SJuraj Linkešfrom typing import ClassVar 20179d7059SJuraj Linkeš 2104f5a5a6SJuraj Linkešfrom .utils import StrEnum 22179d7059SJuraj Linkeš 23179d7059SJuraj Linkešdate_fmt = "%Y/%m/%d %H:%M:%S" 2404f5a5a6SJuraj Linkešstream_fmt = "%(asctime)s - %(stage)s - %(name)s - %(levelname)s - %(message)s" 2504f5a5a6SJuraj Linkešdts_root_logger_name = "dts" 26179d7059SJuraj Linkeš 27179d7059SJuraj Linkeš 2804f5a5a6SJuraj Linkešclass DtsStage(StrEnum): 2904f5a5a6SJuraj Linkeš """The DTS execution stage.""" 306ef07151SJuraj Linkeš 3104f5a5a6SJuraj Linkeš #: 32*85ceeeceSJuraj Linkeš pre_run = auto() 3304f5a5a6SJuraj Linkeš #: 34*85ceeeceSJuraj Linkeš test_run_setup = auto() 3504f5a5a6SJuraj Linkeš #: 3604f5a5a6SJuraj Linkeš test_suite_setup = auto() 3704f5a5a6SJuraj Linkeš #: 3804f5a5a6SJuraj Linkeš test_suite = auto() 3904f5a5a6SJuraj Linkeš #: 4004f5a5a6SJuraj Linkeš test_suite_teardown = auto() 4104f5a5a6SJuraj Linkeš #: 42*85ceeeceSJuraj Linkeš test_run_teardown = auto() 43*85ceeeceSJuraj Linkeš #: 44*85ceeeceSJuraj Linkeš post_run = auto() 456ef07151SJuraj Linkeš 466ef07151SJuraj Linkeš 4704f5a5a6SJuraj Linkešclass DTSLogger(logging.Logger): 4804f5a5a6SJuraj Linkeš """The DTS logger class. 4904f5a5a6SJuraj Linkeš 5004f5a5a6SJuraj Linkeš The class extends the :class:`~logging.Logger` class to add the DTS execution stage information 5104f5a5a6SJuraj Linkeš to log records. The stage is common to all loggers, so it's stored in a class variable. 5204f5a5a6SJuraj Linkeš 5304f5a5a6SJuraj Linkeš Any time we switch to a new stage, we have the ability to log to an additional log file along 5404f5a5a6SJuraj Linkeš with a supplementary log file with machine-readable format. These two log files are used until 5504f5a5a6SJuraj Linkeš a new stage switch occurs. This is useful mainly for logging per test suite. 56179d7059SJuraj Linkeš """ 57179d7059SJuraj Linkeš 58*85ceeeceSJuraj Linkeš _stage: ClassVar[DtsStage] = DtsStage.pre_run 5904f5a5a6SJuraj Linkeš _extra_file_handlers: list[FileHandler] = [] 60179d7059SJuraj Linkeš 6104f5a5a6SJuraj Linkeš def __init__(self, *args, **kwargs): 6204f5a5a6SJuraj Linkeš """Extend the constructor with extra file handlers.""" 6304f5a5a6SJuraj Linkeš self._extra_file_handlers = [] 6404f5a5a6SJuraj Linkeš super().__init__(*args, **kwargs) 656ef07151SJuraj Linkeš 6604f5a5a6SJuraj Linkeš def makeRecord(self, *args, **kwargs) -> logging.LogRecord: 6704f5a5a6SJuraj Linkeš """Generates a record with additional stage information. 6804f5a5a6SJuraj Linkeš 6904f5a5a6SJuraj Linkeš This is the default method for the :class:`~logging.Logger` class. We extend it 7004f5a5a6SJuraj Linkeš to add stage information to the record. 7104f5a5a6SJuraj Linkeš 7204f5a5a6SJuraj Linkeš :meta private: 7304f5a5a6SJuraj Linkeš 7404f5a5a6SJuraj Linkeš Returns: 7504f5a5a6SJuraj Linkeš record: The generated record with the stage information. 7604f5a5a6SJuraj Linkeš """ 7704f5a5a6SJuraj Linkeš record = super().makeRecord(*args, **kwargs) 78282688eaSLuca Vizzarro record.stage = DTSLogger._stage 7904f5a5a6SJuraj Linkeš return record 8004f5a5a6SJuraj Linkeš 8104f5a5a6SJuraj Linkeš def add_dts_root_logger_handlers(self, verbose: bool, output_dir: str) -> None: 8204f5a5a6SJuraj Linkeš """Add logger handlers to the DTS root logger. 8304f5a5a6SJuraj Linkeš 8404f5a5a6SJuraj Linkeš This method should be called only on the DTS root logger. 8504f5a5a6SJuraj Linkeš The log records from child loggers will propagate to these handlers. 8604f5a5a6SJuraj Linkeš 8704f5a5a6SJuraj Linkeš Three handlers are added: 8804f5a5a6SJuraj Linkeš 8904f5a5a6SJuraj Linkeš * A console handler, 9004f5a5a6SJuraj Linkeš * A file handler, 9104f5a5a6SJuraj Linkeš * A supplementary file handler with machine-readable logs 9204f5a5a6SJuraj Linkeš containing more debug information. 9304f5a5a6SJuraj Linkeš 9404f5a5a6SJuraj Linkeš All log messages will be logged to files. The log level of the console handler 9504f5a5a6SJuraj Linkeš is configurable with `verbose`. 966ef07151SJuraj Linkeš 976ef07151SJuraj Linkeš Args: 9804f5a5a6SJuraj Linkeš verbose: If :data:`True`, log all messages to the console. 9904f5a5a6SJuraj Linkeš If :data:`False`, log to console with the :data:`logging.INFO` level. 10004f5a5a6SJuraj Linkeš output_dir: The directory where the log files will be located. 10104f5a5a6SJuraj Linkeš The names of the log files correspond to the name of the logger instance. 1026ef07151SJuraj Linkeš """ 10304f5a5a6SJuraj Linkeš self.setLevel(1) 104179d7059SJuraj Linkeš 10504f5a5a6SJuraj Linkeš sh = StreamHandler() 106179d7059SJuraj Linkeš sh.setFormatter(logging.Formatter(stream_fmt, date_fmt)) 10704f5a5a6SJuraj Linkeš if not verbose: 10804f5a5a6SJuraj Linkeš sh.setLevel(logging.INFO) 10904f5a5a6SJuraj Linkeš self.addHandler(sh) 110179d7059SJuraj Linkeš 11104f5a5a6SJuraj Linkeš self._add_file_handlers(Path(output_dir, self.name)) 112179d7059SJuraj Linkeš 11304f5a5a6SJuraj Linkeš def set_stage(self, stage: DtsStage, log_file_path: Path | None = None) -> None: 11404f5a5a6SJuraj Linkeš """Set the DTS execution stage and optionally log to files. 115179d7059SJuraj Linkeš 11604f5a5a6SJuraj Linkeš Set the DTS execution stage of the DTSLog class and optionally add 11704f5a5a6SJuraj Linkeš file handlers to the instance if the log file name is provided. 11878534506SJuraj Linkeš 11904f5a5a6SJuraj Linkeš The file handlers log all messages. One is a regular human-readable log file and 12004f5a5a6SJuraj Linkeš the other one is a machine-readable log file with extra debug information. 121179d7059SJuraj Linkeš 12204f5a5a6SJuraj Linkeš Args: 12304f5a5a6SJuraj Linkeš stage: The DTS stage to set. 12404f5a5a6SJuraj Linkeš log_file_path: An optional path of the log file to use. This should be a full path 12504f5a5a6SJuraj Linkeš (either relative or absolute) without suffix (which will be appended). 12604f5a5a6SJuraj Linkeš """ 12704f5a5a6SJuraj Linkeš self._remove_extra_file_handlers() 128179d7059SJuraj Linkeš 12904f5a5a6SJuraj Linkeš if DTSLogger._stage != stage: 13004f5a5a6SJuraj Linkeš self.info(f"Moving from stage '{DTSLogger._stage}' to stage '{stage}'.") 13104f5a5a6SJuraj Linkeš DTSLogger._stage = stage 132179d7059SJuraj Linkeš 13304f5a5a6SJuraj Linkeš if log_file_path: 13404f5a5a6SJuraj Linkeš self._extra_file_handlers.extend(self._add_file_handlers(log_file_path)) 13504f5a5a6SJuraj Linkeš 13604f5a5a6SJuraj Linkeš def _add_file_handlers(self, log_file_path: Path) -> list[FileHandler]: 13704f5a5a6SJuraj Linkeš """Add file handlers to the DTS root logger. 13804f5a5a6SJuraj Linkeš 13904f5a5a6SJuraj Linkeš Add two type of file handlers: 14004f5a5a6SJuraj Linkeš 14104f5a5a6SJuraj Linkeš * A regular file handler with suffix ".log", 14204f5a5a6SJuraj Linkeš * A machine-readable file handler with suffix ".verbose.log". 14304f5a5a6SJuraj Linkeš This format provides extensive information for debugging and detailed analysis. 14404f5a5a6SJuraj Linkeš 14504f5a5a6SJuraj Linkeš Args: 14604f5a5a6SJuraj Linkeš log_file_path: The full path to the log file without suffix. 14704f5a5a6SJuraj Linkeš 14804f5a5a6SJuraj Linkeš Returns: 14904f5a5a6SJuraj Linkeš The newly created file handlers. 15004f5a5a6SJuraj Linkeš 15104f5a5a6SJuraj Linkeš """ 15204f5a5a6SJuraj Linkeš fh = FileHandler(f"{log_file_path}.log") 15304f5a5a6SJuraj Linkeš fh.setFormatter(logging.Formatter(stream_fmt, date_fmt)) 15404f5a5a6SJuraj Linkeš self.addHandler(fh) 15504f5a5a6SJuraj Linkeš 15604f5a5a6SJuraj Linkeš verbose_fh = FileHandler(f"{log_file_path}.verbose.log") 157179d7059SJuraj Linkeš verbose_fh.setFormatter( 158179d7059SJuraj Linkeš logging.Formatter( 15904f5a5a6SJuraj Linkeš "%(asctime)s|%(stage)s|%(name)s|%(levelname)s|%(pathname)s|%(lineno)d|" 160179d7059SJuraj Linkeš "%(funcName)s|%(process)d|%(thread)d|%(threadName)s|%(message)s", 161179d7059SJuraj Linkeš datefmt=date_fmt, 162179d7059SJuraj Linkeš ) 163179d7059SJuraj Linkeš ) 16404f5a5a6SJuraj Linkeš self.addHandler(verbose_fh) 165179d7059SJuraj Linkeš 16604f5a5a6SJuraj Linkeš return [fh, verbose_fh] 167179d7059SJuraj Linkeš 16804f5a5a6SJuraj Linkeš def _remove_extra_file_handlers(self) -> None: 16904f5a5a6SJuraj Linkeš """Remove any extra file handlers that have been added to the logger.""" 17004f5a5a6SJuraj Linkeš if self._extra_file_handlers: 17104f5a5a6SJuraj Linkeš for extra_file_handler in self._extra_file_handlers: 17204f5a5a6SJuraj Linkeš self.removeHandler(extra_file_handler) 173179d7059SJuraj Linkeš 17404f5a5a6SJuraj Linkeš self._extra_file_handlers = [] 175179d7059SJuraj Linkeš 176179d7059SJuraj Linkeš 177282688eaSLuca Vizzarrodef get_dts_logger(name: str | None = None) -> DTSLogger: 17804f5a5a6SJuraj Linkeš """Return a DTS logger instance identified by `name`. 1796ef07151SJuraj Linkeš 1806ef07151SJuraj Linkeš Args: 18104f5a5a6SJuraj Linkeš name: If :data:`None`, return the DTS root logger. 18204f5a5a6SJuraj Linkeš If specified, return a child of the DTS root logger. 1836ef07151SJuraj Linkeš 1846ef07151SJuraj Linkeš Returns: 18504f5a5a6SJuraj Linkeš The DTS root logger or a child logger identified by `name`. 186179d7059SJuraj Linkeš """ 18704f5a5a6SJuraj Linkeš original_logger_class = logging.getLoggerClass() 18804f5a5a6SJuraj Linkeš logging.setLoggerClass(DTSLogger) 18904f5a5a6SJuraj Linkeš if name: 19004f5a5a6SJuraj Linkeš name = f"{dts_root_logger_name}.{name}" 19104f5a5a6SJuraj Linkeš else: 19204f5a5a6SJuraj Linkeš name = dts_root_logger_name 19304f5a5a6SJuraj Linkeš logger = logging.getLogger(name) 19404f5a5a6SJuraj Linkeš logging.setLoggerClass(original_logger_class) 19504f5a5a6SJuraj Linkeš return logger # type: ignore[return-value] 196