xref: /aosp_15_r20/external/autotest/client/common_lib/logging_config.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Lint as: python2, python3
2*9c5db199SXin Lifrom __future__ import absolute_import
3*9c5db199SXin Lifrom __future__ import division
4*9c5db199SXin Lifrom __future__ import print_function
5*9c5db199SXin Li
6*9c5db199SXin Liimport logging
7*9c5db199SXin Liimport os
8*9c5db199SXin Liimport six
9*9c5db199SXin Liimport sys
10*9c5db199SXin Liimport time
11*9c5db199SXin Li
12*9c5db199SXin Lifrom autotest_lib.client.common_lib import utils
13*9c5db199SXin Li
14*9c5db199SXin Li# Set up a simple catchall configuration for use during import time.  Some code
15*9c5db199SXin Li# may log messages at import time and we don't want those to get completely
16*9c5db199SXin Li# thrown away.  We'll clear this out when actual configuration takes place.
17*9c5db199SXin Lilogging.basicConfig(level=logging.DEBUG)
18*9c5db199SXin Li
19*9c5db199SXin Li
20*9c5db199SXin Liclass AllowBelowSeverity(logging.Filter):
21*9c5db199SXin Li    """
22*9c5db199SXin Li    Allows only records less severe than a given level (the opposite of what
23*9c5db199SXin Li    the normal logging level filtering does.
24*9c5db199SXin Li    """
25*9c5db199SXin Li
26*9c5db199SXin Li    def __init__(self, level):
27*9c5db199SXin Li        self.level = level
28*9c5db199SXin Li
29*9c5db199SXin Li    def filter(self, record):
30*9c5db199SXin Li        return record.levelno < self.level
31*9c5db199SXin Li
32*9c5db199SXin Li
33*9c5db199SXin Liclass VarLogMessageFormatter(logging.Formatter):
34*9c5db199SXin Li    """
35*9c5db199SXin Li    Respews logging.* strings on the DUT to /var/log/messages for easier
36*9c5db199SXin Li    correlation of user mode test activity with kernel messages.
37*9c5db199SXin Li    """
38*9c5db199SXin Li    _should_respew = True
39*9c5db199SXin Li
40*9c5db199SXin Li    def format(self, record):
41*9c5db199SXin Li        s = super(VarLogMessageFormatter, self).format(record)
42*9c5db199SXin Li        # On Brillo the logger binary is not available. Disable after error.
43*9c5db199SXin Li        if self._should_respew:
44*9c5db199SXin Li            try:
45*9c5db199SXin Li                # in python2 unicode != string, so encode the record into a
46*9c5db199SXin Li                # string for logging. In py3 this is just not needed.
47*9c5db199SXin Li                # Additionally, encoding in py3 results in bytes, which breaks
48*9c5db199SXin Li                # logging.
49*9c5db199SXin Li                if six.PY2 and isinstance(s, unicode):
50*9c5db199SXin Li                    s = s.encode('utf8')
51*9c5db199SXin Li                os.system('logger -t "autotest" "%s"' % utils.sh_escape(s))
52*9c5db199SXin Li            except OSError:
53*9c5db199SXin Li                self._should_respew = False
54*9c5db199SXin Li        return s
55*9c5db199SXin Li
56*9c5db199SXin Li
57*9c5db199SXin Liclass LoggingConfig(object):
58*9c5db199SXin Li    global_level = logging.DEBUG
59*9c5db199SXin Li    stdout_level = logging.INFO
60*9c5db199SXin Li    stderr_level = logging.ERROR
61*9c5db199SXin Li
62*9c5db199SXin Li    FILE_FORMAT = ('%(asctime)s.%(msecs)03d %(levelname)-5.5s|%(module)18.18s:'
63*9c5db199SXin Li                   '%(lineno)4.4d| %(message)s')
64*9c5db199SXin Li    FILE_FORMAT_WITH_THREADNAME = (
65*9c5db199SXin Li            '%(asctime)s.%(msecs)03d %(levelname)-5.5s|%(module)18.18s:'
66*9c5db199SXin Li            '%(lineno)4.4d| %(threadName)16.16s| %(message)s')
67*9c5db199SXin Li    DATE_FORMAT = '%m/%d %H:%M:%S'
68*9c5db199SXin Li
69*9c5db199SXin Li    file_formatter = logging.Formatter(fmt=FILE_FORMAT, datefmt=DATE_FORMAT)
70*9c5db199SXin Li
71*9c5db199SXin Li    CONSOLE_FORMAT = '%(asctime)s %(levelname)-5.5s| %(message)s'
72*9c5db199SXin Li
73*9c5db199SXin Li    console_formatter = logging.Formatter(fmt=CONSOLE_FORMAT,
74*9c5db199SXin Li                                          datefmt='%H:%M:%S')
75*9c5db199SXin Li
76*9c5db199SXin Li    system_formatter = VarLogMessageFormatter(fmt=FILE_FORMAT,
77*9c5db199SXin Li                                              datefmt='%H:%M:%S')
78*9c5db199SXin Li
79*9c5db199SXin Li    def __init__(self, use_console=True):
80*9c5db199SXin Li        self.logger = logging.getLogger()
81*9c5db199SXin Li        self.global_level = logging.DEBUG
82*9c5db199SXin Li        self.use_console = use_console
83*9c5db199SXin Li
84*9c5db199SXin Li    @classmethod
85*9c5db199SXin Li    def get_timestamped_log_name(cls, base_name):
86*9c5db199SXin Li        return '%s.log.%s' % (base_name, time.strftime('%Y-%m-%d-%H.%M.%S'))
87*9c5db199SXin Li
88*9c5db199SXin Li    @classmethod
89*9c5db199SXin Li    def get_autotest_root(cls):
90*9c5db199SXin Li        common_lib_dir = os.path.dirname(__file__)
91*9c5db199SXin Li        return os.path.abspath(os.path.join(common_lib_dir, '..', '..'))
92*9c5db199SXin Li
93*9c5db199SXin Li    @classmethod
94*9c5db199SXin Li    def get_server_log_dir(cls):
95*9c5db199SXin Li        return os.path.join(cls.get_autotest_root(), 'logs')
96*9c5db199SXin Li
97*9c5db199SXin Li    def add_stream_handler(self, stream, level=logging.DEBUG, datefmt=None):
98*9c5db199SXin Li        handler = logging.StreamHandler(stream)
99*9c5db199SXin Li        handler.setLevel(level)
100*9c5db199SXin Li        formatter = self.console_formatter
101*9c5db199SXin Li        if datefmt:
102*9c5db199SXin Li            formatter = logging.Formatter(fmt=self.CONSOLE_FORMAT,
103*9c5db199SXin Li                                          datefmt=datefmt)
104*9c5db199SXin Li        handler.setFormatter(formatter)
105*9c5db199SXin Li        self.logger.addHandler(handler)
106*9c5db199SXin Li        return handler
107*9c5db199SXin Li
108*9c5db199SXin Li    def add_console_handlers(self, datefmt=None):
109*9c5db199SXin Li        stdout_handler = self.add_stream_handler(sys.stdout,
110*9c5db199SXin Li                                                 level=self.stdout_level,
111*9c5db199SXin Li                                                 datefmt=datefmt)
112*9c5db199SXin Li        # only pass records *below* STDERR_LEVEL to stdout, to avoid duplication
113*9c5db199SXin Li        stdout_handler.addFilter(AllowBelowSeverity(self.stderr_level))
114*9c5db199SXin Li
115*9c5db199SXin Li        self.add_stream_handler(sys.stderr, self.stderr_level, datefmt)
116*9c5db199SXin Li
117*9c5db199SXin Li    def add_file_handler(self,
118*9c5db199SXin Li                         file_path,
119*9c5db199SXin Li                         level=logging.DEBUG,
120*9c5db199SXin Li                         log_dir=None,
121*9c5db199SXin Li                         datefmt=None):
122*9c5db199SXin Li        if log_dir:
123*9c5db199SXin Li            file_path = os.path.join(log_dir, file_path)
124*9c5db199SXin Li        handler = logging.FileHandler(file_path)
125*9c5db199SXin Li        handler.setLevel(level)
126*9c5db199SXin Li        formatter = self.file_formatter
127*9c5db199SXin Li        if datefmt:
128*9c5db199SXin Li            formatter = logging.Formatter(fmt=self.FILE_FORMAT, datefmt=datefmt)
129*9c5db199SXin Li        # Respew the content of the selected file to /var/log/messages.
130*9c5db199SXin Li        if file_path == '/usr/local/autotest/results/default/debug/client.0.WARNING':
131*9c5db199SXin Li            formatter = self.system_formatter
132*9c5db199SXin Li        handler.setFormatter(formatter)
133*9c5db199SXin Li        self.logger.addHandler(handler)
134*9c5db199SXin Li        return handler
135*9c5db199SXin Li
136*9c5db199SXin Li    def _add_file_handlers_for_all_levels(self, log_dir, log_name):
137*9c5db199SXin Li        for level in (logging.DEBUG, logging.INFO, logging.WARNING,
138*9c5db199SXin Li                      logging.ERROR):
139*9c5db199SXin Li            file_name = '%s.%s' % (log_name, logging.getLevelName(level))
140*9c5db199SXin Li            self.add_file_handler(file_name, level=level, log_dir=log_dir)
141*9c5db199SXin Li
142*9c5db199SXin Li    def add_debug_file_handlers(self, log_dir, log_name=None):
143*9c5db199SXin Li        raise NotImplementedError
144*9c5db199SXin Li
145*9c5db199SXin Li    def _clear_all_handlers(self):
146*9c5db199SXin Li        for handler in list(self.logger.handlers):
147*9c5db199SXin Li            self.logger.removeHandler(handler)
148*9c5db199SXin Li            # Attempt to close the handler. If it's already closed a KeyError
149*9c5db199SXin Li            # will be generated. http://bugs.python.org/issue8581
150*9c5db199SXin Li            try:
151*9c5db199SXin Li                handler.close()
152*9c5db199SXin Li            except KeyError:
153*9c5db199SXin Li                pass
154*9c5db199SXin Li
155*9c5db199SXin Li    def configure_logging(self, use_console=True, verbose=False, datefmt=None):
156*9c5db199SXin Li        self._clear_all_handlers()  # see comment at top of file
157*9c5db199SXin Li        self.logger.setLevel(self.global_level)
158*9c5db199SXin Li
159*9c5db199SXin Li        if verbose:
160*9c5db199SXin Li            self.stdout_level = logging.DEBUG
161*9c5db199SXin Li        if use_console:
162*9c5db199SXin Li            self.add_console_handlers(datefmt)
163*9c5db199SXin Li
164*9c5db199SXin Li
165*9c5db199SXin Liclass TestingConfig(LoggingConfig):
166*9c5db199SXin Li
167*9c5db199SXin Li    def add_stream_handler(self, *args, **kwargs):
168*9c5db199SXin Li        pass
169*9c5db199SXin Li
170*9c5db199SXin Li    def add_file_handler(self, *args, **kwargs):
171*9c5db199SXin Li        pass
172*9c5db199SXin Li
173*9c5db199SXin Li    def configure_logging(self, **kwargs):
174*9c5db199SXin Li        pass
175*9c5db199SXin Li
176*9c5db199SXin Li
177*9c5db199SXin Lidef add_threadname_in_log():
178*9c5db199SXin Li    """Change logging formatter to include thread name.
179*9c5db199SXin Li
180*9c5db199SXin Li    This is to help logs from each thread runs to include its thread name.
181*9c5db199SXin Li    """
182*9c5db199SXin Li    log = logging.getLogger()
183*9c5db199SXin Li    for handler in logging.getLogger().handlers:
184*9c5db199SXin Li        if isinstance(handler, logging.FileHandler):
185*9c5db199SXin Li            log.removeHandler(handler)
186*9c5db199SXin Li            handler.setFormatter(logging.Formatter(
187*9c5db199SXin Li                    LoggingConfig.FILE_FORMAT_WITH_THREADNAME,
188*9c5db199SXin Li                    LoggingConfig.DATE_FORMAT))
189*9c5db199SXin Li            log.addHandler(handler)
190*9c5db199SXin Li    logging.debug('Logging handler\'s format updated with thread name.')
191