From ea3ebec117b6f06b0a584f39ecaa8e75976a6e37 Mon Sep 17 00:00:00 2001 From: Thomas Hisch Date: Fri, 24 May 2019 04:32:22 +0200 Subject: [PATCH] logging: Improve formatting of multiline message --- changelog/5312.feature.rst | 1 + src/_pytest/logging.py | 33 +++++++++++++++++++++++++++++++ testing/logging/test_formatter.py | 30 ++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 changelog/5312.feature.rst diff --git a/changelog/5312.feature.rst b/changelog/5312.feature.rst new file mode 100644 index 00000000000..bcd5a736b58 --- /dev/null +++ b/changelog/5312.feature.rst @@ -0,0 +1 @@ +Improved formatting of multiline log messages in python3. diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index 6555710bfe6..577a5407be0 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -77,6 +77,36 @@ def format(self, record): return super(ColoredLevelFormatter, self).format(record) +if not six.PY2: + # Formatter classes don't support format styles in PY2 + + class PercentStyleMultiline(logging.PercentStyle): + """A logging style with special support for multiline messages. + + If the message of a record consists of multiple lines, this style + formats the message as if each line were logged separately. + """ + + @staticmethod + def _update_message(record_dict, message): + tmp = record_dict.copy() + tmp["message"] = message + return tmp + + def format(self, record): + if "\n" in record.message: + lines = record.message.splitlines() + formatted = self._fmt % self._update_message(record.__dict__, lines[0]) + # TODO optimize this by introducing an option that tells the + # logging framework that the indentation doesn't + # change. This allows to compute the indentation only once. + indentation = _remove_ansi_escape_sequences(formatted).find(lines[0]) + lines[0] = formatted + return ("\n" + " " * indentation).join(lines) + else: + return self._fmt % record.__dict__ + + def get_option_ini(config, *names): for name in names: ret = config.getoption(name) # 'default' arg won't work as expected @@ -444,6 +474,9 @@ def _create_formatter(self, log_format, log_date_format): ) else: formatter = logging.Formatter(log_format, log_date_format) + + if not six.PY2: + formatter._style = PercentStyleMultiline(formatter._style._fmt) return formatter def _setup_cli_logging(self): diff --git a/testing/logging/test_formatter.py b/testing/logging/test_formatter.py index 1610da8458b..c851c34d784 100644 --- a/testing/logging/test_formatter.py +++ b/testing/logging/test_formatter.py @@ -2,7 +2,9 @@ import logging import py.io +import six +import pytest from _pytest.logging import ColoredLevelFormatter @@ -35,3 +37,31 @@ class option(object): formatter = ColoredLevelFormatter(tw, logfmt) output = formatter.format(record) assert output == ("dummypath 10 INFO Test Message") + + +@pytest.mark.skipif( + six.PY2, reason="Formatter classes don't support format styles in PY2" +) +def test_multiline_message(): + from _pytest.logging import PercentStyleMultiline + + logfmt = "%(filename)-25s %(lineno)4d %(levelname)-8s %(message)s" + + record = logging.LogRecord( + name="dummy", + level=logging.INFO, + pathname="dummypath", + lineno=10, + msg="Test Message line1\nline2", + args=(), + exc_info=False, + ) + # this is called by logging.Formatter.format + record.message = record.getMessage() + + style = PercentStyleMultiline(logfmt) + output = style.format(record) + assert output == ( + "dummypath 10 INFO Test Message line1\n" + " line2" + )