14logger = logging.getLogger(__name__)
18 def __init__(self, path: pathlib.Path):
22 def update_position(self):
25 except FileNotFoundError:
34 eof_handler: typing.Optional[typing.Callable[[], bool]] =
None,
35 limit_position: bool =
False,
38 max_position = self.
path.stat().st_size
42 for line, position
in _raw_line_reader(
46 line = line.decode(encoding=
'utf-8', errors=
'backslashreplace')
49 if not line.startswith(
'tskv\t'):
51 if not line.endswith(
'\n'):
55 if max_position
is not None and position >= max_position:
60 def __init__(self, *, colorize_factory, delay: float = 0.05):
66 def register_logfile(self, path: pathlib.Path):
73 def join(self, timeout: float = 10):
75 for thread
in self.
_threads.values():
76 thread.join(timeout=timeout)
78 def _logreader_thread(self, path: pathlib.Path):
80 while not path.exists():
85 for line
in logfile.readlines(eof_handler=self.
_eof_handler):
86 line = line.rstrip(
'\r\n')
87 line = colorizer(line)
91 def _write_logline(self, line: str):
92 print(line, file=sys.stderr)
94 def _eof_handler(self) -> bool:
104 def __init__(self, *, colorize_factory, config):
110 def pytest_sessionstart(self, session):
111 if _is_live_logs_enabled(self.
_config):
118 def pytest_sessionfinish(self, session):
122 def pytest_runtest_setup(self, item):
126 @pytest.hookimpl(wrapper=True, tryfirst=True)
127 def pytest_runtest_makereport(self, item, call):
133 def update_position(self):
134 for logfile
in self.
_logs.values():
135 logfile.update_position()
137 def register_flusher(self, func):
140 def register_logfile(self, path: pathlib.Path, title: str):
141 logger.info(
'Watching service log file: %s', path)
143 self.
_logs[path, title] = logfile
147 def _userver_report_attach(self, report):
149 for (_, title), logfile
in self.
_logs.items():
152 def _userver_report_attach_log(self, logfile: LogFile, report, title):
155 for line
in logfile.readlines(limit_position=
True):
156 line = line.rstrip(
'\r\n')
157 line = colorizer(line)
161 value = log.getvalue()
163 report.sections.append((f
'Captured {title} {report.when}', value))
165 def _run_flushers(self):
166 loop = asyncio.get_event_loop()
168 loop.run_until_complete(flusher())
171@pytest.fixture(scope='session')
172def service_logfile_path(
173 pytestconfig, service_tmpdir: pathlib.Path,
174) -> typing.Optional[pathlib.Path]:
176 Holds optional service logfile path. You may want to override this
179 By default returns value of --service-logs-file option or creates
182 if pytestconfig.option.service_logs_file:
183 return pytestconfig.option.service_logs_file
184 return service_tmpdir /
'service.log'
187@pytest.fixture(scope='session')
188def _service_logfile_path(
189 userver_register_logfile,
190 service_logfile_path: typing.Optional[pathlib.Path],
191) -> typing.Optional[pathlib.Path]:
192 if not service_logfile_path:
194 userver_register_logfile(
195 service_logfile_path, title=
'userver/log', truncate=
True,
197 return service_logfile_path
200@pytest.fixture(scope='session')
201def userver_register_logfile(_userver_logging_plugin: UserverLoggingPlugin):
203 Register logfile. Registered logfile is monitored in case of test failure
204 and its contents is attached to pytest report.
206 :param path: pathlib.Path corresponding to log file
207 :param title: title to be used in pytest report
208 :param truncate: file is truncated if True
211 def register_logfile(
212 path: pathlib.Path, *, title: str, truncate: bool = False,
217 def do_truncate(path):
218 with path.open(
'wb+')
as fp:
221 def register_logfile(
222 path: pathlib.Path, *, title: str, truncate: bool =
False,
226 _userver_logging_plugin.register_logfile(path, title)
229 return register_logfile
232@pytest.fixture(scope='session')
233def _userver_logging_plugin(pytestconfig) -> UserverLoggingPlugin:
234 return pytestconfig.pluginmanager.get_plugin(
'userver_logging')
237def pytest_configure(config):
238 pretty_logs = config.option.service_logs_pretty
239 colors_enabled = _should_enable_color(config)
240 verbose = pretty_logs ==
'verbose'
242 def colorize_factory():
244 colorizer = colorize.Colorizer(
245 verbose=verbose, colors_enabled=colors_enabled,
247 return colorizer.colorize_line
249 def handle_line(line):
255 colorize_factory=colorize_factory, config=config,
257 config.pluginmanager.register(plugin,
'userver_logging')
260def pytest_report_header(config):
262 if config.option.service_logs_file:
263 headers.append(f
'servicelogs: {config.option.service_logs_file}')
267def pytest_terminal_summary(terminalreporter, config) -> None:
268 logfile = config.option.service_logs_file
270 terminalreporter.ensure_newline()
271 terminalreporter.section(
'Service logs', sep=
'-', blue=
True, bold=
True)
272 terminalreporter.write_sep(
'-', f
'service log file: {logfile}')
275def _should_enable_color(pytestconfig) -> bool:
276 option = getattr(pytestconfig.option,
'color',
'no')
280 return sys.stderr.isatty()
287 eof_handler: typing.Optional[typing.Callable[[], bool]] =
None,
289 with path.open(
'rb')
as fp:
290 position = fp.seek(position)
295 line = partial + line
297 if line.endswith(b
'\n'):
298 position += len(line)
308def _is_live_logs_enabled(config):
309 if not config.option.service_live_logs_disable:
311 config.option.capture ==
'no'
312 and config.option.showcapture
in (
'all',
'log'),