1# Copyright 2010 Google Inc. All Rights Reserved.
2
3from itertools import chain
4import gzip
5import logging
6import logging.handlers
7import time
8import traceback
9
10
11def SetUpRootLogger(filename=None, level=None, display_flags={}):
12  console_handler = logging.StreamHandler()
13  console_handler.setFormatter(CustomFormatter(AnsiColorCoder(), display_flags))
14  logging.root.addHandler(console_handler)
15
16  if filename:
17    file_handler = logging.handlers.RotatingFileHandler(
18        filename,
19        maxBytes=10 * 1024 * 1024,
20        backupCount=9,
21        delay=True)
22    file_handler.setFormatter(CustomFormatter(NullColorCoder(), display_flags))
23    logging.root.addHandler(file_handler)
24
25  if level:
26    logging.root.setLevel(level)
27
28
29class NullColorCoder(object):
30
31  def __call__(self, *args):
32    return ''
33
34
35class AnsiColorCoder(object):
36  CODES = {'reset': (0,),
37           'bold': (1, 22),
38           'italics': (3, 23),
39           'underline': (4, 24),
40           'inverse': (7, 27),
41           'strikethrough': (9, 29),
42           'black': (30, 40),
43           'red': (31, 41),
44           'green': (32, 42),
45           'yellow': (33, 43),
46           'blue': (34, 44),
47           'magenta': (35, 45),
48           'cyan': (36, 46),
49           'white': (37, 47)}
50
51  def __call__(self, *args):
52    codes = []
53
54    for arg in args:
55      if arg.startswith('bg-') or arg.startswith('no-'):
56        codes.append(self.CODES[arg[3:]][1])
57      else:
58        codes.append(self.CODES[arg][0])
59
60    return '\033[%sm' % ';'.join(map(str, codes))
61
62
63class CustomFormatter(logging.Formatter):
64  COLORS = {'DEBUG': ('white',),
65            'INFO': ('green',),
66            'WARN': ('yellow', 'bold'),
67            'ERROR': ('red', 'bold'),
68            'CRIT': ('red', 'inverse', 'bold')}
69
70  def __init__(self, coder, display_flags={}):
71    items = []
72
73    if display_flags.get('datetime', True):
74      items.append('%(asctime)s')
75    if display_flags.get('level', True):
76      items.append('%(levelname)s')
77    if display_flags.get('name', True):
78      items.append(coder('cyan') + '[%(threadName)s:%(name)s]' + coder('reset'))
79    items.append('%(prefix)s%(message)s')
80
81    logging.Formatter.__init__(self, fmt=' '.join(items))
82
83    self._coder = coder
84
85  def formatTime(self, record):
86    ct = self.converter(record.created)
87    t = time.strftime('%Y-%m-%d %H:%M:%S', ct)
88    return '%s.%02d' % (t, record.msecs / 10)
89
90  def formatLevelName(self, record):
91    if record.levelname in ['WARNING', 'CRITICAL']:
92      levelname = record.levelname[:4]
93    else:
94      levelname = record.levelname
95
96    return ''.join([self._coder(*self.COLORS[levelname]), levelname,
97                    self._coder('reset')])
98
99  def formatMessagePrefix(self, record):
100    try:
101      return ' %s%s:%s ' % (self._coder('black', 'bold'), record.prefix,
102                            self._coder('reset'))
103    except AttributeError:
104      return ''
105
106  def format(self, record):
107    if record.exc_info:
108      if not record.exc_text:
109        record.exc_text = self.formatException(record.exc_info)
110    else:
111      record.exc_text = ''
112
113    fmt = record.__dict__.copy()
114    fmt.update({'levelname': self.formatLevelName(record),
115                'asctime': self.formatTime(record),
116                'prefix': self.formatMessagePrefix(record)})
117
118    s = []
119
120    for line in chain(record.getMessage().splitlines(),
121                      record.exc_text.splitlines()):
122      fmt['message'] = line
123
124      s.append(self._fmt % fmt)
125
126    return '\n'.join(s)
127
128
129class CompressedFileHandler(logging.FileHandler):
130
131  def _open(self):
132    return gzip.open(self.baseFilename + '.gz', self.mode, 9)
133
134
135def HandleUncaughtExceptions(fun):
136  """Catches all exceptions that would go outside decorated fun scope."""
137
138  def _Interceptor(*args, **kwargs):
139    try:
140      return fun(*args, **kwargs)
141    except StandardError:
142      logging.exception('Uncaught exception:')
143
144  return _Interceptor
145