1*760c253cSXin Li# Copyright 2019 The ChromiumOS Authors 2*760c253cSXin Li# Use of this source code is governed by a BSD-style license that can be 3*760c253cSXin Li# found in the LICENSE file. 4*760c253cSXin Li 5*760c253cSXin Li"""Logging helper module.""" 6*760c253cSXin Li 7*760c253cSXin Li 8*760c253cSXin Li# System modules 9*760c253cSXin Liimport os.path 10*760c253cSXin Liimport sys 11*760c253cSXin Liimport traceback 12*760c253cSXin Lifrom typing import Union 13*760c253cSXin Li 14*760c253cSXin Li 15*760c253cSXin Li# TODO([email protected]): Use GetRoot from misc 16*760c253cSXin Lidef GetRoot(scr_name): 17*760c253cSXin Li """Break up pathname into (dir+name).""" 18*760c253cSXin Li abs_path = os.path.abspath(scr_name) 19*760c253cSXin Li return (os.path.dirname(abs_path), os.path.basename(abs_path)) 20*760c253cSXin Li 21*760c253cSXin Li 22*760c253cSXin Liclass Logger: 23*760c253cSXin Li """Logging helper class.""" 24*760c253cSXin Li 25*760c253cSXin Li MAX_LOG_FILES = 10 26*760c253cSXin Li 27*760c253cSXin Li def __init__(self, rootdir, basefilename, print_console, subdir="logs"): 28*760c253cSXin Li logdir = os.path.join(rootdir, subdir) 29*760c253cSXin Li basename = os.path.join(logdir, basefilename) 30*760c253cSXin Li 31*760c253cSXin Li try: 32*760c253cSXin Li os.makedirs(logdir) 33*760c253cSXin Li except OSError: 34*760c253cSXin Li pass 35*760c253cSXin Li # print("Warning: Logs directory '%s' already exists." % logdir) 36*760c253cSXin Li 37*760c253cSXin Li self.print_console = print_console 38*760c253cSXin Li 39*760c253cSXin Li self._CreateLogFileHandles(basename) 40*760c253cSXin Li 41*760c253cSXin Li self._WriteTo(self.cmdfd, " ".join(sys.argv), True) 42*760c253cSXin Li 43*760c253cSXin Li def _AddSuffix(self, basename, suffix): 44*760c253cSXin Li return "%s%s" % (basename, suffix) 45*760c253cSXin Li 46*760c253cSXin Li def _FindSuffix(self, basename): 47*760c253cSXin Li timestamps = [] 48*760c253cSXin Li found_suffix = None 49*760c253cSXin Li for i in range(self.MAX_LOG_FILES): 50*760c253cSXin Li suffix = str(i) 51*760c253cSXin Li suffixed_basename = self._AddSuffix(basename, suffix) 52*760c253cSXin Li cmd_file = "%s.cmd" % suffixed_basename 53*760c253cSXin Li if not os.path.exists(cmd_file): 54*760c253cSXin Li found_suffix = suffix 55*760c253cSXin Li break 56*760c253cSXin Li timestamps.append(os.stat(cmd_file).st_mtime) 57*760c253cSXin Li 58*760c253cSXin Li if found_suffix: 59*760c253cSXin Li return found_suffix 60*760c253cSXin Li 61*760c253cSXin Li # Try to pick the oldest file with the suffix and return that one. 62*760c253cSXin Li suffix = str(timestamps.index(min(timestamps))) 63*760c253cSXin Li # print ("Warning: Overwriting log file: %s" % 64*760c253cSXin Li # self._AddSuffix(basename, suffix)) 65*760c253cSXin Li return suffix 66*760c253cSXin Li 67*760c253cSXin Li def _CreateLogFileHandle(self, name): 68*760c253cSXin Li fd = None 69*760c253cSXin Li try: 70*760c253cSXin Li fd = open(name, "w", encoding="utf-8") 71*760c253cSXin Li except IOError: 72*760c253cSXin Li print("Warning: could not open %s for writing." % name) 73*760c253cSXin Li return fd 74*760c253cSXin Li 75*760c253cSXin Li def _CreateLogFileHandles(self, basename): 76*760c253cSXin Li suffix = self._FindSuffix(basename) 77*760c253cSXin Li suffixed_basename = self._AddSuffix(basename, suffix) 78*760c253cSXin Li 79*760c253cSXin Li self.cmdfd = self._CreateLogFileHandle("%s.cmd" % suffixed_basename) 80*760c253cSXin Li self.stdout = self._CreateLogFileHandle("%s.out" % suffixed_basename) 81*760c253cSXin Li self.stderr = self._CreateLogFileHandle("%s.err" % suffixed_basename) 82*760c253cSXin Li 83*760c253cSXin Li self._CreateLogFileSymlinks(basename, suffixed_basename) 84*760c253cSXin Li 85*760c253cSXin Li # Symlink unsuffixed basename to currently suffixed one. 86*760c253cSXin Li def _CreateLogFileSymlinks(self, basename, suffixed_basename): 87*760c253cSXin Li try: 88*760c253cSXin Li for extension in ["cmd", "out", "err"]: 89*760c253cSXin Li src_file = "%s.%s" % ( 90*760c253cSXin Li os.path.basename(suffixed_basename), 91*760c253cSXin Li extension, 92*760c253cSXin Li ) 93*760c253cSXin Li dest_file = "%s.%s" % (basename, extension) 94*760c253cSXin Li if os.path.exists(dest_file): 95*760c253cSXin Li os.remove(dest_file) 96*760c253cSXin Li os.symlink(src_file, dest_file) 97*760c253cSXin Li except Exception as ex: 98*760c253cSXin Li print("Exception while creating symlinks: %s" % str(ex)) 99*760c253cSXin Li 100*760c253cSXin Li def _WriteTo(self, fd, msg, flush): 101*760c253cSXin Li if fd: 102*760c253cSXin Li fd.write(msg) 103*760c253cSXin Li if flush: 104*760c253cSXin Li fd.flush() 105*760c253cSXin Li 106*760c253cSXin Li def LogStartDots(self, print_to_console=True): 107*760c253cSXin Li term_fd = self._GetStdout(print_to_console) 108*760c253cSXin Li if term_fd: 109*760c253cSXin Li term_fd.flush() 110*760c253cSXin Li term_fd.write(". ") 111*760c253cSXin Li term_fd.flush() 112*760c253cSXin Li 113*760c253cSXin Li def LogAppendDot(self, print_to_console=True): 114*760c253cSXin Li term_fd = self._GetStdout(print_to_console) 115*760c253cSXin Li if term_fd: 116*760c253cSXin Li term_fd.write(". ") 117*760c253cSXin Li term_fd.flush() 118*760c253cSXin Li 119*760c253cSXin Li def LogEndDots(self, print_to_console=True): 120*760c253cSXin Li term_fd = self._GetStdout(print_to_console) 121*760c253cSXin Li if term_fd: 122*760c253cSXin Li term_fd.write("\n") 123*760c253cSXin Li term_fd.flush() 124*760c253cSXin Li 125*760c253cSXin Li def LogMsg(self, file_fd, term_fd, msg, flush=True): 126*760c253cSXin Li if file_fd: 127*760c253cSXin Li self._WriteTo(file_fd, msg, flush) 128*760c253cSXin Li if self.print_console: 129*760c253cSXin Li self._WriteTo(term_fd, msg, flush) 130*760c253cSXin Li 131*760c253cSXin Li def _GetStdout(self, print_to_console): 132*760c253cSXin Li if print_to_console: 133*760c253cSXin Li return sys.stdout 134*760c253cSXin Li return None 135*760c253cSXin Li 136*760c253cSXin Li def _GetStderr(self, print_to_console): 137*760c253cSXin Li if print_to_console: 138*760c253cSXin Li return sys.stderr 139*760c253cSXin Li return None 140*760c253cSXin Li 141*760c253cSXin Li def LogCmdToFileOnly(self, cmd, machine="", user=None): 142*760c253cSXin Li if not self.cmdfd: 143*760c253cSXin Li return 144*760c253cSXin Li 145*760c253cSXin Li host = ("%s@%s" % (user, machine)) if user else machine 146*760c253cSXin Li flush = True 147*760c253cSXin Li cmd_string = "CMD (%s): %s\n" % (host, cmd) 148*760c253cSXin Li self._WriteTo(self.cmdfd, cmd_string, flush) 149*760c253cSXin Li 150*760c253cSXin Li def LogCmd(self, cmd, machine="", user=None, print_to_console=True): 151*760c253cSXin Li if user: 152*760c253cSXin Li host = "%s@%s" % (user, machine) 153*760c253cSXin Li else: 154*760c253cSXin Li host = machine 155*760c253cSXin Li 156*760c253cSXin Li self.LogMsg( 157*760c253cSXin Li self.cmdfd, 158*760c253cSXin Li self._GetStdout(print_to_console), 159*760c253cSXin Li "CMD (%s): %s\n" % (host, cmd), 160*760c253cSXin Li ) 161*760c253cSXin Li 162*760c253cSXin Li def LogFatal(self, msg, print_to_console=True): 163*760c253cSXin Li self.LogMsg( 164*760c253cSXin Li self.stderr, self._GetStderr(print_to_console), "FATAL: %s\n" % msg 165*760c253cSXin Li ) 166*760c253cSXin Li self.LogMsg( 167*760c253cSXin Li self.stderr, 168*760c253cSXin Li self._GetStderr(print_to_console), 169*760c253cSXin Li "\n".join(traceback.format_stack()), 170*760c253cSXin Li ) 171*760c253cSXin Li sys.exit(1) 172*760c253cSXin Li 173*760c253cSXin Li def LogError(self, msg, print_to_console=True): 174*760c253cSXin Li self.LogMsg( 175*760c253cSXin Li self.stderr, self._GetStderr(print_to_console), "ERROR: %s\n" % msg 176*760c253cSXin Li ) 177*760c253cSXin Li 178*760c253cSXin Li def LogWarning(self, msg, print_to_console=True): 179*760c253cSXin Li self.LogMsg( 180*760c253cSXin Li self.stderr, 181*760c253cSXin Li self._GetStderr(print_to_console), 182*760c253cSXin Li "WARNING: %s\n" % msg, 183*760c253cSXin Li ) 184*760c253cSXin Li 185*760c253cSXin Li def LogOutput(self, msg, print_to_console=True): 186*760c253cSXin Li self.LogMsg( 187*760c253cSXin Li self.stdout, self._GetStdout(print_to_console), "OUTPUT: %s\n" % msg 188*760c253cSXin Li ) 189*760c253cSXin Li 190*760c253cSXin Li def LogFatalIf(self, condition, msg): 191*760c253cSXin Li if condition: 192*760c253cSXin Li self.LogFatal(msg) 193*760c253cSXin Li 194*760c253cSXin Li def LogErrorIf(self, condition, msg): 195*760c253cSXin Li if condition: 196*760c253cSXin Li self.LogError(msg) 197*760c253cSXin Li 198*760c253cSXin Li def LogWarningIf(self, condition, msg): 199*760c253cSXin Li if condition: 200*760c253cSXin Li self.LogWarning(msg) 201*760c253cSXin Li 202*760c253cSXin Li def LogCommandOutput(self, msg, print_to_console=True): 203*760c253cSXin Li self.LogMsg( 204*760c253cSXin Li self.stdout, self._GetStdout(print_to_console), msg, flush=False 205*760c253cSXin Li ) 206*760c253cSXin Li 207*760c253cSXin Li def LogCommandError(self, msg, print_to_console=True): 208*760c253cSXin Li self.LogMsg( 209*760c253cSXin Li self.stderr, self._GetStderr(print_to_console), msg, flush=False 210*760c253cSXin Li ) 211*760c253cSXin Li 212*760c253cSXin Li def Flush(self): 213*760c253cSXin Li self.cmdfd.flush() 214*760c253cSXin Li self.stdout.flush() 215*760c253cSXin Li self.stderr.flush() 216*760c253cSXin Li 217*760c253cSXin Li 218*760c253cSXin Liclass MockLogger: 219*760c253cSXin Li """Logging helper class.""" 220*760c253cSXin Li 221*760c253cSXin Li MAX_LOG_FILES = 10 222*760c253cSXin Li 223*760c253cSXin Li def __init__(self, *_args, **_kwargs): 224*760c253cSXin Li self.stdout = sys.stdout 225*760c253cSXin Li self.stderr = sys.stderr 226*760c253cSXin Li 227*760c253cSXin Li def _AddSuffix(self, basename, suffix): 228*760c253cSXin Li return "%s%s" % (basename, suffix) 229*760c253cSXin Li 230*760c253cSXin Li def _FindSuffix(self, basename): 231*760c253cSXin Li timestamps = [] 232*760c253cSXin Li found_suffix = None 233*760c253cSXin Li for i in range(self.MAX_LOG_FILES): 234*760c253cSXin Li suffix = str(i) 235*760c253cSXin Li suffixed_basename = self._AddSuffix(basename, suffix) 236*760c253cSXin Li cmd_file = "%s.cmd" % suffixed_basename 237*760c253cSXin Li if not os.path.exists(cmd_file): 238*760c253cSXin Li found_suffix = suffix 239*760c253cSXin Li break 240*760c253cSXin Li timestamps.append(os.stat(cmd_file).st_mtime) 241*760c253cSXin Li 242*760c253cSXin Li if found_suffix: 243*760c253cSXin Li return found_suffix 244*760c253cSXin Li 245*760c253cSXin Li # Try to pick the oldest file with the suffix and return that one. 246*760c253cSXin Li suffix = str(timestamps.index(min(timestamps))) 247*760c253cSXin Li # print ("Warning: Overwriting log file: %s" % 248*760c253cSXin Li # self._AddSuffix(basename, suffix)) 249*760c253cSXin Li return suffix 250*760c253cSXin Li 251*760c253cSXin Li def _CreateLogFileHandle(self, name): 252*760c253cSXin Li print("MockLogger: creating open file handle for %s (writing)" % name) 253*760c253cSXin Li 254*760c253cSXin Li def _CreateLogFileHandles(self, basename): 255*760c253cSXin Li suffix = self._FindSuffix(basename) 256*760c253cSXin Li suffixed_basename = self._AddSuffix(basename, suffix) 257*760c253cSXin Li 258*760c253cSXin Li print("MockLogger: opening file %s.cmd" % suffixed_basename) 259*760c253cSXin Li print("MockLogger: opening file %s.out" % suffixed_basename) 260*760c253cSXin Li print("MockLogger: opening file %s.err" % suffixed_basename) 261*760c253cSXin Li 262*760c253cSXin Li self._CreateLogFileSymlinks(basename, suffixed_basename) 263*760c253cSXin Li 264*760c253cSXin Li # Symlink unsuffixed basename to currently suffixed one. 265*760c253cSXin Li def _CreateLogFileSymlinks(self, basename, suffixed_basename): 266*760c253cSXin Li for extension in ["cmd", "out", "err"]: 267*760c253cSXin Li src_file = "%s.%s" % ( 268*760c253cSXin Li os.path.basename(suffixed_basename), 269*760c253cSXin Li extension, 270*760c253cSXin Li ) 271*760c253cSXin Li dest_file = "%s.%s" % (basename, extension) 272*760c253cSXin Li print( 273*760c253cSXin Li "MockLogger: Calling os.symlink(%s, %s)" % (src_file, dest_file) 274*760c253cSXin Li ) 275*760c253cSXin Li 276*760c253cSXin Li def _WriteTo(self, _fd, msg, _flush): 277*760c253cSXin Li print("MockLogger: %s" % msg) 278*760c253cSXin Li 279*760c253cSXin Li def LogStartDots(self, _print_to_console=True): 280*760c253cSXin Li print(". ") 281*760c253cSXin Li 282*760c253cSXin Li def LogAppendDot(self, _print_to_console=True): 283*760c253cSXin Li print(". ") 284*760c253cSXin Li 285*760c253cSXin Li def LogEndDots(self, _print_to_console=True): 286*760c253cSXin Li print("\n") 287*760c253cSXin Li 288*760c253cSXin Li def LogMsg(self, _file_fd, _term_fd, msg, **_kwargs): 289*760c253cSXin Li print("MockLogger: %s" % msg) 290*760c253cSXin Li 291*760c253cSXin Li def _GetStdout(self, _print_to_console): 292*760c253cSXin Li return None 293*760c253cSXin Li 294*760c253cSXin Li def _GetStderr(self, _print_to_console): 295*760c253cSXin Li return None 296*760c253cSXin Li 297*760c253cSXin Li def LogCmdToFileOnly(self, *_args, **_kwargs): 298*760c253cSXin Li return 299*760c253cSXin Li 300*760c253cSXin Li # def LogCmdToFileOnly(self, cmd, machine='', user=None): 301*760c253cSXin Li # host = ('%s@%s' % (user, machine)) if user else machine 302*760c253cSXin Li # cmd_string = 'CMD (%s): %s\n' % (host, cmd) 303*760c253cSXin Li # print('MockLogger: Writing to file ONLY: %s' % cmd_string) 304*760c253cSXin Li 305*760c253cSXin Li def LogCmd(self, cmd, machine="", user=None, print_to_console=True): 306*760c253cSXin Li if user: 307*760c253cSXin Li host = "%s@%s" % (user, machine) 308*760c253cSXin Li else: 309*760c253cSXin Li host = machine 310*760c253cSXin Li 311*760c253cSXin Li self.LogMsg( 312*760c253cSXin Li 0, self._GetStdout(print_to_console), "CMD (%s): %s\n" % (host, cmd) 313*760c253cSXin Li ) 314*760c253cSXin Li 315*760c253cSXin Li def LogFatal(self, msg, print_to_console=True): 316*760c253cSXin Li self.LogMsg(0, self._GetStderr(print_to_console), "FATAL: %s\n" % msg) 317*760c253cSXin Li self.LogMsg( 318*760c253cSXin Li 0, 319*760c253cSXin Li self._GetStderr(print_to_console), 320*760c253cSXin Li "\n".join(traceback.format_stack()), 321*760c253cSXin Li ) 322*760c253cSXin Li print("MockLogger: Calling sysexit(1)") 323*760c253cSXin Li 324*760c253cSXin Li def LogError(self, msg, print_to_console=True): 325*760c253cSXin Li self.LogMsg(0, self._GetStderr(print_to_console), "ERROR: %s\n" % msg) 326*760c253cSXin Li 327*760c253cSXin Li def LogWarning(self, msg, print_to_console=True): 328*760c253cSXin Li self.LogMsg(0, self._GetStderr(print_to_console), "WARNING: %s\n" % msg) 329*760c253cSXin Li 330*760c253cSXin Li def LogOutput(self, msg, print_to_console=True): 331*760c253cSXin Li self.LogMsg(0, self._GetStdout(print_to_console), "OUTPUT: %s\n" % msg) 332*760c253cSXin Li 333*760c253cSXin Li def LogFatalIf(self, condition, msg): 334*760c253cSXin Li if condition: 335*760c253cSXin Li self.LogFatal(msg) 336*760c253cSXin Li 337*760c253cSXin Li def LogErrorIf(self, condition, msg): 338*760c253cSXin Li if condition: 339*760c253cSXin Li self.LogError(msg) 340*760c253cSXin Li 341*760c253cSXin Li def LogWarningIf(self, condition, msg): 342*760c253cSXin Li if condition: 343*760c253cSXin Li self.LogWarning(msg) 344*760c253cSXin Li 345*760c253cSXin Li def LogCommandOutput(self, msg, print_to_console=True): 346*760c253cSXin Li self.LogMsg( 347*760c253cSXin Li self.stdout, self._GetStdout(print_to_console), msg, flush=False 348*760c253cSXin Li ) 349*760c253cSXin Li 350*760c253cSXin Li def LogCommandError(self, msg, print_to_console=True): 351*760c253cSXin Li self.LogMsg( 352*760c253cSXin Li self.stderr, self._GetStderr(print_to_console), msg, flush=False 353*760c253cSXin Li ) 354*760c253cSXin Li 355*760c253cSXin Li def Flush(self): 356*760c253cSXin Li print("MockLogger: Flushing cmdfd, stdout, stderr") 357*760c253cSXin Li 358*760c253cSXin Li 359*760c253cSXin Limain_logger = None 360*760c253cSXin Li 361*760c253cSXin Li 362*760c253cSXin Lidef InitLogger(script_name, log_dir, print_console=True, mock=False): 363*760c253cSXin Li """Initialize a global logger. To be called only once.""" 364*760c253cSXin Li # pylint: disable=global-statement 365*760c253cSXin Li global main_logger 366*760c253cSXin Li if main_logger: 367*760c253cSXin Li return main_logger 368*760c253cSXin Li rootdir, basefilename = GetRoot(script_name) 369*760c253cSXin Li if not log_dir: 370*760c253cSXin Li log_dir = rootdir 371*760c253cSXin Li if not mock: 372*760c253cSXin Li main_logger = Logger(log_dir, basefilename, print_console) 373*760c253cSXin Li else: 374*760c253cSXin Li main_logger = MockLogger(log_dir, basefilename, print_console) 375*760c253cSXin Li return main_logger 376*760c253cSXin Li 377*760c253cSXin Li 378*760c253cSXin Lidef GetLogger(log_dir="", mock=False) -> Union[Logger, MockLogger]: 379*760c253cSXin Li return InitLogger(sys.argv[0], log_dir, mock=mock) 380*760c253cSXin Li 381*760c253cSXin Li 382*760c253cSXin Lidef HandleUncaughtExceptions(fun): 383*760c253cSXin Li """Catches all exceptions that would go outside decorated fun scope.""" 384*760c253cSXin Li 385*760c253cSXin Li def _Interceptor(*args, **kwargs): 386*760c253cSXin Li try: 387*760c253cSXin Li return fun(*args, **kwargs) 388*760c253cSXin Li except Exception: 389*760c253cSXin Li GetLogger().LogFatal( 390*760c253cSXin Li "Uncaught exception:\n%s" % traceback.format_exc() 391*760c253cSXin Li ) 392*760c253cSXin Li 393*760c253cSXin Li return _Interceptor 394