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 211 def _AddSuffix(self, basename, suffix): 212 return '%s%s' % (basename, suffix) 213 214 def _FindSuffix(self, basename): 215 timestamps = [] 216 found_suffix = None 217 for i in range(self.MAX_LOG_FILES): 218 suffix = str(i) 219 suffixed_basename = self._AddSuffix(basename, suffix) 220 cmd_file = '%s.cmd' % suffixed_basename 221 if not os.path.exists(cmd_file): 222 found_suffix = suffix 223 break 224 timestamps.append(os.stat(cmd_file).st_mtime) 225 226 if found_suffix: 227 return found_suffix 228 229 # Try to pick the oldest file with the suffix and return that one. 230 suffix = str(timestamps.index(min(timestamps))) 231 # print ("Warning: Overwriting log file: %s" % 232 # self._AddSuffix(basename, suffix)) 233 return suffix 234 235 def _CreateLogFileHandle(self, name): 236 print('MockLogger: creating open file handle for %s (writing)' % name) 237 238 def _CreateLogFileHandles(self, basename): 239 suffix = self._FindSuffix(basename) 240 suffixed_basename = self._AddSuffix(basename, suffix) 241 242 print('MockLogger: opening file %s.cmd' % suffixed_basename) 243 print('MockLogger: opening file %s.out' % suffixed_basename) 244 print('MockLogger: opening file %s.err' % suffixed_basename) 245 246 self._CreateLogFileSymlinks(basename, suffixed_basename) 247 248 # Symlink unsuffixed basename to currently suffixed one. 249 def _CreateLogFileSymlinks(self, basename, suffixed_basename): 250 for extension in ['cmd', 'out', 'err']: 251 src_file = '%s.%s' % (os.path.basename(suffixed_basename), extension) 252 dest_file = '%s.%s' % (basename, extension) 253 print('MockLogger: Calling os.symlink(%s, %s)' % (src_file, dest_file)) 254 255 def _WriteTo(self, _fd, msg, _flush): 256 print('MockLogger: %s' % msg) 257 258 def LogStartDots(self, _print_to_console=True): 259 print('. ') 260 261 def LogAppendDot(self, _print_to_console=True): 262 print('. ') 263 264 def LogEndDots(self, _print_to_console=True): 265 print('\n') 266 267 def LogMsg(self, _file_fd, _term_fd, msg, **_kwargs): 268 print('MockLogger: %s' % msg) 269 270 def _GetStdout(self, _print_to_console): 271 return None 272 273 def _GetStderr(self, _print_to_console): 274 return None 275 276 def LogCmdToFileOnly(self, *_args, **_kwargs): 277 return 278 279 # def LogCmdToFileOnly(self, cmd, machine='', user=None): 280 # host = ('%s@%s' % (user, machine)) if user else machine 281 # cmd_string = 'CMD (%s): %s\n' % (host, cmd) 282 # print('MockLogger: Writing to file ONLY: %s' % cmd_string) 283 284 def LogCmd(self, cmd, machine='', user=None, print_to_console=True): 285 if user: 286 host = '%s@%s' % (user, machine) 287 else: 288 host = machine 289 290 self.LogMsg(0, self._GetStdout(print_to_console), 291 'CMD (%s): %s\n' % (host, cmd)) 292 293 def LogFatal(self, msg, print_to_console=True): 294 self.LogMsg(0, self._GetStderr(print_to_console), 'FATAL: %s\n' % msg) 295 self.LogMsg(0, self._GetStderr(print_to_console), 296 '\n'.join(traceback.format_stack())) 297 print('MockLogger: Calling sysexit(1)') 298 299 def LogError(self, msg, print_to_console=True): 300 self.LogMsg(0, self._GetStderr(print_to_console), 'ERROR: %s\n' % msg) 301 302 def LogWarning(self, msg, print_to_console=True): 303 self.LogMsg(0, self._GetStderr(print_to_console), 'WARNING: %s\n' % msg) 304 305 def LogOutput(self, msg, print_to_console=True): 306 self.LogMsg(0, self._GetStdout(print_to_console), 'OUTPUT: %s\n' % msg) 307 308 def LogFatalIf(self, condition, msg): 309 if condition: 310 self.LogFatal(msg) 311 312 def LogErrorIf(self, condition, msg): 313 if condition: 314 self.LogError(msg) 315 316 def LogWarningIf(self, condition, msg): 317 if condition: 318 self.LogWarning(msg) 319 320 def LogCommandOutput(self, msg, print_to_console=True): 321 self.LogMsg( 322 self.stdout, self._GetStdout(print_to_console), msg, flush=False) 323 324 def LogCommandError(self, msg, print_to_console=True): 325 self.LogMsg( 326 self.stderr, self._GetStderr(print_to_console), msg, flush=False) 327 328 def Flush(self): 329 print('MockLogger: Flushing cmdfd, stdout, stderr') 330 331 332main_logger = None 333 334 335def InitLogger(script_name, log_dir, print_console=True, mock=False): 336 """Initialize a global logger. To be called only once.""" 337 # pylint: disable=global-statement 338 global main_logger 339 assert not main_logger, 'The logger has already been initialized' 340 rootdir, basefilename = GetRoot(script_name) 341 if not log_dir: 342 log_dir = rootdir 343 if not mock: 344 main_logger = Logger(log_dir, basefilename, print_console) 345 else: 346 main_logger = MockLogger(log_dir, basefilename, print_console) 347 348 349def GetLogger(log_dir='', mock=False): 350 if not main_logger: 351 InitLogger(sys.argv[0], log_dir, mock=mock) 352 return main_logger 353 354 355def HandleUncaughtExceptions(fun): 356 """Catches all exceptions that would go outside decorated fun scope.""" 357 358 def _Interceptor(*args, **kwargs): 359 try: 360 return fun(*args, **kwargs) 361 except Exception: 362 GetLogger().LogFatal('Uncaught exception:\n%s' % traceback.format_exc()) 363 364 return _Interceptor 365