-
-
Save vsajip/758430 to your computer and use it in GitHub Desktop.
# | |
# Copyright (C) 2010-2012 Vinay Sajip. All rights reserved. Licensed under the new BSD license. | |
# | |
import ctypes | |
import logging | |
import os | |
class ColorizingStreamHandler(logging.StreamHandler): | |
# 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: | |
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): | |
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): | |
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): | |
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): | |
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): | |
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 | |
def main(): | |
root = logging.getLogger() | |
root.setLevel(logging.DEBUG) | |
root.addHandler(ColorizingStreamHandler()) | |
logging.debug('DEBUG') | |
logging.info('INFO') | |
logging.warning('WARNING') | |
logging.error('ERROR') | |
logging.critical('CRITICAL') | |
if __name__ == '__main__': | |
main() |
this also seems to only require ctypes fi you're using NT
@unux: Good point. Gist updated. Thanks.
With python 2.7, this didn't work for me on windows 64bit without "import ctypes" at the top :. Either way, thanks :D
I was looking for this! Thanks for sharing. I'm using your code in a github project I'm developing. Is it enough to keep the copyright notice at the top of the file? Module is here.
Done. Thanks.
Providing an __init__()
will facilitate the customization when using dictConfig()
def __init__(self, level_map=None, *args, **kwargs):
if level_map is not None:
self.level_map = level_map
logging.StreamHandler.__init__(self, *args, **kwargs)
According to the code, the logging.INFO
level seems to be the terminal default, if you comment it or set all params to None
you do not have to make a special case for nt
:
--- ansistrm.py.orig 2012-10-24 15:52:45.051056546 +0200
+++ ansistrm.py 2012-10-24 15:57:06.599067746 +0200
@@ -19,25 +19,21 @@
}
#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:
- 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),
- }
+ level_map = {
+ logging.DEBUG: (None, 'blue', False),
+ logging.INFO: (None, None, False),
+ logging.WARNING: (None, 'yellow', False),
+ logging.ERROR: (None, 'red', False),
+ logging.CRITICAL: ('red', 'white', True),
+ }
csi = '\x1b['
reset = '\x1b[0m'
+ def __init__(self, level_map=None, *args, **kwargs):
+ if level_map is not None:
+ self.level_map = level_map
+ logging.StreamHandler.__init__(self, *args, **kwargs)
+
@property
def is_tty(self):
isatty = getattr(self.stream, 'isatty', None)
Thanks for the gist, well done!
On my terminal (gnome 3.4.1.1) white you specified in the color_map is 'normal white' but not the one the terminal actually uses ('bright white'), so I had to change in the color_map to 'white':9
instead of original 7
. Value 9
should mean the default text color.
After switching from win7 64bit to win8 64bit the following ctypes declaration was needed:
import ctypes
ctypes.windll.kernel32.SetConsoleTextAttribute.argtypes = [ctypes.c_ulong, ctypes.c_ushort]
for python3 support:
import sys
if os.name != 'nt':
def output_colorized(self, message):
if sys.version[0] == '2':
self.stream.write(message)
else:
self.stream.write(message.decode())
For Win10 color support, I found a flush was needed:
diff --git a/ansistrm.py b/ansistrm.py
index a8ef384..e681aeb 100644
--- a/ansistrm.py
+++ b/ansistrm.py
@@ -89,6 +89,7 @@ class ColorizingStreamHandler(logging.StreamHandler):
text = parts.pop(0)
if text:
write(text)
+ self.stream.flush()
if parts:
params = parts.pop(0)
if h is not None:
Seems like info doesnt print on python3
Gist updated to use non-capturing groups. Thanks to Marius Gedminas for the suggestion.