xref: /dpdk/dts/framework/logger.py (revision d029f35384d0844e9aeb5dbc46fbe1b063d649f7)
1# SPDX-License-Identifier: BSD-3-Clause
2# Copyright(c) 2010-2014 Intel Corporation
3# Copyright(c) 2022-2023 PANTHEON.tech s.r.o.
4# Copyright(c) 2022-2023 University of New Hampshire
5
6"""DTS logger module.
7
8DTS framework and TestSuite logs are saved in different log files.
9"""
10
11import logging
12import os.path
13from typing import TypedDict
14
15from .settings import SETTINGS
16
17date_fmt = "%Y/%m/%d %H:%M:%S"
18stream_fmt = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
19
20
21class DTSLOG(logging.LoggerAdapter):
22    """DTS logger adapter class for framework and testsuites.
23
24    The :option:`--verbose` command line argument and the :envvar:`DTS_VERBOSE` environment
25    variable control the verbosity of output. If enabled, all messages will be emitted to the
26    console.
27
28    The :option:`--output` command line argument and the :envvar:`DTS_OUTPUT_DIR` environment
29    variable modify the directory where the logs will be stored.
30
31    Attributes:
32        node: The additional identifier. Currently unused.
33        sh: The handler which emits logs to console.
34        fh: The handler which emits logs to a file.
35        verbose_fh: Just as fh, but logs with a different, more verbose, format.
36    """
37
38    _logger: logging.Logger
39    node: str
40    sh: logging.StreamHandler
41    fh: logging.FileHandler
42    verbose_fh: logging.FileHandler
43
44    def __init__(self, logger: logging.Logger, node: str = "suite"):
45        """Extend the constructor with additional handlers.
46
47        One handler logs to the console, the other one to a file, with either a regular or verbose
48        format.
49
50        Args:
51            logger: The logger from which to create the logger adapter.
52            node: An additional identifier. Currently unused.
53        """
54        self._logger = logger
55        # 1 means log everything, this will be used by file handlers if their level
56        # is not set
57        self._logger.setLevel(1)
58
59        self.node = node
60
61        # add handler to emit to stdout
62        sh = logging.StreamHandler()
63        sh.setFormatter(logging.Formatter(stream_fmt, date_fmt))
64        sh.setLevel(logging.INFO)  # console handler default level
65
66        if SETTINGS.verbose is True:
67            sh.setLevel(logging.DEBUG)
68
69        self._logger.addHandler(sh)
70        self.sh = sh
71
72        # prepare the output folder
73        if not os.path.exists(SETTINGS.output_dir):
74            os.mkdir(SETTINGS.output_dir)
75
76        logging_path_prefix = os.path.join(SETTINGS.output_dir, node)
77
78        fh = logging.FileHandler(f"{logging_path_prefix}.log")
79        fh.setFormatter(
80            logging.Formatter(
81                fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
82                datefmt=date_fmt,
83            )
84        )
85
86        self._logger.addHandler(fh)
87        self.fh = fh
88
89        # This outputs EVERYTHING, intended for post-mortem debugging
90        # Also optimized for processing via AWK (awk -F '|' ...)
91        verbose_fh = logging.FileHandler(f"{logging_path_prefix}.verbose.log")
92        verbose_fh.setFormatter(
93            logging.Formatter(
94                fmt="%(asctime)s|%(name)s|%(levelname)s|%(pathname)s|%(lineno)d|"
95                "%(funcName)s|%(process)d|%(thread)d|%(threadName)s|%(message)s",
96                datefmt=date_fmt,
97            )
98        )
99
100        self._logger.addHandler(verbose_fh)
101        self.verbose_fh = verbose_fh
102
103        super(DTSLOG, self).__init__(self._logger, dict(node=self.node))
104
105    def logger_exit(self) -> None:
106        """Remove the stream handler and the logfile handler."""
107        for handler in (self.sh, self.fh, self.verbose_fh):
108            handler.flush()
109            self._logger.removeHandler(handler)
110
111
112class _LoggerDictType(TypedDict):
113    logger: DTSLOG
114    name: str
115    node: str
116
117
118# List for saving all loggers in use
119_Loggers: list[_LoggerDictType] = []
120
121
122def getLogger(name: str, node: str = "suite") -> DTSLOG:
123    """Get DTS logger adapter identified by name and node.
124
125    An existing logger will be returned if one with the exact name and node already exists.
126    A new one will be created and stored otherwise.
127
128    Args:
129        name: The name of the logger.
130        node: An additional identifier for the logger.
131
132    Returns:
133        A logger uniquely identified by both name and node.
134    """
135    global _Loggers
136    # return saved logger
137    logger: _LoggerDictType
138    for logger in _Loggers:
139        if logger["name"] == name and logger["node"] == node:
140            return logger["logger"]
141
142    # return new logger
143    dts_logger: DTSLOG = DTSLOG(logging.getLogger(name), node)
144    _Loggers.append({"logger": dts_logger, "name": name, "node": node})
145    return dts_logger
146