• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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