4 A reroutable logger that can handle deep tracebacks
8 * for legacy, we want all our code to just do:
10 from sfa.util.sfalogging import logger
14 * depending on whether the code runs (a) inside the server,
15 (b) as part of sfa-import, or (c) as part of the sfi CLI,
16 we want these messages to be directed in different places
18 * also because troubleshooting is very painful, we need a better way
19 to report stacks when an exception occurs.
23 * we use a single unique logger name 'sfa' (wrt getLogger()),
24 and provide an auxiliary function `init_logger()` that
25 accepts for its `context` parameter one of :
26 `server`, `import` `sfi` or `console`
27 It will then reconfigure the 'sfa' logger to do the right thing
29 * also we create our own subclass of loggers, and install it
30 with logging.setLoggerClass(), so we can add our own customized
35 # pylint: disable=c0111, c0103, w1201
37 from __future__ import print_function
44 import logging.handlers
47 # so that users of this module don't need to import logging
48 from logging import (CRITICAL, ERROR, WARNING, INFO, DEBUG)
51 class SfaLogger(logging.getLoggerClass()):
53 a rewrite of old _SfaLogger class that was way too cumbersome
54 keep this as much as possible though
57 # shorthand to avoid having to import logging all over the place
58 def setLevelDebug(self):
61 def debugEnabled(self):
62 return self.getEffectiveLevel() == logging.DEBUG
64 # define a verbose option with s/t like
65 # parser.add_option("-v", "--verbose", action="count",
66 # dest="verbose", default=0)
67 # and pass the coresponding options.verbose to this method to adjust level
68 def setLevelFromOptVerbose(self, verbose):
70 self.setLevel(logging.WARNING)
72 self.setLevel(logging.INFO)
74 self.setLevel(logging.DEBUG)
76 # in case some other code needs a boolean
78 def getBoolVerboseFromOpt(verbose):
82 def getBoolDebugFromOpt(verbose):
85 def log_exc(self, message, limit=100):
87 standard logger has an exception() method but this will
88 dump the stack only between the frames
89 (1) that does `raise` and (2) the one that does `except`
91 log_exc() has a limit argument that allows to see deeper than that
93 use limit=None to get the same behaviour as exception()
95 self.error("%s BEG TRACEBACK" % message + "\n" +
96 traceback.format_exc(limit=limit).strip("\n"))
97 self.error("%s END TRACEBACK" % message)
99 # for investigation purposes, can be placed anywhere
100 def log_stack(self, message, limit=100):
101 to_log = "".join(traceback.format_stack(limit=limit))
102 self.info("%s BEG STACK" % message + "\n" + to_log)
103 self.info("%s END STACK" % message)
105 def enable_console(self):
106 formatter = logging.Formatter("%(message)s")
107 handler = logging.StreamHandler(sys.stdout)
108 handler.setFormatter(formatter)
109 self.addHandler(handler)
112 # install our class as the default
113 logging.setLoggerClass(SfaLogger)
117 # this is *NOT* passed to dictConfig as-is
118 # instead we filter 'handlers' and 'loggers'
119 # to contain just one entry
120 # so make sure that 'handlers' and 'loggers'
121 # have the same set of keys
122 def logging_config(context):
123 if context == 'server':
125 filename = '/var/log/sfa.log'
127 elif context == 'import':
129 filename = '/var/log/sfa-import.log'
131 elif context == 'cli':
133 filename = os.path.expanduser("~/.sfi.log")
135 elif context == 'console':
136 handlername = 'stdout'
140 print("Cannot configure logging - exiting")
145 # IMPORTANT: we may be imported by something else, so:
146 'disable_existing_loggers': False,
149 'datefmt': '%m-%d %H:%M:%S',
150 'format': ('%(asctime)s %(levelname)s '
151 '%(filename)s:%(lineno)d %(message)s'),
154 # fill in later with just the one needed
155 # otherwise a dummy 'ignored' file gets created
160 'handlers': [handlername],
166 if handlername == 'stdout':
167 config['handlers']['stdout'] = {
169 'formatter': 'standard',
170 'class': 'logging.StreamHandler',
173 config['handlers']['file'] = {
174 'filename': filename,
176 'formatter': 'standard',
177 'class': 'logging.handlers.TimedRotatingFileHandler',
178 # every monday and during 3 months
186 logger = logging.getLogger('sfa')
189 def init_logger(context):
190 logging.config.dictConfig(logging_config(context))
193 # if the user process does not do anything
194 # like for the miscell testers and other certificate
195 # probing/dumping utilities
196 init_logger('console')