xref: /dpdk/dts/framework/logger.py (revision 11b2279afbb5e628e9cff26b4b3fff4127711949)
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