summaryrefslogtreecommitdiff
path: root/logutils/colorize.py
diff options
context:
space:
mode:
Diffstat (limited to 'logutils/colorize.py')
-rw-r--r--logutils/colorize.py186
1 files changed, 186 insertions, 0 deletions
diff --git a/logutils/colorize.py b/logutils/colorize.py
new file mode 100644
index 0000000..3dfaad0
--- /dev/null
+++ b/logutils/colorize.py
@@ -0,0 +1,186 @@
+#
+# Copyright (C) 2010-2011 Vinay Sajip. All rights reserved.
+#
+import ctypes
+import logging
+import os
+
+class ColorizingStreamHandler(logging.StreamHandler):
+ """
+ A stream handler which supports colorizing of console streams
+ under Windows, Linux and Mac OS X.
+
+ :param strm: The stream to colorize - typically ``sys.stdout``
+ or ``sys.stderr``.
+ """
+
+ # color names to indices
+ color_map = {
+ 'black': 0,
+ 'red': 1,
+ 'green': 2,
+ 'yellow': 3,
+ 'blue': 4,
+ 'magenta': 5,
+ 'cyan': 6,
+ 'white': 7,
+ }
+
+ #levels to (background, foreground, bold/intense)
+ if os.name == 'nt':
+ level_map = {
+ logging.DEBUG: (None, 'blue', True),
+ logging.INFO: (None, 'white', False),
+ logging.WARNING: (None, 'yellow', True),
+ logging.ERROR: (None, 'red', True),
+ logging.CRITICAL: ('red', 'white', True),
+ }
+ else:
+ "Maps levels to colour/intensity settings."
+ level_map = {
+ logging.DEBUG: (None, 'blue', False),
+ logging.INFO: (None, 'black', False),
+ logging.WARNING: (None, 'yellow', False),
+ logging.ERROR: (None, 'red', False),
+ logging.CRITICAL: ('red', 'white', True),
+ }
+
+ csi = '\x1b['
+ reset = '\x1b[0m'
+
+ @property
+ def is_tty(self):
+ "Returns true if the handler's stream is a terminal."
+ isatty = getattr(self.stream, 'isatty', None)
+ return isatty and isatty()
+
+ def emit(self, record):
+ try:
+ message = self.format(record)
+ stream = self.stream
+ if not self.is_tty:
+ stream.write(message)
+ else:
+ self.output_colorized(message)
+ stream.write(getattr(self, 'terminator', '\n'))
+ self.flush()
+ except (KeyboardInterrupt, SystemExit):
+ raise
+ except:
+ self.handleError(record)
+
+ if os.name != 'nt':
+ def output_colorized(self, message):
+ """
+ Output a colorized message.
+
+ On Linux and Mac OS X, this method just writes the
+ already-colorized message to the stream, since on these
+ platforms console streams accept ANSI escape sequences
+ for colorization. On Windows, this handler implements a
+ subset of ANSI escape sequence handling by parsing the
+ message, extracting the sequences and making Win32 API
+ calls to colorize the output.
+
+ :param message: The message to colorize and output.
+ """
+ self.stream.write(message)
+ else:
+ import re
+ ansi_esc = re.compile(r'\x1b\[((?:\d+)(?:;(?:\d+))*)m')
+
+ nt_color_map = {
+ 0: 0x00, # black
+ 1: 0x04, # red
+ 2: 0x02, # green
+ 3: 0x06, # yellow
+ 4: 0x01, # blue
+ 5: 0x05, # magenta
+ 6: 0x03, # cyan
+ 7: 0x07, # white
+ }
+
+ def output_colorized(self, message):
+ """
+ Output a colorized message.
+
+ On Linux and Mac OS X, this method just writes the
+ already-colorized message to the stream, since on these
+ platforms console streams accept ANSI escape sequences
+ for colorization. On Windows, this handler implements a
+ subset of ANSI escape sequence handling by parsing the
+ message, extracting the sequences and making Win32 API
+ calls to colorize the output.
+
+ :param message: The message to colorize and output.
+ """
+ parts = self.ansi_esc.split(message)
+ write = self.stream.write
+ h = None
+ fd = getattr(self.stream, 'fileno', None)
+ if fd is not None:
+ fd = fd()
+ if fd in (1, 2): # stdout or stderr
+ h = ctypes.windll.kernel32.GetStdHandle(-10 - fd)
+ while parts:
+ text = parts.pop(0)
+ if text:
+ write(text)
+ if parts:
+ params = parts.pop(0)
+ if h is not None:
+ params = [int(p) for p in params.split(';')]
+ color = 0
+ for p in params:
+ if 40 <= p <= 47:
+ color |= self.nt_color_map[p - 40] << 4
+ elif 30 <= p <= 37:
+ color |= self.nt_color_map[p - 30]
+ elif p == 1:
+ color |= 0x08 # foreground intensity on
+ elif p == 0: # reset to default color
+ color = 0x07
+ else:
+ pass # error condition ignored
+ ctypes.windll.kernel32.SetConsoleTextAttribute(h, color)
+
+ def colorize(self, message, record):
+ """
+ Colorize a message for a logging event.
+
+ This implementation uses the ``level_map`` class attribute to
+ map the LogRecord's level to a colour/intensity setting, which is
+ then applied to the whole message.
+
+ :param message: The message to colorize.
+ :param record: The ``LogRecord`` for the message.
+ """
+ if record.levelno in self.level_map:
+ bg, fg, bold = self.level_map[record.levelno]
+ params = []
+ if bg in self.color_map:
+ params.append(str(self.color_map[bg] + 40))
+ if fg in self.color_map:
+ params.append(str(self.color_map[fg] + 30))
+ if bold:
+ params.append('1')
+ if params:
+ message = ''.join((self.csi, ';'.join(params),
+ 'm', message, self.reset))
+ return message
+
+ def format(self, record):
+ """
+ Formats a record for output.
+
+ This implementation colorizes the message line, but leaves
+ any traceback unolorized.
+ """
+ message = logging.StreamHandler.format(self, record)
+ if self.is_tty:
+ # Don't colorize any traceback
+ parts = message.split('\n', 1)
+ parts[0] = self.colorize(parts[0], record)
+ message = '\n'.join(parts)
+ return message
+