-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Add log-auto-indent option to control multiline formatting #5926
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
Allow selective auto-indentation of multiline log messages. | ||
|
||
Adds command line option ``--log-auto-indent``, config option | ||
``log_auto_indent`` and support for per-entry configuration of | ||
indentation behavior on calls to ``logging.log()``. | ||
|
||
Alters the default for auto-indention from ``on`` to ``off``. This | ||
restores the older behavior that existed prior to v4.6.0. This | ||
reversion to earlier behavior was done because it is better to | ||
activate new features that may lead to broken tests explicitly | ||
rather than implicitly. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1192,6 +1192,29 @@ passed multiple times. The expected format is ``name=value``. For example:: | |
[pytest] | ||
junit_suite_name = my_suite | ||
|
||
.. confval:: log_auto_indent | ||
|
||
Allow selective auto-indentation of multiline log messages. | ||
|
||
Supports command line option ``--log-auto-indent [value]`` | ||
and config option ``log_auto_indent = [value]`` to set the | ||
auto-indentation behavior for all logging. | ||
|
||
``[value]`` can be: | ||
* True or "On" - Dynamically auto-indent multiline log messages | ||
* False or "Off" or 0 - Do not auto-indent multiline log messages (the default behavior) | ||
* [positive integer] - auto-indent multiline log messages by [value] spaces | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please mention that auto-indentation is turned off by default. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
|
||
.. code-block:: ini | ||
|
||
[pytest] | ||
log_auto_indent = False | ||
|
||
Supports passing kwarg ``extra={"auto_indent": [value]}`` to | ||
calls to ``logging.log()`` to specify auto-indentation behavior for | ||
a specific entry in the log. ``extra`` kwarg overrides the value specified | ||
on the command line or in the config. | ||
|
||
|
||
.. confval:: log_cli_date_format | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
|
||
import pytest | ||
from _pytest.compat import nullcontext | ||
from _pytest.config import _strtobool | ||
from _pytest.config import create_terminal_writer | ||
from _pytest.pathlib import Path | ||
|
||
|
@@ -72,24 +73,87 @@ class PercentStyleMultiline(logging.PercentStyle): | |
formats the message as if each line were logged separately. | ||
""" | ||
|
||
def __init__(self, fmt, auto_indent): | ||
super().__init__(fmt) | ||
self._auto_indent = self._get_auto_indent(auto_indent) | ||
|
||
@staticmethod | ||
def _update_message(record_dict, message): | ||
tmp = record_dict.copy() | ||
tmp["message"] = message | ||
return tmp | ||
|
||
@staticmethod | ||
def _get_auto_indent(auto_indent_option) -> int: | ||
"""Determines the current auto indentation setting | ||
|
||
Specify auto indent behavior (on/off/fixed) by passing in | ||
extra={"auto_indent": [value]} to the call to logging.log() or | ||
using a --log-auto-indent [value] command line or the | ||
log_auto_indent [value] config option. | ||
|
||
Default behavior is auto-indent off. | ||
|
||
Using the string "True" or "on" or the boolean True as the value | ||
turns auto indent on, using the string "False" or "off" or the | ||
boolean False or the int 0 turns it off, and specifying a | ||
positive integer fixes the indentation position to the value | ||
specified. | ||
|
||
Any other values for the option are invalid, and will silently be | ||
converted to the default. | ||
|
||
:param any auto_indent_option: User specified option for indentation | ||
from command line, config or extra kwarg. Accepts int, bool or str. | ||
str option accepts the same range of values as boolean config options, | ||
as well as positive integers represented in str form. | ||
|
||
:returns: indentation value, which can be | ||
-1 (automatically determine indentation) or | ||
0 (auto-indent turned off) or | ||
>0 (explicitly set indentation position). | ||
""" | ||
|
||
if type(auto_indent_option) is int: | ||
return int(auto_indent_option) | ||
elif type(auto_indent_option) is str: | ||
try: | ||
return int(auto_indent_option) | ||
except ValueError: | ||
pass | ||
try: | ||
if _strtobool(auto_indent_option): | ||
return -1 | ||
except ValueError: | ||
return 0 | ||
elif type(auto_indent_option) is bool: | ||
if auto_indent_option: | ||
return -1 | ||
|
||
return 0 | ||
|
||
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__ | ||
if hasattr(record, "auto_indent"): | ||
# passed in from the "extra={}" kwarg on the call to logging.log() | ||
auto_indent = self._get_auto_indent(record.auto_indent) | ||
else: | ||
auto_indent = self._auto_indent | ||
|
||
if auto_indent: | ||
lines = record.message.splitlines() | ||
formatted = self._fmt % self._update_message(record.__dict__, lines[0]) | ||
|
||
if auto_indent < 0: | ||
indentation = _remove_ansi_escape_sequences(formatted).find( | ||
lines[0] | ||
) | ||
else: | ||
# optimizes logging by allowing a fixed indentation | ||
indentation = auto_indent | ||
lines[0] = formatted | ||
return ("\n" + " " * indentation).join(lines) | ||
return self._fmt % record.__dict__ | ||
|
||
|
||
def get_option_ini(config, *names): | ||
|
@@ -183,6 +247,12 @@ def add_option_ini(option, dest, default=None, type=None, **kwargs): | |
default=DEFAULT_LOG_DATE_FORMAT, | ||
help="log date format as used by the logging module.", | ||
) | ||
add_option_ini( | ||
"--log-auto-indent", | ||
dest="log_auto_indent", | ||
default=None, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do you set auto-indent by default to None and not to "true"? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My preference is to set the variables that represent options to This may not specifically relate to the setting I'm working on, but I find it to be a good practice overall because it may not be clear up front whether or not distinguishing the default from an explicit choice equal to the default may be necessary later. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FTR I like this approach too. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thx for the explaination. |
||
help="Auto-indent multiline messages passed to the logging module. Accepts true|on, false|off or an integer.", | ||
) | ||
|
||
|
||
@contextmanager | ||
|
@@ -413,6 +483,7 @@ def __init__(self, config): | |
self.formatter = self._create_formatter( | ||
get_option_ini(config, "log_format"), | ||
get_option_ini(config, "log_date_format"), | ||
get_option_ini(config, "log_auto_indent"), | ||
) | ||
self.log_level = get_actual_log_level(config, "log_level") | ||
|
||
|
@@ -444,7 +515,7 @@ def __init__(self, config): | |
if self._log_cli_enabled(): | ||
self._setup_cli_logging() | ||
|
||
def _create_formatter(self, log_format, log_date_format): | ||
def _create_formatter(self, log_format, log_date_format, auto_indent): | ||
# color option doesn't exist if terminal plugin is disabled | ||
color = getattr(self._config.option, "color", "no") | ||
if color != "no" and ColoredLevelFormatter.LEVELNAME_FMT_REGEX.search( | ||
|
@@ -456,7 +527,10 @@ def _create_formatter(self, log_format, log_date_format): | |
else: | ||
formatter = logging.Formatter(log_format, log_date_format) | ||
|
||
formatter._style = PercentStyleMultiline(formatter._style._fmt) | ||
formatter._style = PercentStyleMultiline( | ||
formatter._style._fmt, auto_indent=auto_indent | ||
) | ||
|
||
return formatter | ||
|
||
def _setup_cli_logging(self): | ||
|
@@ -473,6 +547,7 @@ def _setup_cli_logging(self): | |
log_cli_formatter = self._create_formatter( | ||
get_option_ini(config, "log_cli_format", "log_format"), | ||
get_option_ini(config, "log_cli_date_format", "log_date_format"), | ||
get_option_ini(config, "log_auto_indent"), | ||
) | ||
|
||
log_cli_level = get_actual_log_level(config, "log_cli_level", "log_level") | ||
|
Uh oh!
There was an error while loading. Please reload this page.