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