Add support for logging in collection-phase

The logging plugin does not output log messages generated during the
collection-phase when live-logging is enabled. This fixes this.

Fixes #3964
This commit is contained in:
Thomas Hisch 2018-09-15 01:41:07 +02:00
parent a79dc12f1e
commit e967d4587a
3 changed files with 48 additions and 15 deletions

2
changelog/3964.rst Normal file
View File

@ -0,0 +1,2 @@
Log messages generated in the collection phase are now shown when
live-logging is enabled.

View File

@ -2,7 +2,7 @@
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
import logging import logging
from contextlib import closing, contextmanager from contextlib import contextmanager
import re import re
import six import six
@ -414,7 +414,6 @@ class LoggingPlugin(object):
else: else:
self.log_file_handler = None self.log_file_handler = None
# initialized during pytest_runtestloop
self.log_cli_handler = None self.log_cli_handler = None
def _log_cli_enabled(self): def _log_cli_enabled(self):
@ -425,6 +424,22 @@ class LoggingPlugin(object):
"--log-cli-level" "--log-cli-level"
) is not None or self._config.getini("log_cli") ) is not None or self._config.getini("log_cli")
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_collection(self):
# This has to be called before the first log message is logged,
# so we can access the terminal reporter plugin.
self._setup_cli_logging()
with self.live_logs_context():
if self.log_cli_handler:
self.log_cli_handler.set_when("collection")
if self.log_file_handler is not None:
with catching_logs(self.log_file_handler, level=self.log_file_level):
yield # run all the tests
else:
yield
@contextmanager @contextmanager
def _runtest_for(self, item, when): def _runtest_for(self, item, when):
"""Implements the internals of pytest_runtest_xxx() hook.""" """Implements the internals of pytest_runtest_xxx() hook."""
@ -484,22 +499,15 @@ class LoggingPlugin(object):
@pytest.hookimpl(hookwrapper=True) @pytest.hookimpl(hookwrapper=True)
def pytest_runtestloop(self, session): def pytest_runtestloop(self, session):
"""Runs all collected test items.""" """Runs all collected test items."""
self._setup_cli_logging() with self.live_logs_context():
with self.live_logs_context:
if self.log_file_handler is not None: if self.log_file_handler is not None:
with closing(self.log_file_handler): with catching_logs(self.log_file_handler, level=self.log_file_level):
with catching_logs(
self.log_file_handler, level=self.log_file_level
):
yield # run all the tests yield # run all the tests
else: else:
yield # run all the tests yield # run all the tests
def _setup_cli_logging(self): def _setup_cli_logging(self):
"""Sets up the handler and logger for the Live Logs feature, if enabled. """Sets up the handler and logger for the Live Logs feature, if enabled."""
This must be done right before starting the loop so we can access the terminal reporter plugin.
"""
terminal_reporter = self._config.pluginmanager.get_plugin("terminalreporter") terminal_reporter = self._config.pluginmanager.get_plugin("terminalreporter")
if self._log_cli_enabled() and terminal_reporter is not None: if self._log_cli_enabled() and terminal_reporter is not None:
capture_manager = self._config.pluginmanager.get_plugin("capturemanager") capture_manager = self._config.pluginmanager.get_plugin("capturemanager")
@ -529,11 +537,14 @@ class LoggingPlugin(object):
self._config, "log_cli_level", "log_level" self._config, "log_cli_level", "log_level"
) )
self.log_cli_handler = log_cli_handler self.log_cli_handler = log_cli_handler
self.live_logs_context = catching_logs( self.live_logs_context = lambda: catching_logs(
log_cli_handler, formatter=log_cli_formatter, level=log_cli_level log_cli_handler, formatter=log_cli_formatter, level=log_cli_level
) )
else: else:
self.live_logs_context = dummy_context_manager() self.live_logs_context = lambda: dummy_context_manager()
# Note that the lambda for the live_logs_context is needed because
# live_logs_context can otherwise not be entered multiple times due
# to limitations of contextlib.contextmanager
class _LiveLoggingStreamHandler(logging.StreamHandler): class _LiveLoggingStreamHandler(logging.StreamHandler):

View File

@ -908,3 +908,23 @@ def test_live_logging_suspends_capture(has_capture_manager, request):
else: else:
assert MockCaptureManager.calls == [] assert MockCaptureManager.calls == []
assert out_file.getvalue() == "\nsome message\n" assert out_file.getvalue() == "\nsome message\n"
def test_collection_live_logging(testdir):
testdir.makepyfile(
"""
import logging
logging.getLogger().info("Normal message")
"""
)
result = testdir.runpytest("--log-cli-level=INFO")
result.stdout.fnmatch_lines(
[
"collecting*",
"*--- live log collection ---*",
"*Normal message*",
"collected 0 items",
]
)