xref: /dpdk/dts/framework/logger.py (revision 04f5a5a6e48a06d86ccc5236452514bfbb0b6de7)
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š
8*04f5a5a6SJuraj LinkešThe module provides several additional features:
9*04f5a5a6SJuraj Linkeš
10*04f5a5a6SJuraj Linkeš    * The storage of DTS execution stages,
11*04f5a5a6SJuraj Linkeš    * Logging to console, a human-readable log file and a machine-readable log file,
12*04f5a5a6SJuraj Linkeš    * Optional log files for specific stages.
13179d7059SJuraj Linkeš"""
14179d7059SJuraj Linkeš
15179d7059SJuraj Linkešimport logging
16*04f5a5a6SJuraj Linkešfrom enum import auto
17*04f5a5a6SJuraj Linkešfrom logging import FileHandler, StreamHandler
18*04f5a5a6SJuraj Linkešfrom pathlib import Path
19*04f5a5a6SJuraj Linkešfrom typing import ClassVar
20179d7059SJuraj Linkeš
21*04f5a5a6SJuraj Linkešfrom .utils import StrEnum
22179d7059SJuraj Linkeš
23179d7059SJuraj Linkešdate_fmt = "%Y/%m/%d %H:%M:%S"
24*04f5a5a6SJuraj Linkešstream_fmt = "%(asctime)s - %(stage)s - %(name)s - %(levelname)s - %(message)s"
25*04f5a5a6SJuraj Linkešdts_root_logger_name = "dts"
26179d7059SJuraj Linkeš
27179d7059SJuraj Linkeš
28*04f5a5a6SJuraj Linkešclass DtsStage(StrEnum):
29*04f5a5a6SJuraj Linkeš    """The DTS execution stage."""
306ef07151SJuraj Linkeš
31*04f5a5a6SJuraj Linkeš    #:
32*04f5a5a6SJuraj Linkeš    pre_execution = auto()
33*04f5a5a6SJuraj Linkeš    #:
34*04f5a5a6SJuraj Linkeš    execution_setup = auto()
35*04f5a5a6SJuraj Linkeš    #:
36*04f5a5a6SJuraj Linkeš    execution_teardown = auto()
37*04f5a5a6SJuraj Linkeš    #:
38*04f5a5a6SJuraj Linkeš    build_target_setup = auto()
39*04f5a5a6SJuraj Linkeš    #:
40*04f5a5a6SJuraj Linkeš    build_target_teardown = auto()
41*04f5a5a6SJuraj Linkeš    #:
42*04f5a5a6SJuraj Linkeš    test_suite_setup = auto()
43*04f5a5a6SJuraj Linkeš    #:
44*04f5a5a6SJuraj Linkeš    test_suite = auto()
45*04f5a5a6SJuraj Linkeš    #:
46*04f5a5a6SJuraj Linkeš    test_suite_teardown = auto()
47*04f5a5a6SJuraj Linkeš    #:
48*04f5a5a6SJuraj Linkeš    post_execution = auto()
496ef07151SJuraj Linkeš
506ef07151SJuraj Linkeš
51*04f5a5a6SJuraj Linkešclass DTSLogger(logging.Logger):
52*04f5a5a6SJuraj Linkeš    """The DTS logger class.
53*04f5a5a6SJuraj Linkeš
54*04f5a5a6SJuraj Linkeš    The class extends the :class:`~logging.Logger` class to add the DTS execution stage information
55*04f5a5a6SJuraj Linkeš    to log records. The stage is common to all loggers, so it's stored in a class variable.
56*04f5a5a6SJuraj Linkeš
57*04f5a5a6SJuraj Linkeš    Any time we switch to a new stage, we have the ability to log to an additional log file along
58*04f5a5a6SJuraj Linkeš    with a supplementary log file with machine-readable format. These two log files are used until
59*04f5a5a6SJuraj Linkeš    a new stage switch occurs. This is useful mainly for logging per test suite.
60179d7059SJuraj Linkeš    """
61179d7059SJuraj Linkeš
62*04f5a5a6SJuraj Linkeš    _stage: ClassVar[DtsStage] = DtsStage.pre_execution
63*04f5a5a6SJuraj Linkeš    _extra_file_handlers: list[FileHandler] = []
64179d7059SJuraj Linkeš
65*04f5a5a6SJuraj Linkeš    def __init__(self, *args, **kwargs):
66*04f5a5a6SJuraj Linkeš        """Extend the constructor with extra file handlers."""
67*04f5a5a6SJuraj Linkeš        self._extra_file_handlers = []
68*04f5a5a6SJuraj Linkeš        super().__init__(*args, **kwargs)
696ef07151SJuraj Linkeš
70*04f5a5a6SJuraj Linkeš    def makeRecord(self, *args, **kwargs) -> logging.LogRecord:
71*04f5a5a6SJuraj Linkeš        """Generates a record with additional stage information.
72*04f5a5a6SJuraj Linkeš
73*04f5a5a6SJuraj Linkeš        This is the default method for the :class:`~logging.Logger` class. We extend it
74*04f5a5a6SJuraj Linkeš        to add stage information to the record.
75*04f5a5a6SJuraj Linkeš
76*04f5a5a6SJuraj Linkeš        :meta private:
77*04f5a5a6SJuraj Linkeš
78*04f5a5a6SJuraj Linkeš        Returns:
79*04f5a5a6SJuraj Linkeš            record: The generated record with the stage information.
80*04f5a5a6SJuraj Linkeš        """
81*04f5a5a6SJuraj Linkeš        record = super().makeRecord(*args, **kwargs)
82*04f5a5a6SJuraj Linkeš        record.stage = DTSLogger._stage  # type: ignore[attr-defined]
83*04f5a5a6SJuraj Linkeš        return record
84*04f5a5a6SJuraj Linkeš
85*04f5a5a6SJuraj Linkeš    def add_dts_root_logger_handlers(self, verbose: bool, output_dir: str) -> None:
86*04f5a5a6SJuraj Linkeš        """Add logger handlers to the DTS root logger.
87*04f5a5a6SJuraj Linkeš
88*04f5a5a6SJuraj Linkeš        This method should be called only on the DTS root logger.
89*04f5a5a6SJuraj Linkeš        The log records from child loggers will propagate to these handlers.
90*04f5a5a6SJuraj Linkeš
91*04f5a5a6SJuraj Linkeš        Three handlers are added:
92*04f5a5a6SJuraj Linkeš
93*04f5a5a6SJuraj Linkeš            * A console handler,
94*04f5a5a6SJuraj Linkeš            * A file handler,
95*04f5a5a6SJuraj Linkeš            * A supplementary file handler with machine-readable logs
96*04f5a5a6SJuraj Linkeš              containing more debug information.
97*04f5a5a6SJuraj Linkeš
98*04f5a5a6SJuraj Linkeš        All log messages will be logged to files. The log level of the console handler
99*04f5a5a6SJuraj Linkeš        is configurable with `verbose`.
1006ef07151SJuraj Linkeš
1016ef07151SJuraj Linkeš        Args:
102*04f5a5a6SJuraj Linkeš            verbose: If :data:`True`, log all messages to the console.
103*04f5a5a6SJuraj Linkeš                If :data:`False`, log to console with the :data:`logging.INFO` level.
104*04f5a5a6SJuraj Linkeš            output_dir: The directory where the log files will be located.
105*04f5a5a6SJuraj Linkeš                The names of the log files correspond to the name of the logger instance.
1066ef07151SJuraj Linkeš        """
107*04f5a5a6SJuraj Linkeš        self.setLevel(1)
108179d7059SJuraj Linkeš
109*04f5a5a6SJuraj Linkeš        sh = StreamHandler()
110179d7059SJuraj Linkeš        sh.setFormatter(logging.Formatter(stream_fmt, date_fmt))
111*04f5a5a6SJuraj Linkeš        if not verbose:
112*04f5a5a6SJuraj Linkeš            sh.setLevel(logging.INFO)
113*04f5a5a6SJuraj Linkeš        self.addHandler(sh)
114179d7059SJuraj Linkeš
115*04f5a5a6SJuraj Linkeš        self._add_file_handlers(Path(output_dir, self.name))
116179d7059SJuraj Linkeš
117*04f5a5a6SJuraj Linkeš    def set_stage(self, stage: DtsStage, log_file_path: Path | None = None) -> None:
118*04f5a5a6SJuraj Linkeš        """Set the DTS execution stage and optionally log to files.
119179d7059SJuraj Linkeš
120*04f5a5a6SJuraj Linkeš        Set the DTS execution stage of the DTSLog class and optionally add
121*04f5a5a6SJuraj Linkeš        file handlers to the instance if the log file name is provided.
12278534506SJuraj Linkeš
123*04f5a5a6SJuraj Linkeš        The file handlers log all messages. One is a regular human-readable log file and
124*04f5a5a6SJuraj Linkeš        the other one is a machine-readable log file with extra debug information.
125179d7059SJuraj Linkeš
126*04f5a5a6SJuraj Linkeš        Args:
127*04f5a5a6SJuraj Linkeš            stage: The DTS stage to set.
128*04f5a5a6SJuraj Linkeš            log_file_path: An optional path of the log file to use. This should be a full path
129*04f5a5a6SJuraj Linkeš                (either relative or absolute) without suffix (which will be appended).
130*04f5a5a6SJuraj Linkeš        """
131*04f5a5a6SJuraj Linkeš        self._remove_extra_file_handlers()
132179d7059SJuraj Linkeš
133*04f5a5a6SJuraj Linkeš        if DTSLogger._stage != stage:
134*04f5a5a6SJuraj Linkeš            self.info(f"Moving from stage '{DTSLogger._stage}' to stage '{stage}'.")
135*04f5a5a6SJuraj Linkeš            DTSLogger._stage = stage
136179d7059SJuraj Linkeš
137*04f5a5a6SJuraj Linkeš        if log_file_path:
138*04f5a5a6SJuraj Linkeš            self._extra_file_handlers.extend(self._add_file_handlers(log_file_path))
139*04f5a5a6SJuraj Linkeš
140*04f5a5a6SJuraj Linkeš    def _add_file_handlers(self, log_file_path: Path) -> list[FileHandler]:
141*04f5a5a6SJuraj Linkeš        """Add file handlers to the DTS root logger.
142*04f5a5a6SJuraj Linkeš
143*04f5a5a6SJuraj Linkeš        Add two type of file handlers:
144*04f5a5a6SJuraj Linkeš
145*04f5a5a6SJuraj Linkeš            * A regular file handler with suffix ".log",
146*04f5a5a6SJuraj Linkeš            * A machine-readable file handler with suffix ".verbose.log".
147*04f5a5a6SJuraj Linkeš              This format provides extensive information for debugging and detailed analysis.
148*04f5a5a6SJuraj Linkeš
149*04f5a5a6SJuraj Linkeš        Args:
150*04f5a5a6SJuraj Linkeš            log_file_path: The full path to the log file without suffix.
151*04f5a5a6SJuraj Linkeš
152*04f5a5a6SJuraj Linkeš        Returns:
153*04f5a5a6SJuraj Linkeš            The newly created file handlers.
154*04f5a5a6SJuraj Linkeš
155*04f5a5a6SJuraj Linkeš        """
156*04f5a5a6SJuraj Linkeš        fh = FileHandler(f"{log_file_path}.log")
157*04f5a5a6SJuraj Linkeš        fh.setFormatter(logging.Formatter(stream_fmt, date_fmt))
158*04f5a5a6SJuraj Linkeš        self.addHandler(fh)
159*04f5a5a6SJuraj Linkeš
160*04f5a5a6SJuraj Linkeš        verbose_fh = FileHandler(f"{log_file_path}.verbose.log")
161179d7059SJuraj Linkeš        verbose_fh.setFormatter(
162179d7059SJuraj Linkeš            logging.Formatter(
163*04f5a5a6SJuraj Linkeš                "%(asctime)s|%(stage)s|%(name)s|%(levelname)s|%(pathname)s|%(lineno)d|"
164179d7059SJuraj Linkeš                "%(funcName)s|%(process)d|%(thread)d|%(threadName)s|%(message)s",
165179d7059SJuraj Linkeš                datefmt=date_fmt,
166179d7059SJuraj Linkeš            )
167179d7059SJuraj Linkeš        )
168*04f5a5a6SJuraj Linkeš        self.addHandler(verbose_fh)
169179d7059SJuraj Linkeš
170*04f5a5a6SJuraj Linkeš        return [fh, verbose_fh]
171179d7059SJuraj Linkeš
172*04f5a5a6SJuraj Linkeš    def _remove_extra_file_handlers(self) -> None:
173*04f5a5a6SJuraj Linkeš        """Remove any extra file handlers that have been added to the logger."""
174*04f5a5a6SJuraj Linkeš        if self._extra_file_handlers:
175*04f5a5a6SJuraj Linkeš            for extra_file_handler in self._extra_file_handlers:
176*04f5a5a6SJuraj Linkeš                self.removeHandler(extra_file_handler)
177179d7059SJuraj Linkeš
178*04f5a5a6SJuraj Linkeš            self._extra_file_handlers = []
179179d7059SJuraj Linkeš
180179d7059SJuraj Linkeš
181*04f5a5a6SJuraj Linkešdef get_dts_logger(name: str = None) -> DTSLogger:
182*04f5a5a6SJuraj Linkeš    """Return a DTS logger instance identified by `name`.
1836ef07151SJuraj Linkeš
1846ef07151SJuraj Linkeš    Args:
185*04f5a5a6SJuraj Linkeš        name: If :data:`None`, return the DTS root logger.
186*04f5a5a6SJuraj Linkeš            If specified, return a child of the DTS root logger.
1876ef07151SJuraj Linkeš
1886ef07151SJuraj Linkeš    Returns:
189*04f5a5a6SJuraj Linkeš         The DTS root logger or a child logger identified by `name`.
190179d7059SJuraj Linkeš    """
191*04f5a5a6SJuraj Linkeš    original_logger_class = logging.getLoggerClass()
192*04f5a5a6SJuraj Linkeš    logging.setLoggerClass(DTSLogger)
193*04f5a5a6SJuraj Linkeš    if name:
194*04f5a5a6SJuraj Linkeš        name = f"{dts_root_logger_name}.{name}"
195*04f5a5a6SJuraj Linkeš    else:
196*04f5a5a6SJuraj Linkeš        name = dts_root_logger_name
197*04f5a5a6SJuraj Linkeš    logger = logging.getLogger(name)
198*04f5a5a6SJuraj Linkeš    logging.setLoggerClass(original_logger_class)
199*04f5a5a6SJuraj Linkeš    return logger  # type: ignore[return-value]
200