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 """ This logging module is fully compatible with the old msger module, and
18 it supports interactive mode, logs the messages with specified levels
19 to specified stream, can also catch all error messages including the
20 involved 3rd party modules to the logger
33 'disable_interactive',
49 # define the color constants
50 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(30, 38)
52 # color sequence for tty terminal
53 COLOR_SEQ = "\033[%dm" # pylint: disable=W1401
54 # reset sequence for tty terminal
55 RESET_SEQ = "\033[0m" # pylint: disable=W1401
61 # define colors for log levels
63 'DEBUG': COLOR_SEQ % BLUE,
64 'VERBOSE': COLOR_SEQ % MAGENTA,
65 'INFO': COLOR_SEQ % GREEN,
66 'WARNING': COLOR_SEQ % YELLOW,
67 'ERROR': COLOR_SEQ % RED,
71 class LevelFilter(logging.Filter):
72 """ A filter that selects logging message with specified level """
73 def __init__(self, levels): # pylint: disable=W0231
76 def filter(self, record):
78 return record.levelname in self._levels
82 class MicStreamHandler(logging.StreamHandler):
83 """ A stream handler that print colorized levelname in tty terminal """
84 def __init__(self, stream=None):
85 logging.StreamHandler.__init__(self, stream)
86 msg_fmt = "%(color)s%(levelname)s:%(reset)s %(message)s"
87 self.setFormatter(logging.Formatter(fmt=msg_fmt))
90 """ Check if to print in color or not """
91 in_emacs = (os.getenv("EMACS") and
92 os.getenv("INSIDE_EMACS", "").endswith(",comint"))
93 return self.stream.isatty() and not in_emacs
95 def format(self, record):
96 """ Format the logging record if need color """
97 record.color = record.reset = ""
99 record.color = COLORS[record.levelname]
100 record.reset = RESET_SEQ
101 return logging.StreamHandler.format(self, record)
104 class RedirectedStderr(object):
105 """ A faked error stream that redirect stderr to a temp file """
107 self.tmpfile = tempfile.NamedTemporaryFile()
115 """ Close the temp file and clear the buffer """
123 """ Truncate the tempfile to size zero """
125 os.ftruncate(self.tmpfile.fileno(), 0)
128 """ Redirect stderr to the temp file """
129 self.fderr = os.dup(2)
130 os.dup2(self.tmpfile.fileno(), 2)
133 """ Restore the stderr and read the bufferred data """
134 os.dup2(self.fderr, 2)
138 self.tmpfile.seek(0, 0)
139 self.value = self.tmpfile.read()
142 """ Read the bufferred data """
144 self.tmpfile.seek(0, 0)
145 self.value = self.tmpfile.read()
146 os.ftruncate(self.tmpfile.fileno(), 0)
150 class MicFileHandler(logging.FileHandler):
151 """ This file handler is supposed to catch the stderr output from
152 all modules even 3rd party modules involed, as it redirects
153 the stderr stream to a temp file stream, if logfile assigned,
154 it will flush the record to file stream, else it's a buffer
155 handler; once logfile assigned, the buffer will be flushed
157 def __init__(self, filename=None, mode='w', encoding=None, capacity=10):
158 # we don't use FileHandler to initialize,
159 # because filename might be expected to None
160 logging.Handler.__init__(self)
163 self.baseFilename = os.path.abspath(filename)
165 self.baseFilename = None
168 self.capacity = capacity
169 # buffering the records
172 # set formater locally
173 msg_fmt = "[%(asctime)s] %(message)s"
174 date_fmt = "%m/%d %H:%M:%S %Z"
175 self.setFormatter(logging.Formatter(fmt=msg_fmt, datefmt=date_fmt))
176 self.olderr = sys.stderr
177 self.stderr = RedirectedStderr()
180 def set_logfile(self, filename, mode='w'):
181 """ Set logfile path to make it possible flush records not-on-fly """
182 self.baseFilename = os.path.abspath(filename)
185 def redirect_stderr(self):
186 """ Start to redirect stderr for catching all error output """
187 self.stderr.redirect()
189 def restore_stderr(self):
190 """ Restore stderr stream and log the error messages to both stderr
191 and log file if error messages are not empty
193 self.stderr.restore()
194 self.errmsg = self.stderr.value
199 """ Log catched error message from stderr redirector """
203 sys.stdout.write(self.errmsg)
206 record = logging.makeLogRecord({'msg': self.errmsg})
207 self.buffer.append(record)
209 # truncate the redirector for the errors is logged
210 self.stderr.truncate()
213 def emit(self, record):
214 """ Emit the log record to Handler """
215 # if there error message catched, log it first
216 self.errmsg = self.stderr.getvalue()
220 # if no logfile assigned, it's a buffer handler
221 if not self.baseFilename:
222 self.buffer.append(record)
223 if len(self.buffer) >= self.capacity:
226 self.flushing(record)
228 def flushing(self, record=None):
229 """ Flush buffer and record to logfile """
230 # NOTE: 'flushing' can't be named 'flush' because of 'emit' calling it
231 # set file stream position to SEEK_END(=2)
233 self.stream.seek(0, 2)
234 # if bufferred, flush it
236 for arecord in self.buffer:
237 logging.FileHandler.emit(self, arecord)
239 # if recorded, flush it
241 logging.FileHandler.emit(self, record)
244 """ Close handler after flushing the buffer """
245 # if any left in buffer, flush it
248 logging.FileHandler.close(self)
251 class MicLogger(logging.Logger):
252 """ The MIC logger class, it supports interactive mode, and logs the
253 messages with specified levels tospecified stream, also can catch
254 all error messages including the involved 3rd party modules
256 def __init__(self, name, level=logging.INFO):
257 logging.Logger.__init__(self, name, level)
258 self.interactive = True
260 self._allhandlers = {
261 'default': logging.StreamHandler(sys.stdout),
262 'stdout': MicStreamHandler(sys.stdout),
263 'stderr': MicStreamHandler(sys.stderr),
264 'logfile': MicFileHandler(),
267 self._allhandlers['default'].addFilter(LevelFilter(['RAWTEXT']))
268 self._allhandlers['default'].setFormatter(
269 logging.Formatter(fmt="%(message)s"))
270 self.addHandler(self._allhandlers['default'])
272 self._allhandlers['stdout'].addFilter(LevelFilter(['DEBUG', 'VERBOSE',
274 self.addHandler(self._allhandlers['stdout'])
276 self._allhandlers['stderr'].addFilter(LevelFilter(['WARNING',
278 self.addHandler(self._allhandlers['stderr'])
280 self.addHandler(self._allhandlers['logfile'])
282 def set_logfile(self, filename, mode='w'):
283 """ Set logfile path """
284 self.logfile = filename
285 self._allhandlers['logfile'].set_logfile(self.logfile, mode)
287 def enable_logstderr(self):
288 """ Start to log all error messages """
290 self._allhandlers['logfile'].redirect_stderr()
292 def disable_logstderr(self):
293 """ Stop to log all error messages """
295 self._allhandlers['logfile'].restore_stderr()
297 def verbose(self, msg, *args, **kwargs):
298 """ Log a message with level VERBOSE """
299 if self.isEnabledFor(VERBOSE):
300 self._log(VERBOSE, msg, args, **kwargs)
302 def raw(self, msg, *args, **kwargs):
303 """ Log a message in raw text format """
304 if self.isEnabledFor(RAWTEXT):
305 self._log(RAWTEXT, msg, args, **kwargs)
307 def select(self, msg, optdict, default=None):
308 """ Log a message in interactive mode """
309 if not optdict.keys():
312 default = optdict.keys()[0]
313 msg += " [%s](%s): " % ('/'.join(optdict.keys()), default)
314 if not self.interactive or self.logfile:
316 self.raw(msg + reply)
319 reply = raw_input(msg).strip()
320 if not reply or reply in optdict:
324 return optdict[reply]
328 """ Log a message with level ERROR on the MIC logger """
333 """ Log a message with level WARNING on the MIC logger """
337 """ Log a message with level INFO on the MIC logger """
341 """ Log a message with level VERBOSE on the MIC logger """
342 # pylint: disable=E1103
346 """ Log a message with level DEBUG on the MIC logger """
350 """ Log a message on the MIC logger in raw text format"""
351 # pylint: disable=E1103
354 def select(msg, optdict, default=None):
355 """ Show an interactive scene in tty terminal and
356 logs them on MIC logger
358 # pylint: disable=E1103
359 return LOGGER.select(msg, optdict, default)
361 def choice(msg, optlist, default=0):
362 """ Give some alternatives to users for answering the question """
363 # pylint: disable=E1103
364 return LOGGER.select(msg, dict(zip(optlist, optlist)), optlist[default])
366 def ask(msg, ret=True):
367 """ Ask users to answer 'yes' or 'no' to the question """
368 answers = {'y': True, 'n': False}
369 default = {True: 'y', False: 'n'}[ret]
370 # pylint: disable=E1103
371 return LOGGER.select(msg, answers, default)
374 """ Pause for any key """
376 msg = "press ANY KEY to continue ..."
379 def set_logfile(logfile, mode='w'):
380 """ Set logfile path to the MIC logger """
381 # pylint: disable=E1103
382 LOGGER.set_logfile(logfile, mode)
384 def set_loglevel(level):
385 """ Set loglevel to the MIC logger """
386 if isinstance(level, basestring):
387 level = logging.getLevelName(level)
388 LOGGER.setLevel(level)
391 """ Get the loglevel of the MIC logger """
392 return logging.getLevelName(LOGGER.level)
394 def disable_interactive():
395 """ Disable the interactive mode """
396 LOGGER.interactive = False
398 def enable_interactive():
399 """ Enable the interactive mode """
400 LOGGER.interactive = True
402 def set_interactive(value):
403 """ Set the interactive mode (for compatibility) """
407 disable_interactive()
409 def enable_logstderr(fpath=None): # pylint: disable=W0613
410 """ Start to log all error message on the MIC logger """
411 # pylint: disable=E1103
412 LOGGER.enable_logstderr()
414 def disable_logstderr():
415 """ Stop to log all error message on the MIC logger """
416 # pylint: disable=E1103
417 LOGGER.disable_logstderr()
420 # add two level to the MIC logger: 'VERBOSE', 'RAWTEXT'
421 logging.addLevelName(VERBOSE, 'VERBOSE')
422 logging.addLevelName(RAWTEXT, 'RAWTEXT')
423 # initial the MIC logger
424 logging.setLoggerClass(MicLogger)
425 LOGGER = logging.getLogger("MIC")