Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
"""
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