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