Save all warnings encountered in a warnings file#4642
Save all warnings encountered in a warnings file#4642LecrisUT wants to merge 15 commits intoteemtee:mainfrom
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a feature to save all encountered warnings into a warnings.yaml file. However, it introduces several critical security and stability risks. A critical initialization order flaw in the Run class and a missing import in tmt/log.py will cause the application to crash immediately. Furthermore, an unsafe assert statement in the logging path poses a Denial of Service risk, and the lack of synchronization when writing to the warnings file introduces a race condition that could lead to data loss. The current implementation also suffers from inefficiency due to frequent file I/O.
|
My two cents noone asked for: have you considered using a The handler could be attached to the run logger (like a logfile is attached to the root logger via |
Ah you mean to reuse that class as the file handler, yeah, sounds reasonable. Right now I am at the stage to check if the run logger is propagated consistently. Stuff like
This part I need more eyes on the code to figure out what you mean here |
Well, not really reuse the class we have, but adding a new one, as the format of the file would be different, plus the filtering is needed. But following the same concept of using a logging handler for this job, yes. The separation of "what to log" and "where it should go". We may decide to sent all warnings and errors to a Sentry instance, for that we would add another handler, with filtering and Sentry API client, and attach it to the root logger, rather than touching
Nothing exceptional there, just loggers sending their records upward to their parent. Usually with the In your scenario, you would introduce new handler, I am just not sure whether it should be attached to the root logger, or to the run-level one. Probably the run, bit may miss some warnings emitted before workdir is created, for example. On the other hand, where would such warning be saved by the handler, when there's no workdir yet to host the |
Is there a reason we cannot move the run workdir creation as early as needed?
For the case where we know it should not have a
Isn't this something we do not have right now? A logger with multiple sub-loggers/drivers? Our The idea is sound, but how do we do the separation inside the
This part you lost me again because we do not have a design that bubbles up the logging right? Would need a sketch to figure out the refactoring idea of that. |
Just the complexity and technical debt. I have some ideas about how workdirs should be handled in the code, but it's just so painful to start something as it takes ages to get it in, and touching complex topics like this spells a hell of a rebase along the road. Some commands have no need for it, e.g.
Ideally once in a run context, we should have a workdir and a place to write warnings into.
# in Logger.descend():
actual_logger = self._normalize_logger(self._logger.getChild(logger_name))Newly created @staticmethod
def _normalize_logger(logger: logging.Logger) -> logging.Logger:
"""
Reset properties of a given :py:class:`logging.Logger` instance
"""
logger.propagate = True
logger.level = logging.DEBUG
logger.handlers = []
return loggerEvery logger is set to propagate its records to its parent, have the lowest loglevel possible, and have no handlers - the root one gets handlers attached later.
We do :) |
|
There is a big complication with the order of how
This thing is puzzling me though. If we are nuking the |
Attaching a warning-file handler to the run-level logger should work like this: all records travel to root logger, they must pass through the run-level logger, which would sent them to its warning-file handler as well as to its parent, to end up in the logfile and on console. |
Ok, that explains things. There are 2 options here then:
|
I believe the first option is the right way to go: once |
But that would miss the warnings in between the click code and creation of the The only other thing is that it breaks the current state of handlers are only at the top-level, and it could open the gate for having some plugin-specific handlers. This would not be a negative IMO, just a new thing to be aware of. |
Correct, there would be this gap, a gap before the "run context" begins. For the first version, I for one would find it acceptable. To cover this gap, we could think about commands like
Yes. So far we had no use for them, the "save warnings that happened during run" use case calls for the change. I think it's the right approach, and if we discover there's a use case for |
10a4187 to
4c7e678
Compare
4c7e678 to
0a17aa8
Compare
skycastlelily
left a comment
There was a problem hiding this comment.
LGTM in general, you may want to extend the test:)
|
@LecrisUT Can you update MR description, even the issue is so brief it is hard to understand the motivation behind this change . |
0db1b9a to
fa7b74f
Compare
thrix
left a comment
There was a problem hiding this comment.
Well-designed implementation. The append-only YAML approach is clever and avoids race conditions. The stacklevel threading through the logging chain is correctly implemented (verified by tracing the math). Previously flagged issues (source/reason swap, is vs ==, workdir init timing) are all resolved.
Two actionable findings:
to_dict()→to_minimal_dict()- avoids noisysource: null/reason: nullin every warning entry (verified:to_minimal_dict()omitsNonevalues)- Empty warnings file fails schema validation -
touch()creates empty file which parses asNone, not[], violating thetype: arrayschema (verified experimentally with ruamel.yaml)
Try to provide a more reasonable `stacklevel` to the inner `logging.Logger._log`, skipping any functions that are wrappers around the loggers. The references in the logger's record should point to the most relevant function that requested the log command, or any such indirect commands like the `guest.run`. --- Found that this would be useful in #4642 for getting a more accurate source of the caller and I split it out from there to simplify the review. The stack can be further improved as it goes, as shown in the second commit of this PR, but that can be gradually addressed. --------- Signed-off-by: Cristian Le <git@lecris.dev>
Signed-off-by: Cristian Le <git@lecris.dev>
Signed-off-by: Cristian Le <git@lecris.dev>
Signed-off-by: Cristian Le <git@lecris.dev>
Signed-off-by: Cristian Le <git@lecris.dev>
Signed-off-by: Cristian Le <git@lecris.dev>
Signed-off-by: Cristian Le <git@lecris.dev>
Signed-off-by: Cristian Le <git@lecris.dev>
Signed-off-by: Cristian Le <git@lecris.dev>
Signed-off-by: Cristian Le <git@lecris.dev>
Signed-off-by: Cristian Le <git@lecris.dev>
Signed-off-by: Cristian Le <git@lecris.dev>
Signed-off-by: Cristian Le <git@lecris.dev>
Signed-off-by: Cristian Le <git@lecris.dev>
212c9bc to
2dca253
Compare
|
I am stumped with the failure in Details |
|
@LecrisUT please, set the "Size" of this PR. |
thrix
left a comment
There was a problem hiding this comment.
All review items addressed:
to_minimal_dict()adopted for cleaner YAML outputdelay=Trueadopted to avoid empty-file schema issue (212c9bc)- All previously flagged issues (source/reason swap,
isvs==, workdir init) resolved
Clean implementation with correct stacklevel threading.
Signed-off-by: Cristian Le <git@lecris.dev>
a6ac43f to
67a9210
Compare
Signed-off-by: Cristian Le <git@lecris.dev>
Added a new
RunWarningsHandlerthat is populated as soon as the run workdir is available. All warnings are then saved under thewarnings.yamlfile with a format likeExtended the
logger.warninginterface to be able to specify areasonandsourcethat would be added to these warnings file.Pull Request Checklist
extend the test coveragedeffering for nowmention the versionIt should be part of the story schemaDepends-on #4656
Fixes #4618