userver: en/testsuite/plugins/servicelogs.py Source File
Loading...
Searching...
No Matches
servicelogs.py
1import asyncio
2import io
3import logging
4import pathlib
5
6import pytest
7
8from testsuite._internal import logreader
9
10logger = logging.getLogger(__name__)
11
12
14 def __init__(self, path: pathlib.Path, *, formatter_factory):
15 super().__init__(path)
16 self._formatter_factory = formatter_factory
17
18 def make_report(self):
19 formatter = self._formatter_factory()
20 log = io.StringIO()
21 for line in self.readlines(limit_position=True):
22 line = line.rstrip(b'\r\n')
23 line = formatter(line)
24 if line is not None:
25 log.write(line)
26 log.write('\n')
27 return log.getvalue()
28
29
31 _live_logs = None
32
33 def __init__(self, *, config):
34 self._config = config
35 self._logs = {}
36 self._flushers = []
37
38 def pytest_sessionstart(self, session):
39 if _live_logs_enabled(self._config):
41 else:
42 self._live_logs = None
43
44 def pytest_sessionfinish(self, session):
45 if self._live_logs:
46 self._live_logs.join()
47
48 def pytest_runtest_setup(self, item):
49 self._flushers.clear()
50 self.update_position()
51
52 @pytest.hookimpl(wrapper=True, tryfirst=True)
53 def pytest_runtest_makereport(self, item, call):
54 report = yield
55 if report.failed and not self._live_logs:
56 self._userver_report_attach(report)
57 return report
58
59 def register_flusher(self, func):
60 loop = asyncio.get_running_loop()
61 self._flushers.append((loop, func))
62
63 def register_logfile(
64 self, path: pathlib.Path, title: str, formatter_factory
65 ):
66 logger.info('Watching service log file: %s', path)
67 self._logs[path, title] = LogFileReporter(
68 path, formatter_factory=formatter_factory
69 )
70 if self._live_logs:
71 self._live_logs.register_logfile(
72 path, formatter=formatter_factory()
73 )
74
75 def update_position(self):
76 for logfile in self._logs.values():
77 logfile.update_position()
78
79 def _userver_report_attach(self, report):
80 self._run_flushers()
81 for (_, title), logfile in self._logs.items():
82 self._userver_report_attach_log(logfile, report, title)
83
84 def _userver_report_attach_log(self, logfile, report, title):
85 value = logfile.make_report()
86 if value:
87 report.sections.append((f'Captured {title} {report.when}', value))
88
89 def _run_flushers(self):
90 try:
91 for loop, flusher in self._flushers:
92 if not loop.is_closed():
93 loop.run_until_complete(flusher())
94 except Exception:
95 logger.exception('failed to run logging flushers:')
96
97
98def pytest_addoption(parser) -> None:
99 parser.addoption(
100 '--service-livelogs-disable',
101 action='store_true',
102 help='Disable service live logs (enabled with -s)',
103 )
104
105
106def pytest_configure(config):
107 config.pluginmanager.register(
108 ServiceLogsPlugin(config=config),
109 'service_logs_plugin',
110 )
111
112
113@pytest.fixture(scope='session')
114def servicelogs_register_logfile(_servicelogs_logging_plugin):
115 def register(path: pathlib.Path, *, title: str, formatter_factory):
116 _servicelogs_logging_plugin.register_logfile(
117 path, title, formatter_factory
118 )
119
120 return register
121
122
123@pytest.fixture
124def servicelogs_register_flusher(
125 _servicelogs_logging_plugin: ServiceLogsPlugin,
126):
127 def register(func):
128 _servicelogs_logging_plugin.register_flusher(func)
129
130 return register
131
132
133@pytest.fixture(scope='session')
134def service_logs_update_position(
135 _servicelogs_logging_plugin: ServiceLogsPlugin,
136):
137 def update_position():
138 return _servicelogs_logging_plugin.update_position()
139
140 return update_position
141
142
143@pytest.fixture(scope='session')
144def _servicelogs_logging_plugin(pytestconfig) -> ServiceLogsPlugin:
145 return pytestconfig.pluginmanager.get_plugin('service_logs_plugin')
146
147
148def _live_logs_enabled(config):
149 if not config.option.service_livelogs_disable:
150 return bool(
151 config.option.capture == 'no'
152 and config.option.showcapture in ('all', 'log'),
153 )
154 return False