Source code for testsuite.plugins.servicelogs
from __future__ import annotations
import asyncio
import io
import logging
import pathlib
import pytest
from testsuite._internal import logreader
logger = logging.getLogger(__name__)
class LogFileReporter(logreader.LogFile):
def __init__(self, path: pathlib.Path, *, formatter_factory):
super().__init__(path)
self._formatter_factory = formatter_factory
def make_report(self):
formatter = self._formatter_factory()
log = io.StringIO()
for line in self.readlines(limit_position=True):
line = line.rstrip(b'\r\n')
line = formatter(line)
if line is not None:
log.write(line)
log.write('\n')
return log.getvalue()
class ServiceLogsPlugin:
_live_logs = None
def __init__(self, *, config):
self._config = config
self._logs = {}
self._flushers = []
def pytest_sessionstart(self, session):
if _live_logs_enabled(self._config):
self._live_logs = logreader.LiveLogHandler()
else:
self._live_logs = None
def pytest_sessionfinish(self, session):
if self._live_logs:
self._live_logs.join()
def pytest_runtest_setup(self, item):
self._flushers.clear()
self.update_position()
@pytest.hookimpl(wrapper=True, tryfirst=True)
def pytest_runtest_makereport(self, item, call):
report = yield
if report.failed and not self._live_logs:
self._userver_report_attach(report)
return report
def register_flusher(self, func):
loop = asyncio.get_running_loop()
self._flushers.append((loop, func))
def register_logfile(
self, path: pathlib.Path, title: str, formatter_factory
):
logger.info('Watching service log file: %s', path)
self._logs[path, title] = LogFileReporter(
path, formatter_factory=formatter_factory
)
if self._live_logs:
self._live_logs.register_logfile(
path, formatter=formatter_factory()
)
def update_position(self):
for logfile in self._logs.values():
logfile.update_position()
def _userver_report_attach(self, report):
self._run_flushers()
for (_, title), logfile in self._logs.items():
self._userver_report_attach_log(logfile, report, title)
def _userver_report_attach_log(self, logfile, report, title):
value = logfile.make_report()
if value:
report.sections.append((f'Captured {title} {report.when}', value))
def _run_flushers(self):
try:
for loop, flusher in self._flushers:
if not loop.is_closed():
loop.run_until_complete(flusher())
except Exception:
logger.exception('failed to run logging flushers:')
def pytest_addoption(parser) -> None:
parser.addoption(
'--service-livelogs-disable',
action='store_true',
help='Disable service live logs (enabled with -s)',
)
def pytest_configure(config):
config.pluginmanager.register(
ServiceLogsPlugin(config=config),
'service_logs_plugin',
)
[docs]
@pytest.fixture(scope='session')
def servicelogs_register_logfile(_servicelogs_logging_plugin):
def register(path: pathlib.Path, *, title: str, formatter_factory):
_servicelogs_logging_plugin.register_logfile(
path, title, formatter_factory
)
return register
[docs]
@pytest.fixture
def servicelogs_register_flusher(
_servicelogs_logging_plugin: ServiceLogsPlugin,
):
def register(func):
_servicelogs_logging_plugin.register_flusher(func)
return register
[docs]
@pytest.fixture(scope='session')
def service_logs_update_position(
_servicelogs_logging_plugin: ServiceLogsPlugin,
):
def update_position():
return _servicelogs_logging_plugin.update_position()
return update_position
@pytest.fixture(scope='session')
def _servicelogs_logging_plugin(pytestconfig) -> ServiceLogsPlugin:
return pytestconfig.pluginmanager.get_plugin('service_logs_plugin')
def _live_logs_enabled(config):
if not config.option.service_livelogs_disable:
return bool(
config.option.capture == 'no'
and config.option.showcapture in ('all', 'log'),
)
return False