Defer log record formatting until after filters#4954
Conversation
`Logger._log` formatted the message - `tmt run` does not care, whatever is logged is also emitted and lands in `log.txt`, but there are commands that do not have `log.txt`, like `tmt * ls`, that do not have log file. Such commands could benefit from rendering of messages that would be actually emitted. It turns out `LogRecordDetails` already contains everything we need to format the message. If the log record has `details`, we can easily postpone formatting. Brief comparison of `tmt test ls` in tmt repo, on my workstation: - before: 10 seconds (no prof), 25 seconds (with prof), 17/16/8 seconds in `debug`/`_log`/`indent`; - after: 7 seconds (no prof), 17 seconds (with prof), 7/7 seconds in `debug`/`_log`, `indent` does not register. Simple `tmt` in a repo with 179 plans takes 11 seconds before the change, 9 seconds after. The effect is measurable, reliably, running with profiler makes it stand out even more, yet it's not as big as expected.
There was a problem hiding this comment.
Code Review
This pull request introduces a custom LogRecord class and a type guard to handle custom log record details, refactoring formatting and filtering logic to use this custom record type. Feedback suggests chaining the log record factory instead of overriding it directly to preserve existing custom factories set by other libraries or test runners.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| # Tell logging to use our custom log record class. | ||
| logging.setLogRecordFactory(LogRecord) |
There was a problem hiding this comment.
Chain the log record factory to preserve any existing custom factories set by other libraries or test runners. Overriding it directly can break logging capture or custom record types.
# Tell logging to use our custom log record class, preserving any existing factory.
_old_factory = logging.getLogRecordFactory()
def _log_record_factory(*args: Any, **kwargs: Any) -> logging.LogRecord:
record = _old_factory(*args, **kwargs)
if not isinstance(record, LogRecord):
record.__class__ = LogRecord
return record
logging.setLogRecordFactory(_log_record_factory)
Logger._logformatted the message -tmt rundoes not care, whatever is logged is also emitted and lands inlog.txt, but there are commands that do not havelog.txt, liketmt * ls, that do not have log file. Such commands could benefit from rendering of messages that would be actually emitted.It turns out
LogRecordDetailsalready contains everything we need to format the message. If the log record hasdetails, we can easily postpone formatting.Brief comparison of
tmt test lsin tmt repo, on my workstation:debug/_log/indent;debug/_log,indentdoes not register.Simple
tmtin a repo with 179 plans takes 11 seconds before the change, 9 seconds after. In a repo with 3679 tests & 1 plan, 2 minutes 28 seconds before, 1 minute 33 seconds after.The effect is measurable, reliably, running with profiler makes it stand out even more, yet it's not as big as expected.
PR addresses just one part of the formatting process, i.e. rendering of a message by logging subsystem from key and optional value fields, and its indentation. It does not address messages that are actually formatted in place, as f-strings, and never emitted. To address these, more work will be needed, as the next iteration on top of this PR.
Pull Request Checklist