Skip to content

Commit d797e3f

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

File tree

2 files changed

+55
-2
lines changed

2 files changed

+55
-2
lines changed

_pytest/logging.py

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,65 @@
11
from __future__ import absolute_import, division, print_function
22

3-
import logging
43
from contextlib import closing, contextmanager
4+
import logging
5+
import re
56
import six
67

78
import pytest
89
import py
910

11+
LOGLEVEL_COLOROPTS = {
12+
logging.CRITICAL: {'red'},
13+
logging.ERROR: {'red', 'bold'},
14+
logging.WARNING: {'yellow'},
15+
logging.WARN: {'yellow'},
16+
logging.INFO: {'green'},
17+
logging.DEBUG: {'purple'},
18+
logging.NOTSET: set(),
19+
}
1020

1121
DEFAULT_LOG_FORMAT = '%(filename)-25s %(lineno)4d %(levelname)-8s %(message)s'
1222
DEFAULT_LOG_DATE_FORMAT = '%H:%M:%S'
1323

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

1564
def get_option_ini(config, *names):
1665
for name in names:
@@ -376,7 +425,10 @@ def _setup_cli_logging(self):
376425
log_cli_handler = _LiveLoggingStreamHandler(terminal_reporter, capture_manager)
377426
log_cli_format = get_option_ini(self._config, 'log_cli_format', 'log_format')
378427
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)
428+
if LEVELNAME_FMT_REGEX.search(log_cli_format):
429+
log_cli_formatter = ColoredLevelFormatter(log_cli_format, datefmt=log_cli_date_format)
430+
else:
431+
log_cli_formatter = logging.Formatter(log_cli_format, datefmt=log_cli_date_format)
380432
log_cli_level = get_actual_log_level(self._config, 'log_cli_level', 'log_level')
381433
self.log_cli_handler = log_cli_handler
382434
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)