Skip to content

Commit ed1051f

Browse files
committed
live-logging: Colorize levelname
1 parent a580990 commit ed1051f

File tree

2 files changed

+63
-1
lines changed

2 files changed

+63
-1
lines changed

_pytest/logging.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,73 @@
22

33
import logging
44
from contextlib import closing, contextmanager
5+
import re
56
import six
67

78
import pytest
89
import py
910

1011

12+
LOGLEVEL_COLOROPTS = {
13+
logging.CRITICAL: {'red'},
14+
logging.ERROR: {'red', 'bold'},
15+
logging.WARNING: {'yellow'},
16+
logging.WARN: {'yellow'},
17+
logging.INFO: {'green'},
18+
logging.DEBUG: {'purple'},
19+
logging.NOTSET: set(),
20+
}
21+
1122
DEFAULT_LOG_FORMAT = '%(filename)-25s %(lineno)4d %(levelname)-8s %(message)s'
1223
DEFAULT_LOG_DATE_FORMAT = '%H:%M:%S'
1324

25+
# move to __init__ of ColoredLevelFormatter?
26+
LEVELNAME_FMT_REGEX = re.compile(r'%\(levelname\)([+-]?\d*s)')
27+
28+
29+
class ColoredLevelFormatter(logging.Formatter):
30+
"""
31+
Colorize the %(levelname)..s part of the log format passed to __init__.
32+
"""
33+
34+
def __init__(self, *args, **kwargs):
35+
super(ColoredLevelFormatter, self).__init__(
36+
*args, **kwargs)
37+
if py.std.sys.version_info[0] >= 3:
38+
self._original_fmt = self._style._fmt
39+
else:
40+
self._original_fmt = self._fmt
41+
self._level_to_fmt_mapping = {}
42+
43+
levelname_fmt_match = LEVELNAME_FMT_REGEX.search(self._fmt)
44+
if not levelname_fmt_match:
45+
return
46+
levelname_fmt = levelname_fmt_match.group()
47+
48+
tw = py.io.TerminalWriter()
49+
50+
for level, color_opts in LOGLEVEL_COLOROPTS.items():
51+
formatted_levelname = levelname_fmt % {
52+
'levelname': logging.getLevelName(level)}
53+
54+
# add ANSI escape sequences around the formatted levelname
55+
color_kwargs = {name: True for name in color_opts}
56+
colorized_formatted_levelname = tw.markup(
57+
formatted_levelname, **color_kwargs)
58+
self._level_to_fmt_mapping[level] = LEVELNAME_FMT_REGEX.sub(
59+
colorized_formatted_levelname,
60+
self._fmt)
61+
62+
def format(self, record):
63+
fmt = self._level_to_fmt_mapping.get(
64+
record.levelno, self._original_fmt)
65+
66+
if py.std.sys.version_info[0] >= 3:
67+
self._style._fmt = fmt
68+
else:
69+
self._fmt = fmt
70+
return super(ColoredLevelFormatter, self).format(record)
71+
1472

1573
def get_option_ini(config, *names):
1674
for name in names:
@@ -376,7 +434,10 @@ def _setup_cli_logging(self):
376434
log_cli_handler = _LiveLoggingStreamHandler(terminal_reporter, capture_manager)
377435
log_cli_format = get_option_ini(self._config, 'log_cli_format', 'log_format')
378436
log_cli_date_format = get_option_ini(self._config, 'log_cli_date_format', 'log_date_format')
379-
log_cli_formatter = logging.Formatter(log_cli_format, datefmt=log_cli_date_format)
437+
if LEVELNAME_FMT_REGEX.search(log_cli_format):
438+
log_cli_formatter = ColoredLevelFormatter(log_cli_format, datefmt=log_cli_date_format)
439+
else:
440+
log_cli_formatter = logging.Formatter(log_cli_format, datefmt=log_cli_date_format)
380441
log_cli_level = get_actual_log_level(self._config, 'log_cli_level', 'log_level')
381442
self.log_cli_handler = log_cli_handler
382443
self.live_logs_context = catching_logs(log_cli_handler, formatter=log_cli_formatter, level=log_cli_level)

changelog/3142.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Colorize the levelname column in the live-log output.

0 commit comments

Comments
 (0)