From 1d077fe34ed53377a8d9e0101bdde9da4ef603f4 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Mon, 31 Oct 2011 19:01:39 -0400 Subject: [PATCH 1/4] better logging formatter with support for color --- overviewer.py | 23 ++++++++- overviewer_core/util.py | 106 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 2 deletions(-) diff --git a/overviewer.py b/overviewer.py index 6992fdc..ee4284d 100755 --- a/overviewer.py +++ b/overviewer.py @@ -49,8 +49,6 @@ import time import logging from overviewer_core import util -logging.basicConfig(level=logging.INFO,format="%(asctime)s [%(levelname)s] %(message)s") - this_dir = util.get_program_path() # make sure the c_overviewer extension is available @@ -108,8 +106,29 @@ from overviewer_core import googlemap, rendernode helptext = """ %prog [OPTIONS] """ +def configure_logger(): + # Configure the root logger to our liking + logger = logging.getLogger() + handler = logging.StreamHandler(sys.stderr) + if platform.system() == 'Windows': + # Windows logging for windows terminals + # TODO (this is a placeholder) + formatter = util.DumbFormatter() + elif sys.stderr.isatty(): + # terminal logging with ANSI color + formatter = util.ANSIColorFormatter() + else: + # Let's not assume anything. Just text. + formatter = util.DumbFormatter() + + handler.setFormatter(formatter) + logger.addHandler(handler) + logger.setLevel(logging.INFO) def main(): + + configure_logger() + try: cpus = multiprocessing.cpu_count() except NotImplementedError: diff --git a/overviewer_core/util.py b/overviewer_core/util.py index 2e51a55..dec8031 100644 --- a/overviewer_core/util.py +++ b/overviewer_core/util.py @@ -22,6 +22,7 @@ import os import os.path import sys from subprocess import Popen, PIPE +import logging def get_program_path(): if hasattr(sys, "frozen") or imp.is_frozen("__main__"): @@ -74,3 +75,108 @@ def findGitVersion(): return overviewer_version.VERSION except Exception: return "unknown" + + +# Logging related classes are below + +# Some cool code for colored logging: +# For background, add 40. For foreground, add 30 +BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) + +RESET_SEQ = "\033[0m" +COLOR_SEQ = "\033[1;%dm" +BOLD_SEQ = "\033[1m" + +COLORIZE = { + #'INFO': WHITE, + 'DEBUG': BLUE, +} +HIGHLIGHT = { + 'CRITICAL': RED, + 'ERROR': RED, + 'WARNING': YELLOW, +} + +class HighlightingFormatter(logging.Formatter): + """Base class of our custom formatter + + """ + fmtstr = '%(fileandlineno)-18s:PID(%(pid)s):%(asctime)s ' \ + '%(levelname)-8s %(message)s' + datefmt = "%H:%M:%S" + funcName_len = 15 + + def __init__(self): + logging.Formatter.__init__(self, self.fmtstr, self.datefmt) + + def format(self, record): + """Add a few extra options to the record + + pid + The process ID + + fileandlineno + A combination filename:linenumber string, so it can be justified as + one entry in a format string. + + funcName + The function name truncated/padded to a fixed width characters + + """ + record.pid = os.getpid() + record.fileandlineno = "%s:%s" % (record.filename, record.lineno) + + # Set the max length for the funcName field, and left justify + l = self.funcName_len + record.funcName = ("%-" + str(l) + 's') % record.funcName[:l] + + return self.highlight(record) + + def highlight(self, record): + """This method applies any special effects such as colorization. It + should modify the records in the record object, and should return the + *formatted line*. This probably involves calling + logging.Formatter.format() + + Override this in subclasses + + """ + return logging.Formatter.format(self, record) + +class DumbFormatter(HighlightingFormatter): + """Formatter for dumb terminals that don't support color, or log files. + Prints a bunch of stars before a highlighted line. + + """ + def highlight(self, record): + if record.levelname in HIGHLIGHT: + line = logging.Formatter.format(self, record) + line = "*" * min(79,len(line)) + "\n" + line + return line + else: + return super(DumbFormatter, self).highlight(record) + +class ANSIColorFormatter(HighlightingFormatter): + """Highlights and colorizes log entries with ANSI escape sequences + + """ + def highlight(self, record): + if record.levelname in COLORIZE: + # Colorize just the levelname + # left justify again because the color sequence bumps the length up + # above 8 chars + levelname_color = COLOR_SEQ % (30 + COLORIZE[record.levelname]) + \ + "%-8s" % record.levelname + RESET_SEQ + record.levelname = levelname_color + return logging.Formatter.format(self, record) + + elif record.levelname in HIGHLIGHT: + # Colorize the entire line + line = logging.Formatter.format(self, record) + line = COLOR_SEQ % (40 + HIGHLIGHT[record.levelname]) + line + \ + RESET_SEQ + return line + + else: + # No coloring if it's not to be highlighted or colored + return logging.Formatter.format(self, record) From 6a67841b44d758c8602041016e2c960bc3d45727 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Mon, 31 Oct 2011 22:33:49 -0400 Subject: [PATCH 2/4] Windows code for fancy colors --- overviewer.py | 8 +-- overviewer_core/util.py | 131 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 133 insertions(+), 6 deletions(-) diff --git a/overviewer.py b/overviewer.py index ee4284d..94365a1 100755 --- a/overviewer.py +++ b/overviewer.py @@ -109,11 +109,11 @@ helptext = """ def configure_logger(): # Configure the root logger to our liking logger = logging.getLogger() - handler = logging.StreamHandler(sys.stderr) + handler = util.OverviewerHandler(sys.stdout) if platform.system() == 'Windows': - # Windows logging for windows terminals - # TODO (this is a placeholder) - formatter = util.DumbFormatter() + # Our custom OverviewerHandler knows how to deal with select + # ANSI color escape sequences + formatter = util.ANSIColorFormatter() elif sys.stderr.isatty(): # terminal logging with ANSI color formatter = util.ANSIColorFormatter() diff --git a/overviewer_core/util.py b/overviewer_core/util.py index dec8031..61a1f66 100644 --- a/overviewer_core/util.py +++ b/overviewer_core/util.py @@ -23,6 +23,9 @@ import os.path import sys from subprocess import Popen, PIPE import logging +from cStringIO import StringIO +import ctypes +import platform def get_program_path(): if hasattr(sys, "frozen") or imp.is_frozen("__main__"): @@ -87,9 +90,21 @@ RESET_SEQ = "\033[0m" COLOR_SEQ = "\033[1;%dm" BOLD_SEQ = "\033[1m" +# Windows colors, taken from WinCon.h +FOREGROUND_BLUE = 0x01 +FOREGROUND_GREEN = 0x02 +FOREGROUND_RED = 0x04 +FOREGROUND_BOLD = 0x08 +FOREGROUND_WHITE = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED + +BACKGROUND_BLACK = 0x00 +BACKGROUND_BLUE = 0x10 +BACKGROUND_GREEN = 0x20 +BACKGROUND_RED = 0x40 + COLORIZE = { - #'INFO': WHITE, - 'DEBUG': BLUE, + #'INFO': WHITe, + 'DEBUG': CYAN, } HIGHLIGHT = { 'CRITICAL': RED, @@ -97,6 +112,117 @@ HIGHLIGHT = { 'WARNING': YELLOW, } + +class OverviewerHandler(logging.Handler): + def __init__(self, stream=sys.stderr): + logging.Handler.__init__(self) + self.stream = stream + + # go go gadget ctypes + if platform.system() == 'Windows': + self.GetStdHandle = ctypes.windll.kernel32.GetStdHandle + self.SetConsoleTextAttribute = ctypes.windll.kernel32.SetConsoleTextAttribute + self.STD_OUTPUT_HANDLE = ctypes.c_int(0xFFFFFFF5) + self.output_handle = self.GetStdHandle(self.STD_OUTPUT_HANDLE) + if self.output_handle == 0xFFFFFFFF: + raise Exception("Something failed in WindowsColorFormatter") + + + # default is white text on a black background + self.currentForeground = FOREGROUND_WHITE + self.currentBackground = BACKGROUND_BLACK + self.currentBold = 0 + + def updateWinColor(self, Fore=None, Back=None, Bold=False): + if Fore != None: self.currentForeground = Fore + if Back != None: self.currentBackground = Back + if Bold: + self.currentBold = FOREGROUND_BOLD + else: + self.currentBold = 0 + + self.SetConsoleTextAttribute(self.output_handle, + ctypes.c_int(self.currentForeground | self.currentBackground | self.currentBold)) + + def emit(self, record): + msg = str(self.format(record)) + + msg_strm = StringIO(msg) + + # only on Windows do we do this fancy ANSI parsing magic + if platform.system() == 'Windows': + while (True): + c = msg_strm.read(1) + if c == '': break + if c == '\033': + c1 = msg_strm.read(1) + if c1 != '[': # + sys.stream.write(c + c1) + continue + c2 = msg_strm.read(2) + if c2 == "0m": # RESET_SEQ + self.updateWinColor(Fore=FOREGROUND_WHITE, Back=BACKGROUND_BLACK) + + elif c2 == "1;": + color = "" + while(True): + nc = msg_strm.read(1) + if nc == 'm': break + color += nc + color = int(color) + if (color >= 40): # background + color = color - 40 + if color == BLACK: + self.updateWinColor(Back=BACKGROUND_BLACK) + if color == RED: + self.updateWinColor(Back=BACKGROUND_RED) + elif color == GREEN: + self.updateWinColor(Back=BACKGROUND_GREEN) + elif color == YELLOW: + self.updateWinColor(Back=BACKGROUND_RED | BACKGROUND_GREEN) + elif color == BLUE: + self.updateWinColor(Back=BACKGROUND_BLUE) + elif color == MAGENTA: + self.updateWinColor(Back=BACKGROUND_RED | BACKGROUND_BLUE) + elif color == CYAN: + self.updateWinColor(Back=BACKGROUND_GREEN | BACKGROUND_BLUE) + elif color == WHITE: + self.updateWinColor(Back=BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE) + elif (color >= 30): # foreground + color = color - 30 + if color == BLACK: + self.updateWinColor(Fore=FOREGROUND_BLACK) + if color == RED: + self.updateWinColor(Fore=FOREGROUND_RED) + elif color == GREEN: + self.updateWinColor(Fore=FOREGROUND_GREEN) + elif color == YELLOW: + self.updateWinColor(Fore=FOREGROUND_RED | FOREGROUND_GREEN) + elif color == BLUE: + self.updateWinColor(Fore=FOREGROUND_BLUE) + elif color == MAGENTA: + self.updateWinColor(Fore=FOREGROUND_RED | FOREGROUND_BLUE) + elif color == CYAN: + self.updateWinColor(Fore=FOREGROUND_GREEN | FOREGROUND_BLUE) + elif color == WHITE: + self.updateWinColor(Fore=FOREGROUND_WHITE) + + + + elif c2 == "1m": # BOLD_SEQ + pass + + else: + self.stream.write(c) + else: + self.stream.write(msg) + + self.stream.write("\n") + + + def flush(self): + self.stream.flush() + class HighlightingFormatter(logging.Formatter): """Base class of our custom formatter @@ -156,6 +282,7 @@ class DumbFormatter(HighlightingFormatter): else: return super(DumbFormatter, self).highlight(record) + class ANSIColorFormatter(HighlightingFormatter): """Highlights and colorizes log entries with ANSI escape sequences From 470440563c011dfbaa3bf3221f54721831d0c865 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Tue, 1 Nov 2011 00:03:32 -0400 Subject: [PATCH 3/4] changed windows logging to be a special output stream --- overviewer.py | 15 ++-- overviewer_core/util.py | 156 ++++++++++++++++++++-------------------- 2 files changed, 87 insertions(+), 84 deletions(-) diff --git a/overviewer.py b/overviewer.py index 94365a1..dc3a05c 100755 --- a/overviewer.py +++ b/overviewer.py @@ -107,20 +107,25 @@ helptext = """ %prog [OPTIONS] """ def configure_logger(): - # Configure the root logger to our liking - logger = logging.getLogger() - handler = util.OverviewerHandler(sys.stdout) + "Configures the root logger to our liking" + + outstream = sys.stderr if platform.system() == 'Windows': - # Our custom OverviewerHandler knows how to deal with select - # ANSI color escape sequences + # Our custom output stream processor knows how to deal with select ANSI + # color escape sequences + outstream = util.WindowsOutputStream() formatter = util.ANSIColorFormatter() + elif sys.stderr.isatty(): # terminal logging with ANSI color formatter = util.ANSIColorFormatter() + else: # Let's not assume anything. Just text. formatter = util.DumbFormatter() + logger = logging.getLogger() + handler = logging.StreamHandler(outstream) handler.setFormatter(formatter) logger.addHandler(handler) logger.setLevel(logging.INFO) diff --git a/overviewer_core/util.py b/overviewer_core/util.py index 61a1f66..5e758df 100644 --- a/overviewer_core/util.py +++ b/overviewer_core/util.py @@ -113,19 +113,22 @@ HIGHLIGHT = { } -class OverviewerHandler(logging.Handler): - def __init__(self, stream=sys.stderr): - logging.Handler.__init__(self) - self.stream = stream +class WindowsOutputStream(object): + """A file-like object that proxies sys.stderr and interprets simple ANSI + escape codes for color, translating them to the appropriate Windows calls. + + """ + def __init__(self, stream=None): + assert platform.system() == 'Windows' + self.stream = stream or sys.stderr # go go gadget ctypes - if platform.system() == 'Windows': - self.GetStdHandle = ctypes.windll.kernel32.GetStdHandle - self.SetConsoleTextAttribute = ctypes.windll.kernel32.SetConsoleTextAttribute - self.STD_OUTPUT_HANDLE = ctypes.c_int(0xFFFFFFF5) - self.output_handle = self.GetStdHandle(self.STD_OUTPUT_HANDLE) - if self.output_handle == 0xFFFFFFFF: - raise Exception("Something failed in WindowsColorFormatter") + self.GetStdHandle = ctypes.windll.kernel32.GetStdHandle + self.SetConsoleTextAttribute = ctypes.windll.kernel32.SetConsoleTextAttribute + self.STD_OUTPUT_HANDLE = ctypes.c_int(0xFFFFFFF5) + self.output_handle = self.GetStdHandle(self.STD_OUTPUT_HANDLE) + if self.output_handle == 0xFFFFFFFF: + raise Exception("Something failed in WindowsColorFormatter") # default is white text on a black background @@ -144,78 +147,73 @@ class OverviewerHandler(logging.Handler): self.SetConsoleTextAttribute(self.output_handle, ctypes.c_int(self.currentForeground | self.currentBackground | self.currentBold)) - def emit(self, record): - msg = str(self.format(record)) + def write(self, s): - msg_strm = StringIO(msg) + msg_strm = StringIO(s) - # only on Windows do we do this fancy ANSI parsing magic - if platform.system() == 'Windows': - while (True): - c = msg_strm.read(1) - if c == '': break - if c == '\033': - c1 = msg_strm.read(1) - if c1 != '[': # - sys.stream.write(c + c1) - continue - c2 = msg_strm.read(2) - if c2 == "0m": # RESET_SEQ - self.updateWinColor(Fore=FOREGROUND_WHITE, Back=BACKGROUND_BLACK) + while (True): + c = msg_strm.read(1) + if c == '': break + if c == '\033': + c1 = msg_strm.read(1) + if c1 != '[': # + sys.stream.write(c + c1) + continue + c2 = msg_strm.read(2) + if c2 == "0m": # RESET_SEQ + self.updateWinColor(Fore=FOREGROUND_WHITE, Back=BACKGROUND_BLACK) - elif c2 == "1;": - color = "" - while(True): - nc = msg_strm.read(1) - if nc == 'm': break - color += nc - color = int(color) - if (color >= 40): # background - color = color - 40 - if color == BLACK: - self.updateWinColor(Back=BACKGROUND_BLACK) - if color == RED: - self.updateWinColor(Back=BACKGROUND_RED) - elif color == GREEN: - self.updateWinColor(Back=BACKGROUND_GREEN) - elif color == YELLOW: - self.updateWinColor(Back=BACKGROUND_RED | BACKGROUND_GREEN) - elif color == BLUE: - self.updateWinColor(Back=BACKGROUND_BLUE) - elif color == MAGENTA: - self.updateWinColor(Back=BACKGROUND_RED | BACKGROUND_BLUE) - elif color == CYAN: - self.updateWinColor(Back=BACKGROUND_GREEN | BACKGROUND_BLUE) - elif color == WHITE: - self.updateWinColor(Back=BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE) - elif (color >= 30): # foreground - color = color - 30 - if color == BLACK: - self.updateWinColor(Fore=FOREGROUND_BLACK) - if color == RED: - self.updateWinColor(Fore=FOREGROUND_RED) - elif color == GREEN: - self.updateWinColor(Fore=FOREGROUND_GREEN) - elif color == YELLOW: - self.updateWinColor(Fore=FOREGROUND_RED | FOREGROUND_GREEN) - elif color == BLUE: - self.updateWinColor(Fore=FOREGROUND_BLUE) - elif color == MAGENTA: - self.updateWinColor(Fore=FOREGROUND_RED | FOREGROUND_BLUE) - elif color == CYAN: - self.updateWinColor(Fore=FOREGROUND_GREEN | FOREGROUND_BLUE) - elif color == WHITE: - self.updateWinColor(Fore=FOREGROUND_WHITE) + elif c2 == "1;": + color = "" + while(True): + nc = msg_strm.read(1) + if nc == 'm': break + color += nc + color = int(color) + if (color >= 40): # background + color = color - 40 + if color == BLACK: + self.updateWinColor(Back=BACKGROUND_BLACK) + if color == RED: + self.updateWinColor(Back=BACKGROUND_RED) + elif color == GREEN: + self.updateWinColor(Back=BACKGROUND_GREEN) + elif color == YELLOW: + self.updateWinColor(Back=BACKGROUND_RED | BACKGROUND_GREEN) + elif color == BLUE: + self.updateWinColor(Back=BACKGROUND_BLUE) + elif color == MAGENTA: + self.updateWinColor(Back=BACKGROUND_RED | BACKGROUND_BLUE) + elif color == CYAN: + self.updateWinColor(Back=BACKGROUND_GREEN | BACKGROUND_BLUE) + elif color == WHITE: + self.updateWinColor(Back=BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE) + elif (color >= 30): # foreground + color = color - 30 + if color == BLACK: + self.updateWinColor(Fore=FOREGROUND_BLACK) + if color == RED: + self.updateWinColor(Fore=FOREGROUND_RED) + elif color == GREEN: + self.updateWinColor(Fore=FOREGROUND_GREEN) + elif color == YELLOW: + self.updateWinColor(Fore=FOREGROUND_RED | FOREGROUND_GREEN) + elif color == BLUE: + self.updateWinColor(Fore=FOREGROUND_BLUE) + elif color == MAGENTA: + self.updateWinColor(Fore=FOREGROUND_RED | FOREGROUND_BLUE) + elif color == CYAN: + self.updateWinColor(Fore=FOREGROUND_GREEN | FOREGROUND_BLUE) + elif color == WHITE: + self.updateWinColor(Fore=FOREGROUND_WHITE) - - - elif c2 == "1m": # BOLD_SEQ - pass + - else: - self.stream.write(c) - else: - self.stream.write(msg) + elif c2 == "1m": # BOLD_SEQ + pass + + else: + self.stream.write(c) self.stream.write("\n") @@ -284,7 +282,7 @@ class DumbFormatter(HighlightingFormatter): class ANSIColorFormatter(HighlightingFormatter): - """Highlights and colorizes log entries with ANSI escape sequences + """Uses ANSI escape sequences to enable GLORIOUS EXTRA-COLOR! """ def highlight(self, record): From 1b9a40e183de1e00b06789b83e9b2aa9c4f4a549 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Tue, 1 Nov 2011 10:29:58 -0400 Subject: [PATCH 4/4] Removed extra newline --- overviewer_core/util.py | 1 - 1 file changed, 1 deletion(-) diff --git a/overviewer_core/util.py b/overviewer_core/util.py index 5e758df..4506db3 100644 --- a/overviewer_core/util.py +++ b/overviewer_core/util.py @@ -215,7 +215,6 @@ class WindowsOutputStream(object): else: self.stream.write(c) - self.stream.write("\n") def flush(self):