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)
138 """ Redirect stderr to the temp file """
139 self.fderr = os.dup(2)
140 os.dup2(self.tmpfile.fileno(), 2)
143 """ Restore the stderr and read the bufferred data """
144 os.dup2(self.fderr, 2)
148 self.tmpfile.seek(0, 0)
149 self.value = self.tmpfile.read()
152 """ Read the bufferred data """
154 self.tmpfile.seek(0, 0)
155 self.value = self.tmpfile.read()
156 os.ftruncate(self.tmpfile.fileno(), 0)
160 class MicFileHandler(logging.FileHandler):
161 """ This file handler is supposed to catch the stderr output from
162 all modules even 3rd party modules involed, as it redirects
163 the stderr stream to a temp file stream, if logfile assigned,
164 it will flush the record to file stream, else it's a buffer
165 handler; once logfile assigned, the buffer will be flushed
167 def __init__(self, filename=None, mode='w', encoding=None, capacity=10):
168 # we don't use FileHandler to initialize,
169 # because filename might be expected to None
170 logging.Handler.__init__(self)
173 self.baseFilename = os.path.abspath(filename)
175 self.baseFilename = None
178 self.capacity = capacity
179 # buffering the records
182 # set formater locally
183 msg_fmt = "[%(asctime)s] %(message)s"
184 date_fmt = "%m/%d %H:%M:%S %Z"
185 self.setFormatter(logging.Formatter(fmt=msg_fmt, datefmt=date_fmt))
186 self.olderr = sys.stderr
187 self.stderr = RedirectedStderr()
190 def set_logfile(self, filename, mode='w'):
191 """ Set logfile path to make it possible flush records not-on-fly """
192 self.baseFilename = os.path.abspath(filename)
195 def redirect_stderr(self):
196 """ Start to redirect stderr for catching all error output """
197 self.stderr.redirect()
199 def restore_stderr(self):
200 """ Restore stderr stream and log the error messages to both stderr
201 and log file if error messages are not empty
203 self.stderr.restore()
204 self.errmsg = self.stderr.value
209 """ Log catched error message from stderr redirector """
213 sys.stdout.write(self.errmsg)
216 record = logging.makeLogRecord({'msg': self.errmsg})
217 self.buffer.append(record)
219 # truncate the redirector for the errors is logged
220 self.stderr.truncate()
223 def emit(self, record):
224 """ Emit the log record to Handler """
225 # if there error message catched, log it first
226 self.errmsg = self.stderr.getvalue()
230 # if no logfile assigned, it's a buffer handler
231 if not self.baseFilename:
232 self.buffer.append(record)
233 if len(self.buffer) >= self.capacity:
236 self.flushing(record)
238 def flushing(self, record=None):
239 """ Flush buffer and record to logfile """
240 # NOTE: 'flushing' can't be named 'flush' because of 'emit' calling it
241 # set file stream position to SEEK_END(=2)
243 self.stream.seek(0, 2)
244 # if bufferred, flush it
246 for arecord in self.buffer:
247 logging.FileHandler.emit(self, arecord)
249 # if recorded, flush it
251 logging.FileHandler.emit(self, record)
254 """ Close handler after flushing the buffer """
255 # if any left in buffer, flush it
258 logging.FileHandler.close(self)
261 class MicLogger(logging.Logger):
262 """ The MIC logger class, it supports interactive mode, and logs the
263 messages with specified levels tospecified stream, also can catch
264 all error messages including the involved 3rd party modules
266 def __init__(self, name, level=logging.INFO):
267 logging.Logger.__init__(self, name, level)
268 self.propagate = False
269 self.interactive = True
271 self._allhandlers = {
272 'default': logging.StreamHandler(sys.stdout),
273 'stdout': MicStreamHandler(sys.stdout),
274 'stderr': MicStreamHandler(sys.stderr),
275 'logfile': MicFileHandler(),
278 self._allhandlers['default'].addFilter(LevelFilter(['RAWTEXT']))
279 self._allhandlers['default'].setFormatter(
280 logging.Formatter(fmt="%(message)s"))
281 self.addHandler(self._allhandlers['default'])
283 self._allhandlers['stdout'].addFilter(LevelFilter(['DEBUG', 'VERBOSE',
285 self.addHandler(self._allhandlers['stdout'])
287 self._allhandlers['stderr'].addFilter(LevelFilter(['WARNING',
289 self.addHandler(self._allhandlers['stderr'])
291 self.addHandler(self._allhandlers['logfile'])
293 def set_logfile(self, filename, mode='w'):
294 """ Set logfile path """
295 self.logfile = filename
296 self._allhandlers['logfile'].set_logfile(self.logfile, mode)
298 def enable_logstderr(self):
299 """ Start to log all error messages """
301 self._allhandlers['logfile'].redirect_stderr()
303 def disable_logstderr(self):
304 """ Stop to log all error messages """
306 self._allhandlers['logfile'].restore_stderr()
308 def verbose(self, msg, *args, **kwargs):
309 """ Log a message with level VERBOSE """
310 if self.isEnabledFor(VERBOSE):
311 self._log(VERBOSE, msg, args, **kwargs)
313 def raw(self, msg, *args, **kwargs):
314 """ Log a message in raw text format """
315 if self.isEnabledFor(RAWTEXT):
316 self._log(RAWTEXT, msg, args, **kwargs)
318 def select(self, msg, optdict, default=None):
319 """ Log a message in interactive mode """
320 if not optdict.keys():
323 default = optdict.keys()[0]
324 msg += " [%s](%s): " % ('/'.join(optdict.keys()), default)
325 if not self.interactive or self.logfile:
327 self.raw(msg + reply)
330 reply = raw_input(msg).strip()
331 if not reply or reply in optdict:
335 return optdict[reply]
339 """ Log a message with level ERROR on the MIC logger """
344 """ Log a message with level WARNING on the MIC logger """
348 """ Log a message with level INFO on the MIC logger """
352 """ Log a message with level VERBOSE on the MIC logger """
356 """ Log a message with level DEBUG on the MIC logger """
360 """ Log a message on the MIC logger in raw text format"""
363 def select(msg, optdict, default=None):
364 """ Show an interactive scene in tty terminal and
365 logs them on MIC logger
367 return LOGGER.select(msg, optdict, default)
369 def choice(msg, optlist, default=0):
370 """ Give some alternatives to users for answering the question """
371 return LOGGER.select(msg, dict(zip(optlist, optlist)), optlist[default])
373 def ask(msg, ret=True):
374 """ Ask users to answer 'yes' or 'no' to the question """
375 answers = {'y': True, 'n': False}
376 default = {True: 'y', False: 'n'}[ret]
377 return LOGGER.select(msg, answers, default)
380 """ Pause for any key """
382 msg = "press ANY KEY to continue ..."
385 def set_logfile(logfile, mode='w'):
386 """ Set logfile path to the MIC logger """
387 LOGGER.set_logfile(logfile, mode)
389 def set_loglevel(level):
390 """ Set loglevel to the MIC logger """
391 if isinstance(level, basestring):
392 level = logging.getLevelName(level)
393 LOGGER.setLevel(level)
396 """ Get the loglevel of the MIC logger """
397 return logging.getLevelName(LOGGER.level)
399 def disable_interactive():
400 """ Disable the interactive mode """
401 LOGGER.interactive = False
403 def enable_interactive():
404 """ Enable the interactive mode """
405 LOGGER.interactive = True
407 def set_interactive(value):
408 """ Set the interactive mode (for compatibility) """
412 disable_interactive()
414 def enable_logstderr(fpath=None):
415 """ Start to log all error message on the MIC logger """
416 LOGGER.enable_logstderr()
418 def disable_logstderr():
419 """ Stop to log all error message on the MIC logger """
420 LOGGER.disable_logstderr()
423 # add two level to the MIC logger: 'VERBOSE', 'RAWTEXT'
424 logging.addLevelName(VERBOSE, 'VERBOSE')
425 logging.addLevelName(RAWTEXT, 'RAWTEXT')
426 # initial the MIC logger
427 logging.setLoggerClass(MicLogger)
428 LOGGER = logging.getLogger("MIC")