Save all warnings encountered in a warnings file#4642
Save all warnings encountered in a warnings file#4642LecrisUT wants to merge 7 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
|
|
||
| class RunWarningsFilter(logging.Filter): | ||
| def filter(self, record: logging.LogRecord) -> bool: | ||
| if record.levelno is logging.WARNING: |
There was a problem hiding this comment.
Uses identity comparison (is) instead of equality comparison (==) for integer values. The is operator checks object identity, not value equality. While this might work for logging.WARNING (value 30) due to Python's integer caching for small numbers, it's incorrect and unreliable. This should be:
if record.levelno == logging.WARNING:Without this fix, the filter may fail to capture warning messages, causing them not to be written to the warnings file.
| if record.levelno is logging.WARNING: | |
| if record.levelno == logging.WARNING: |
Spotted by Graphite
Is this helpful? React 👍 or 👎 to let us know.
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>
4c7e678 to
0a17aa8
Compare
Pull Request Checklist
Fixes #4618