Skip to content
Snippets Groups Projects
Commit d2878063 authored by Art's avatar Art :lizard:
Browse files

Colored logger from hshdev

parents
No related branches found
No related tags found
No related merge requests found
"""
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.
"""
import sys
import logging
import itertools
import random
# Control sequences (just google VT52 to see the compatibility burden straight from 1975)
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"]
intro_colors = [34, 36, 32, 33, 35]
decorate_info = {
# method names and their desired highlight colors
"debug": "1;90",
"info": "1;32",
"warning": "1;33",
"error": "1;91",
"critical": "1;95",
"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"))
def _apply_color_to_msg(msg, color, extras):
return "".join([color_set.format(color, extras), str(msg), color_reset])
def decorate_for_tty(method, color="1;39", extras=""):
""" it also knows how to decorate bound methods, but it is not used anymore """
method_is_bound = bool(getattr(method, "__self__", None)) # existing loggers cause bound methods
msg_index = 0 if method_is_bound else 1 # message argument is located there
def wrapper(*args, **kwargs):
args = list(args) # it's an immutable tuple when received
args[msg_index] = _apply_color_to_msg(args[msg_index], color, extras)
return method(*args, **kwargs)
return wrapper
for entity in [logging.Logger]:
for method_name, color_str in decorate_info.items():
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)
else:
pass # that should be a placeholder object (non-public API), ignoring it
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment