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