2 # Copyright (c) 2013 Intel, Inc.
4 # This program is free software; you can redistribute it and/or modify it
5 # under the terms of the GNU General Public License as published by the Free
6 # Software Foundation; version 2 of the License
8 # This program is distributed in the hope that it will be useful, but
9 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 # You should have received a copy of the GNU General Public License along
14 # with this program; if not, write to the Free Software Foundation, Inc., 59
15 # Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 # Following messages should be disabled in pylint:
18 # * Too many instance attributes (R0902)
19 # * Too few public methods (R0903)
20 # * Too many public methods (R0904)
21 # * Anomalous backslash in string (W1401)
22 # * __init__ method from base class %r is not called (W0231)
23 # * __init__ method from a non direct base class %r is called (W0233)
24 # * Invalid name for type (C0103)
25 # * RootLogger has no '%s' member (E1103)
26 # pylint: disable=R0902,R0903,R0904,W1401,W0231,W0233,C0103,E1103
28 """ This logging module is fully compatible with the old msger module, and
29 it supports interactive mode, logs the messages with specified levels
30 to specified stream, can also catch all error messages including the
31 involved 3rd party modules to the logger
43 'disable_interactive',
59 # define the color constants
60 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(30, 38)
62 # color sequence for tty terminal
63 COLOR_SEQ = "\033[%dm"
64 # reset sequence for tty terminal
71 # define colors for log levels
73 'DEBUG': COLOR_SEQ % BLUE,
74 'VERBOSE': COLOR_SEQ % MAGENTA,
75 'INFO': COLOR_SEQ % GREEN,
76 'WARNING': COLOR_SEQ % YELLOW,
77 'ERROR': COLOR_SEQ % RED,
81 class LevelFilter(logging.Filter):
82 """ A filter that selects logging message with specified level """
83 def __init__(self, levels):
86 def filter(self, record):
88 return record.levelname in self._levels
92 class MicStreamHandler(logging.StreamHandler):
93 """ A stream handler that print colorized levelname in tty terminal """
94 def __init__(self, stream=None):
95 logging.StreamHandler.__init__(self, stream)
96 msg_fmt = "%(color)s%(levelname)s:%(reset)s %(message)s"
97 self.setFormatter(logging.Formatter(fmt=msg_fmt))
100 """ Check if to print in color or not """
101 in_emacs = (os.getenv("EMACS") and
102 os.getenv("INSIDE_EMACS", "").endswith(",comint"))
103 return self.stream.isatty() and not in_emacs
105 def format(self, record):
106 """ Format the logging record if need color """
107 record.color = record.reset = ""
108 if self._use_color():
109 record.color = COLORS[record.levelname]
110 record.reset = RESET_SEQ
111 return logging.StreamHandler.format(self, record)
114 class RedirectedStderr(object):
115 """ A faked error stream that redirect stderr to a temp file """
117 self.tmpfile = tempfile.NamedTemporaryFile()
125 """ Close the temp file and clear the buffer """
133 """ Truncate the tempfile to size zero """
135 os.ftruncate(self.tmpfile.fileno(), 0)
136 os.lseek(self.tmpfile.fileno(), 0, os.SEEK_SET)
139 """ Redirect stderr to the temp file """
140 self.fderr = os.dup(2)
141 os.dup2(self.tmpfile.fileno(), 2)
144 """ Restore the stderr and read the bufferred data """
145 os.dup2(self.fderr, 2)
149 self.tmpfile.seek(0, 0)
150 self.value = self.tmpfile.read()
153 """ Read the bufferred data """
155 self.tmpfile.seek(0, 0)
156 self.value = self.tmpfile.read()
157 os.ftruncate(self.tmpfile.fileno(), 0)
158 os.lseek(self.tmpfile.fileno(), 0, os.SEEK_SET)
162 class MicFileHandler(logging.FileHandler):
163 """ This file handler is supposed to catch the stderr output from
164 all modules even 3rd party modules involed, as it redirects
165 the stderr stream to a temp file stream, if logfile assigned,
166 it will flush the record to file stream, else it's a buffer
167 handler; once logfile assigned, the buffer will be flushed
169 def __init__(self, filename=None, mode='w', encoding=None, capacity=10):
170 # we don't use FileHandler to initialize,
171 # because filename might be expected to None
172 logging.Handler.__init__(self)
175 self.baseFilename = os.path.abspath(filename)
177 self.baseFilename = None
180 self.capacity = capacity
181 # buffering the records
184 # set formater locally
185 msg_fmt = "[%(asctime)s] %(message)s"
186 date_fmt = "%m/%d %H:%M:%S %Z"
187 self.setFormatter(logging.Formatter(fmt=msg_fmt, datefmt=date_fmt))
188 self.olderr = sys.stderr
189 self.stderr = RedirectedStderr()
192 def set_logfile(self, filename, mode='w'):
193 """ Set logfile path to make it possible flush records not-on-fly """
194 self.baseFilename = os.path.abspath(filename)
197 def redirect_stderr(self):
198 """ Start to redirect stderr for catching all error output """
199 self.stderr.redirect()
201 def restore_stderr(self):
202 """ Restore stderr stream and log the error messages to both stderr
203 and log file if error messages are not empty
205 self.stderr.restore()
206 self.errmsg = self.stderr.value
211 """ Log catched error message from stderr redirector """
215 sys.stdout.write(self.errmsg)
218 record = logging.makeLogRecord({'msg': self.errmsg})
219 self.buffer.append(record)
221 # truncate the redirector for the errors is logged
222 self.stderr.truncate()
225 def emit(self, record):
226 """ Emit the log record to Handler """
227 # if there error message catched, log it first
228 self.errmsg = self.stderr.getvalue()
232 # if no logfile assigned, it's a buffer handler
233 if not self.baseFilename:
234 self.buffer.append(record)
235 if len(self.buffer) >= self.capacity:
238 self.flushing(record)
240 def flushing(self, record=None):
241 """ Flush buffer and record to logfile """
242 # NOTE: 'flushing' can't be named 'flush' because of 'emit' calling it
243 # set file stream position to SEEK_END(=2)
245 self.stream.seek(0, 2)
246 # if bufferred, flush it
248 for arecord in self.buffer:
249 logging.FileHandler.emit(self, arecord)
251 # if recorded, flush it
253 logging.FileHandler.emit(self, record)
256 """ Close handler after flushing the buffer """
257 # if any left in buffer, flush it
260 logging.FileHandler.close(self)
263 class MicLogger(logging.Logger):
264 """ The MIC logger class, it supports interactive mode, and logs the
265 messages with specified levels tospecified stream, also can catch
266 all error messages including the involved 3rd party modules
268 def __init__(self, name, level=logging.INFO):
269 logging.Logger.__init__(self, name, level)
270 self.propagate = False
271 self.interactive = True
273 self._allhandlers = {
274 'default': logging.StreamHandler(sys.stdout),
275 'stdout': MicStreamHandler(sys.stdout),
276 'stderr': MicStreamHandler(sys.stderr),
277 'logfile': MicFileHandler(),
280 self._allhandlers['default'].addFilter(LevelFilter(['RAWTEXT']))
281 self._allhandlers['default'].setFormatter(
282 logging.Formatter(fmt="%(message)s"))
283 self.addHandler(self._allhandlers['default'])
285 self._allhandlers['stdout'].addFilter(LevelFilter(['DEBUG', 'VERBOSE',
287 self.addHandler(self._allhandlers['stdout'])
289 self._allhandlers['stderr'].addFilter(LevelFilter(['WARNING',
291 self.addHandler(self._allhandlers['stderr'])
293 self.addHandler(self._allhandlers['logfile'])
295 def set_logfile(self, filename, mode='w'):
296 """ Set logfile path """
297 self.logfile = filename
298 self._allhandlers['logfile'].set_logfile(self.logfile, mode)
300 def enable_logstderr(self):
301 """ Start to log all error messages """
303 self._allhandlers['logfile'].redirect_stderr()
305 def disable_logstderr(self):
306 """ Stop to log all error messages """
308 self._allhandlers['logfile'].restore_stderr()
310 def verbose(self, msg, *args, **kwargs):
311 """ Log a message with level VERBOSE """
312 if self.isEnabledFor(VERBOSE):
313 self._log(VERBOSE, msg, args, **kwargs)
315 def raw(self, msg, *args, **kwargs):
316 """ Log a message in raw text format """
317 if self.isEnabledFor(RAWTEXT):
318 self._log(RAWTEXT, msg, args, **kwargs)
320 def select(self, msg, optdict, default=None):
321 """ Log a message in interactive mode """
322 if not optdict.keys():
325 default = optdict.keys()[0]
326 msg += " [%s](%s): " % ('/'.join(optdict.keys()), default)
327 if not self.interactive or self.logfile:
329 self.raw(msg + reply)
332 reply = raw_input(msg).strip()
333 if not reply or reply in optdict:
337 return optdict[reply]
341 """ Log a message with level ERROR on the MIC logger """
346 """ Log a message with level WARNING on the MIC logger """
350 """ Log a message with level INFO on the MIC logger """
354 """ Log a message with level VERBOSE on the MIC logger """
358 """ Log a message with level DEBUG on the MIC logger """
362 """ Log a message on the MIC logger in raw text format"""
365 def select(msg, optdict, default=None):
366 """ Show an interactive scene in tty terminal and
367 logs them on MIC logger
369 return LOGGER.select(msg, optdict, default)
371 def choice(msg, optlist, default=0):
372 """ Give some alternatives to users for answering the question """
373 return LOGGER.select(msg, dict(zip(optlist, optlist)), optlist[default])
375 def ask(msg, ret=True):
376 """ Ask users to answer 'yes' or 'no' to the question """
377 answers = {'y': True, 'n': False}
378 default = {True: 'y', False: 'n'}[ret]
379 return LOGGER.select(msg, answers, default)
382 """ Pause for any key """
384 msg = "press ANY KEY to continue ..."
387 def set_logfile(logfile, mode='w'):
388 """ Set logfile path to the MIC logger """
389 LOGGER.set_logfile(logfile, mode)
391 def set_loglevel(level):
392 """ Set loglevel to the MIC logger """
393 if isinstance(level, basestring):
394 level = logging.getLevelName(level)
395 LOGGER.setLevel(level)
398 """ Get the loglevel of the MIC logger """
399 return logging.getLevelName(LOGGER.level)
401 def disable_interactive():
402 """ Disable the interactive mode """
403 LOGGER.interactive = False
405 def enable_interactive():
406 """ Enable the interactive mode """
407 LOGGER.interactive = True
409 def set_interactive(value):
410 """ Set the interactive mode (for compatibility) """
414 disable_interactive()
416 def enable_logstderr(fpath=None):
417 """ Start to log all error message on the MIC logger """
418 LOGGER.enable_logstderr()
420 def disable_logstderr():
421 """ Stop to log all error message on the MIC logger """
422 LOGGER.disable_logstderr()
425 # add two level to the MIC logger: 'VERBOSE', 'RAWTEXT'
426 logging.addLevelName(VERBOSE, 'VERBOSE')
427 logging.addLevelName(RAWTEXT, 'RAWTEXT')
428 # initial the MIC logger
429 logging.setLoggerClass(MicLogger)
430 LOGGER = logging.getLogger("MIC")