From 140726c2cb35dd5f770c4bde86fee3f5d61348b6 Mon Sep 17 00:00:00 2001 From: Art Lukyanchyk <artiom.lukyanchyk@hs-hannover.de> Date: Tue, 12 Sep 2017 15:08:58 +0200 Subject: [PATCH] Fix and adjust the colored logger code. --- colored_logger.py | 91 ++++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 45 deletions(-) diff --git a/colored_logger.py b/colored_logger.py index 42a5f6f..300fab3 100644 --- a/colored_logger.py +++ b/colored_logger.py @@ -1,12 +1,7 @@ """ - -This module decorates methods of Logger class. -It's quite harmless, unless you use some weird non-tty stdout/stderr while DEBUG-ing. - -This hack should be auto-loaded by it's app. -If you don't use the app, just import this file to apply the hack. - +This module decorates/patches methods of logging.Logger class, plus patches already existing logger instances. +It's quite harmless, unless you use some weird non-tty stdout/stderr. """ import sys @@ -16,9 +11,10 @@ import random # Control sequences (just google VT52 to see the compatibility burden straight from 1975) -color_set = "\x1b[{}m{}" +color_set = "\x1b[{}m" color_reset = "\x1b[0m" + # useless stuff displayed before applying patches... yes, I felt very bored intro_words = ["magic", "MaGiC", "voodoo", "sorcery", "wizardry", "witchery", "foobar", "rainbow"] intro_adjectives = ["colourful", "evil", "fairy", "random"] @@ -34,45 +30,30 @@ decorate_info = { "exception": "1;95", } -if settings.DEBUG: # probably PyCharm or some other IDE with own stream handling - # useless foo - intro_seq = zip(itertools.cycle(intro_colors), random.choice(intro_words)) - print( - "{adj} {word}{reset}:".format( - adj=random.choice(intro_adjectives).capitalize(), - word=str().join(color_set.format(*symbol) for symbol in intro_seq), - reset=color_reset, - ), - end=" ") - # Patching handlers: Python logging.StreamHandler targets stderr by default, - # therefore Django example config does, therefore our logging config also does, - # so PyCharm believes every line of log is error. Well... Fixing it now. - # Existing loggers first. - patched = list() - for logger in logging.Logger.manager.loggerDict.values(): - for handler in getattr(logger, "handlers", list()): - if isinstance(handler, logging.StreamHandler): - if handler.stream == sys.stderr: - handler.stream = sys.stdout - patched.append(handler) - # Future loggers (they should be initially created with the proper stream). - original_constructor = logging.StreamHandler.__init__ # must grab a reference outside of the lambda - logging.StreamHandler.__init__ = lambda zelf, stream=None: original_constructor(zelf, stream or sys.stdout) - # the isatty() stuff below solved some problems in the past, I'm not sure anymore which exactly - try: - stdout_tty = sys.stdout.isatty() - except Exception: - stdout_tty = False - if not stdout_tty: - patched.append(sys.stdout.isatty) - sys.stdout.isatty = lambda: True # stdout is now a TTY no matter what - # reporting - print("applied {n} logging patches.".format(n=len(patched))) - logging.getLogger().debug("Logging patches: {}.".format(", ".join(str(e) for e in patched) or "none")) - +# Adjusting handlers: Python logging.StreamHandler targets stderr by default. +# Django default logging configuration doesn't change it, too. +# Existing loggers first. +patched_objects = set() +for logger in logging.Logger.manager.loggerDict.values(): + for handler in getattr(logger, "handlers", list()): + if isinstance(handler, logging.StreamHandler): + if handler.stream == sys.stderr: + handler.stream = sys.stdout + patched_objects.add(handler) +# Future loggers (they should be initially created with the proper stream). +original_constructor = logging.StreamHandler.__init__ # must grab a reference outside of the lambda +logging.StreamHandler.__init__ = lambda zelf, stream=None: original_constructor(zelf, stream or sys.stdout) +# the isatty() stuff below solved some problems in the past, I'm not sure anymore which exactly +try: + stdout_tty = sys.stdout.isatty() +except Exception: + stdout_tty = False +if not stdout_tty: + patched_objects.add(sys.stdout.isatty) + sys.stdout.isatty = lambda: True # stdout is now a TTY no matter what def _apply_color_to_msg(msg, color, extras): - return "".join([color_set.format(color, extras), str(msg), color_reset]) + return "".join([color_set.format(color), extras, str(msg), color_reset]) def decorate_for_tty(method, color="1;39", extras=""): @@ -92,7 +73,27 @@ for entity in [logging.Logger]: if isinstance(entity, logging.Logger) or entity is logging.Logger: new_method = decorate_for_tty(getattr(entity, method_name), color_str) setattr(entity, method_name, new_method) + patched_objects.add(entity) else: pass # that should be a placeholder object (non-public API), ignoring it +# useless foo +intro_seq = zip(itertools.cycle(intro_colors), random.choice(intro_words)) +intro_msg = "{adj} {word}{reset}".format( + adj=random.choice(intro_adjectives).capitalize(), + word=str().join((color_set + "{}").format(*symbol) for symbol in intro_seq), + reset=color_reset, +) + +# reporting +sys.stdout.write("{intro_msg}{c}: patched {n} object{s}{cr}\n".format( + intro_msg=intro_msg, + c=color_set.format("1;90"), + n=len(patched_objects), + s="" if len(patched_objects) is 1 else "s", + cr=color_reset, +)) +logging.getLogger("pydevutils").debug("Logging patches: {}.".format(", ".join(str(e) for e in patched_objects) or "none")) + + -- GitLab