fix mic run error in mic-bootstrap
[tools/mic.git] / mic / msger.py
1 #
2 # Copyright (c) 2013 Intel, Inc.
3 #
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
7 #
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
11 # for more details.
12 #
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.
16
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
27
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
32 """
33 import os
34 import sys
35 import logging
36 import tempfile
37
38 __ALL__ = [
39     'get_loglevel',
40     'set_loglevel',
41     'set_logfile',
42     'enable_interactive',
43     'disable_interactive',
44     'enable_logstderr',
45     'disable_logstderr',
46     'raw',
47     'debug',
48     'verbose',
49     'info',
50     'warning',
51     'error',
52     'select',
53     'choice',
54     'ask',
55     'pause',
56 ]
57
58
59 # define the color constants
60 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = list(range(30, 38))
61
62 # color sequence for tty terminal
63 COLOR_SEQ = "\033[%dm" 
64 # reset sequence for tty terminal
65 RESET_SEQ = "\033[0m"
66
67 # new log level
68 RAWTEXT = 25
69 VERBOSE = 15
70
71 # define colors for log levels
72 COLORS = {
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,
78 }
79
80
81 class LevelFilter(logging.Filter):
82     """ A filter that selects logging message with specified level """
83     def __init__(self, levels):
84         self._levels = levels
85
86     def filter(self, record):
87         if self._levels:
88             return record.levelname in self._levels
89         return False
90
91
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))
98
99     def _use_color(self):
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
104
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)
112
113
114 class RedirectedStderr(object):
115     """ A faked error stream that redirect stderr to a temp file """
116     def __init__(self):
117         self.tmpfile = tempfile.NamedTemporaryFile()
118         self.fderr = None
119         self.value = None
120
121     def __del__(self):
122         self.close()
123
124     def close(self):
125         """ Close the temp file and clear the buffer """
126         try:
127             self.value = None
128             self.tmpfile.close()
129         except OSError:
130             pass
131
132     def truncate(self):
133         """ Truncate the tempfile to size zero """
134         if self.tmpfile:
135             os.ftruncate(self.tmpfile.fileno(), 0)
136             os.lseek(self.tmpfile.fileno(), 0, os.SEEK_SET)
137
138     def redirect(self):
139         """ Redirect stderr to the temp file """
140         self.fderr = os.dup(2)
141         os.dup2(self.tmpfile.fileno(), 2)
142
143     def restore(self):
144         """ Restore the stderr and read the bufferred data """
145         os.dup2(self.fderr, 2)
146         self.fderr = None
147
148         if self.tmpfile:
149             self.tmpfile.seek(0, 0)
150             self.value = self.tmpfile.read()
151
152     def getvalue(self):
153         """ Read the bufferred data """
154         if self.tmpfile:
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)
159             return self.value
160         return None
161
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
168     """
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)
173         self._builtin_open = open
174         self.errors = None
175         self.stream = None
176         if filename:
177             self.baseFilename = os.path.abspath(filename)
178         else:
179             self.baseFilename = None
180         self.mode = mode
181         self.encoding = None
182         self.capacity = capacity
183         # buffering the records
184         self.buffer = []
185
186         # set formater locally
187         msg_fmt = "[%(asctime)s] %(message)s"
188         date_fmt = "%m/%d %H:%M:%S %Z"
189         self.setFormatter(logging.Formatter(fmt=msg_fmt, datefmt=date_fmt))
190         self.olderr = sys.stderr
191         self.stderr = RedirectedStderr()
192         self.errmsg = None
193
194     def set_logfile(self, filename, mode='w'):
195         """ Set logfile path to make it possible flush records not-on-fly """
196         self.baseFilename = os.path.abspath(filename)
197         self.mode = mode
198
199     def redirect_stderr(self):
200         """ Start to redirect stderr for catching all error output """
201         self.stderr.redirect()
202
203     def restore_stderr(self):
204         """ Restore stderr stream and log the error messages to both stderr
205             and log file if error messages are not empty
206         """
207         self.stderr.restore()
208         self.errmsg = self.stderr.value
209         if self.errmsg:
210             self.logstderr()
211
212     def logstderr(self):
213         """ Log catched error message from stderr redirector """
214         if not self.errmsg:
215             return
216
217         sys.stdout.write(self.errmsg)
218         sys.stdout.flush()
219
220         record = logging.makeLogRecord({'msg': self.errmsg})
221         self.buffer.append(record)
222
223         # truncate the redirector for the errors is logged
224         self.stderr.truncate()
225         self.errmsg = None
226
227     def emit(self, record):
228         """ Emit the log record to Handler """
229         # if there error message catched, log it first
230         self.errmsg = self.stderr.getvalue()
231         if self.errmsg:
232             self.logstderr()
233
234         # if no logfile assigned, it's a buffer handler
235         if not self.baseFilename:
236             self.buffer.append(record)
237             if len(self.buffer) >= self.capacity:
238                 self.buffer = []
239         else:
240             self.flushing(record)
241
242     def flushing(self, record=None):
243         """ Flush buffer and record to logfile """
244         # NOTE: 'flushing' can't be named 'flush' because of 'emit' calling it
245         # set file stream position to SEEK_END(=2)
246         if self.stream:
247             self.stream.seek(0, 2)
248         # if bufferred, flush it
249         if self.buffer:
250             for arecord in self.buffer:
251                 logging.FileHandler.emit(self, arecord)
252             self.buffer = []
253         # if recorded, flush it
254         if record:
255             logging.FileHandler.emit(self, record)
256
257     def close(self):
258         """ Close handler after flushing the buffer """
259         # if any left in buffer, flush it
260         if self.stream:
261             self.flushing()
262         logging.FileHandler.close(self)
263
264
265 class MicLogger(logging.Logger):
266     """ The MIC logger class, it supports interactive mode, and logs the
267         messages with specified levels tospecified stream, also can catch
268         all error messages including the involved 3rd party modules
269     """
270     def __init__(self, name, level=logging.INFO):
271         logging.Logger.__init__(self, name, level)
272         self.propagate = False
273         self.interactive = True
274         self.logfile = None
275         self._allhandlers = {
276             'default': logging.StreamHandler(sys.stdout),
277             'stdout': MicStreamHandler(sys.stdout),
278             'stderr': MicStreamHandler(sys.stderr),
279             'logfile': MicFileHandler(),
280         }
281
282         self._allhandlers['default'].addFilter(LevelFilter(['RAWTEXT']))
283         self._allhandlers['default'].setFormatter(
284             logging.Formatter(fmt="%(message)s"))
285         self.addHandler(self._allhandlers['default'])
286
287         self._allhandlers['stdout'].addFilter(LevelFilter(['DEBUG', 'VERBOSE',
288                                                           'INFO']))
289         self.addHandler(self._allhandlers['stdout'])
290
291         self._allhandlers['stderr'].addFilter(LevelFilter(['WARNING',
292                                                            'ERROR']))
293         self.addHandler(self._allhandlers['stderr'])
294
295         self.addHandler(self._allhandlers['logfile'])
296
297     def set_logfile(self, filename, mode='w'):
298         """ Set logfile path """
299         self.logfile = filename
300         self._allhandlers['logfile'].set_logfile(self.logfile, mode)
301
302     def enable_logstderr(self):
303         """ Start to log all error messages """
304         if self.logfile:
305             self._allhandlers['logfile'].redirect_stderr()
306
307     def disable_logstderr(self):
308         """ Stop to log all error messages """
309         if self.logfile:
310             self._allhandlers['logfile'].restore_stderr()
311
312     def verbose(self, msg, *args, **kwargs):
313         """ Log a message with level VERBOSE """
314         if self.isEnabledFor(VERBOSE):
315             self._log(VERBOSE, msg, args, **kwargs)
316
317     def raw(self, msg, *args, **kwargs):
318         """ Log a message in raw text format """
319         if self.isEnabledFor(RAWTEXT):
320             self._log(RAWTEXT, msg, args, **kwargs)
321
322     def select(self, msg, optdict, default=None):
323         """ Log a message in interactive mode """
324         if not list(optdict.keys()):
325             return default
326         if default is None:
327             default = list(optdict.keys())[0]
328         msg += " [%s](%s): " % ('/'.join(list(optdict.keys())), default)
329         if not self.interactive or self.logfile:
330             reply = default
331             self.raw(msg + reply)
332         else:
333             while True:
334                 reply = input(msg).strip()
335                 if not reply or reply in optdict:
336                     break
337             if not reply:
338                 reply = default
339         return optdict[reply]
340
341
342 def error(msg):
343     """ Log a message with level ERROR on the MIC logger """
344     LOGGER.error(msg)
345     sys.exit(2)
346
347 def warning(msg):
348     """ Log a message with level WARNING on the MIC logger """
349     LOGGER.warning(msg)
350
351 def info(msg):
352     """ Log a message with level INFO on the MIC logger """
353     LOGGER.info(msg)
354
355 def verbose(msg):
356     """ Log a message with level VERBOSE on the MIC logger """
357     LOGGER.verbose(msg)
358
359 def debug(msg):
360     """ Log a message with level DEBUG on the MIC logger """
361     LOGGER.debug(msg)
362
363 def raw(msg):
364     """ Log a message on the MIC logger in raw text format"""
365     LOGGER.raw(msg)
366
367 def select(msg, optdict, default=None):
368     """ Show an interactive scene in tty terminal and
369         logs them on MIC logger
370     """
371     return LOGGER.select(msg, optdict, default)
372
373 def choice(msg, optlist, default=0):
374     """ Give some alternatives to users for answering the question """
375     return LOGGER.select(msg, dict(list(zip(optlist, optlist))), optlist[default])
376
377 def ask(msg, ret=True):
378     """ Ask users to answer 'yes' or 'no' to the question """
379     answers = {'y': True, 'n': False}
380     default = {True: 'y', False: 'n'}[ret]
381     return LOGGER.select(msg, answers, default)
382
383 def pause(msg=None):
384     """ Pause for any key """
385     if msg is None:
386         msg = "press ANY KEY to continue ..."
387     eval(input(msg))
388
389 def set_logfile(logfile, mode='w'):
390     """ Set logfile path to the MIC logger """
391     LOGGER.set_logfile(logfile, mode)
392
393 def set_loglevel(level):
394     """ Set loglevel to the MIC logger """
395     if isinstance(level, str):
396         level = logging.getLevelName(level)
397     LOGGER.setLevel(level)
398
399 def get_loglevel():
400     """ Get the loglevel of the MIC logger """
401     return logging.getLevelName(LOGGER.level)
402
403 def disable_interactive():
404     """ Disable the interactive mode """
405     LOGGER.interactive = False
406
407 def enable_interactive():
408     """ Enable the interactive mode """
409     LOGGER.interactive = True
410
411 def set_interactive(value):
412     """ Set the interactive mode (for compatibility) """
413     if value:
414         enable_interactive()
415     else:
416         disable_interactive()
417
418 def enable_logstderr(fpath=None):
419     """ Start to log all error message on the MIC logger """
420     LOGGER.enable_logstderr()
421
422 def disable_logstderr():
423     """ Stop to log all error message on the MIC logger """
424     LOGGER.disable_logstderr()
425
426
427 # add two level to the MIC logger: 'VERBOSE', 'RAWTEXT'
428 logging.addLevelName(VERBOSE, 'VERBOSE')
429 logging.addLevelName(RAWTEXT, 'RAWTEXT')
430 # initial the MIC logger
431 logging.setLoggerClass(MicLogger)
432 LOGGER = logging.getLogger("MIC")