Skip to content

Commit

Permalink
live-logging: Colorize levelname
Browse files Browse the repository at this point in the history
  • Loading branch information
twmr committed Jan 27, 2018
1 parent a580990 commit c1da614
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 1 deletion.
60 changes: 59 additions & 1 deletion _pytest/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import logging
from contextlib import closing, contextmanager
import re
import six

import pytest
Expand All @@ -12,6 +13,60 @@
DEFAULT_LOG_DATE_FORMAT = '%H:%M:%S'


class ColoredLevelFormatter(logging.Formatter):
"""
Colorize the %(levelname)..s part of the log format passed to __init__.
"""

LOGLEVEL_COLOROPTS = {
logging.CRITICAL: {'red'},
logging.ERROR: {'red', 'bold'},
logging.WARNING: {'yellow'},
logging.WARN: {'yellow'},
logging.INFO: {'green'},
logging.DEBUG: {'purple'},
logging.NOTSET: set(),
}
LEVELNAME_FMT_REGEX = re.compile(r'%\(levelname\)([+-]?\d*s)')

def __init__(self, *args, **kwargs):
super(ColoredLevelFormatter, self).__init__(
*args, **kwargs)
if six.PY2:
self._original_fmt = self._fmt
else:
self._original_fmt = self._style._fmt
self._level_to_fmt_mapping = {}

levelname_fmt_match = self.LEVELNAME_FMT_REGEX.search(self._fmt)
if not levelname_fmt_match:
return
levelname_fmt = levelname_fmt_match.group()

tw = py.io.TerminalWriter()

for level, color_opts in self.LOGLEVEL_COLOROPTS.items():
formatted_levelname = levelname_fmt % {
'levelname': logging.getLevelName(level)}

# add ANSI escape sequences around the formatted levelname
color_kwargs = {name: True for name in color_opts}
colorized_formatted_levelname = tw.markup(
formatted_levelname, **color_kwargs)
self._level_to_fmt_mapping[level] = self.LEVELNAME_FMT_REGEX.sub(
colorized_formatted_levelname,
self._fmt)

def format(self, record):
fmt = self._level_to_fmt_mapping.get(
record.levelno, self._original_fmt)
if six.PY2:
self._fmt = fmt
else:
self._style._fmt = fmt
return super(ColoredLevelFormatter, self).format(record)


def get_option_ini(config, *names):
for name in names:
ret = config.getoption(name) # 'default' arg won't work as expected
Expand Down Expand Up @@ -376,7 +431,10 @@ def _setup_cli_logging(self):
log_cli_handler = _LiveLoggingStreamHandler(terminal_reporter, capture_manager)
log_cli_format = get_option_ini(self._config, 'log_cli_format', 'log_format')
log_cli_date_format = get_option_ini(self._config, 'log_cli_date_format', 'log_date_format')
log_cli_formatter = logging.Formatter(log_cli_format, datefmt=log_cli_date_format)
if ColoredLevelFormatter.LEVELNAME_FMT_REGEX.search(log_cli_format):
log_cli_formatter = ColoredLevelFormatter(log_cli_format, datefmt=log_cli_date_format)
else:
log_cli_formatter = logging.Formatter(log_cli_format, datefmt=log_cli_date_format)
log_cli_level = get_actual_log_level(self._config, 'log_cli_level', 'log_level')
self.log_cli_handler = log_cli_handler
self.live_logs_context = catching_logs(log_cli_handler, formatter=log_cli_formatter, level=log_cli_level)
Expand Down
1 change: 1 addition & 0 deletions changelog/3142.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Colorize the levelname column in the live-log output.
32 changes: 32 additions & 0 deletions testing/logging/test_formatter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import logging
import os

import six

from py.io import TerminalWriter
import pytest
from _pytest.logging import ColoredLevelFormatter


def test_coloredlogformatter():
# TODO remove usage of mock module in this test
pytest.importorskip("mock")
import mock

logfmt = '%(filename)-25s %(lineno)4d %(levelname)-8s %(message)s'

tw = TerminalWriter()
tw.hasmarkup = True # otherwise tw.markup does not add any escape
# sequences

record = logging.LogRecord(
name='dummy', level=logging.INFO, pathname='dummypath', lineno=10,
msg='Test Message', args=(), exc_info=False)

with mock.patch('py.io.TerminalWriter') as mock_tw:
mock_tw.return_value = tw
formatter = ColoredLevelFormatter(logfmt)

output = formatter.format(record)
assert output == ('dummypath 10 '
'\x1b[32mINFO \x1b[0m Test Message')

0 comments on commit c1da614

Please sign in to comment.