# | |
# 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 comment has been minimized.
This comment has been minimized.
this also seems to only require ctypes fi you're using NT |
This comment has been minimized.
This comment has been minimized.
@unux: Good point. Gist updated. Thanks. |
This comment has been minimized.
This comment has been minimized.
With python 2.7, this didn't work for me on windows 64bit without "import ctypes" at the top :. Either way, thanks :D |
This comment has been minimized.
This comment has been minimized.
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. |
This comment has been minimized.
This comment has been minimized.
Hi Vítor,
I've updated the Gist to clarify in the copyright notice that the licensing is new BSD. Please ensure you update your copy with the new notice.
Regards,
Vinay Sajip
…----- Original Message -----
From: Vítor Brandão ***@***.***
To: Vinay Sajip ***@***.***
Cc:
Sent: Saturday, 14 April 2012, 23:57
Subject: Re: gist gist: 758430
I was looking for this! Thanks for sharing. I'm using your code in a [github
project](https://github.com/noiselabs/box-linux-sync) I'm developing. Is it
enough to keep the copyright notice at the top of the file? Module is
[here](https://github.com/noiselabs/box-linux-sync/blob/master/src/noiselabs/box/ansistrm.py).
---
Reply to this email directly or view it on GitHub:
https://gist.github.com/758430
|
This comment has been minimized.
This comment has been minimized.
Done. Thanks. |
This comment has been minimized.
This comment has been minimized.
Providing an
|
This comment has been minimized.
This comment has been minimized.
According to the code, the --- 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) |
This comment has been minimized.
This comment has been minimized.
Thanks for the gist, well done! |
This comment has been minimized.
This comment has been minimized.
After switching from win7 64bit to win8 64bit the following ctypes declaration was needed:
|
This comment has been minimized.
This comment has been minimized.
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()) |
This comment has been minimized.
This comment has been minimized.
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: |
This comment has been minimized.
This comment has been minimized.
Seems like info doesnt print on python3 |
This comment has been minimized.
Gist updated to use non-capturing groups. Thanks to Marius Gedminas for the suggestion.