bpo-45171: Fix stacklevel handling in logging. (GH-28287)#28287
bpo-45171: Fix stacklevel handling in logging. (GH-28287)#28287vsajip merged 1 commit intopython:mainfrom
Conversation
f4f78ab to
a7c6ccb
Compare
Lib/logging/__init__.py
Outdated
There was a problem hiding this comment.
I am not sure if renaming this now is good because probably there is code out there using this, and this would break and force them up to add the infamous if python version < xxx.
There was a problem hiding this comment.
I agree and restored the original name. However, I do think either renaming currentframe to _currentframe or documenting it as an official method should be considered.
Lib/logging/__init__.py
Outdated
There was a problem hiding this comment.
So far, findCaller did not throw, so to keep the more backward-compatible I think it would be better if it did not throw.
Lib/logging/__init__.py
Outdated
There was a problem hiding this comment.
Why start skipping importlib and _bootstrap? Does the warning module have a reason for that? Why would the logging module benefit from it? If so, I would it would be better just call their function _is_internal_frame instead of copying its contents.
There was a problem hiding this comment.
Initially, the warnings module was used for it, but then I realized that no underscore methods of other modules are used in this file. Therefore, this implementation of logging could in principle be used in Python implementations that have a different implementation of warnings. For this reason, I chose to duplicate that piece of code. I noted that the currentframe code was initially lifted from inspect.
There was a problem hiding this comment.
Skipping importlib._bootstrap was done to allow module-level warnings (here: log messages) with stacklevels that point to the importing module and not to the import mechanism. A useful thing, I'd say.
There was a problem hiding this comment.
Thanks for explaining it. I am not sure if it would be a problem for the logging module to show the stack trace from importlib or _bootstrap as it was a problem for the warning module. What if I am using the logging module inside importlib? Disregarding this last scenario, I would wait to see what someone more experienced with Python library think about this.
Just out of curiosity, did you have trouble with the logging module showing stack traces of importlib or _bootstrap which lead you to bring this checking from the warning module or you just happened to know about their protection and thought it would be good to add it here too?
There was a problem hiding this comment.
It is not "importlib or _bootstrap" that is checked for, but "importlib and _bootstrap" (i.e. importlib._bootstrap). There is no logging in Lib/importlib/_bootstrap.py and that module doesn't import anything (for obvious reasons), so I think we can safely say that the logging module is not used in importlib._bootstrap :-).
I ran into the stacklevel oddity mentioned in bpo-45171 and read that stacklevel was supposed to work like "the equivalent [parameter] in the warnings module". Thus I read how warnings chases the stack and copied the logic to logging.
214a707 to
188e6d9
Compare
188e6d9 to
a35493e
Compare
| if not f: | ||
| f = orig_f | ||
| rv = "(unknown file)", 0, "(unknown function)", None | ||
| while hasattr(f, "f_code"): |
There was a problem hiding this comment.
Note that the new code doesn't verify that frame object contain an f_code attribute. It does verify that the frame object is not None. The f_code attribute is prescribed by the CPython data model and the warnings module doesn't check for it's presence either (it does check for stack exhaustion/None, like the new code here).
|
This PR is stale because it has been open for 30 days with no activity. |
|
@vsajip: I'm sorry to bother you, but you seem to be the chief-of- |
|
@vsajip, I still stand behind this MR since it solves a real bug (the current code does not pass the tests added in this MR). Could you comment on its feasibility? |
|
@joukewitteveen Sorry, been busy lately with other commitments. Will get to looking at this as soon as I can. |
|
I'll close and reopen to unblock stalled checks. |
| if next_f is None: | ||
| ##TODO: We've got options here | ||
| ## If we want to use the last (deepest) frame: | ||
| break | ||
| ## If we want to mimic the warnings module: | ||
| #return ("sys", 1, "(unknown function)", None) | ||
| ## If we want to be pedantic: | ||
| #raise ValueError("call stack is not deep enough") |
There was a problem hiding this comment.
@vsajip Thanks for the approval and merge! Note that there was a TODO here which I summarized in the original PR description as:
Regarding the last point, I did not know what to do in case more frames are to be skipped than the height of the stack. I listed three possibilities in the code which I think would all be sensible.
Having it merged like this is not wrong per se (it will use the deepest available frame if the stack is exhausted), but it would be good to at least remove the TODO: part on line 1571. Maybe all of the above could even be replaced with
if next_f is None:
# Not enough stack frames. We use the last (deepest) one.
breakThere was a problem hiding this comment.
Or maybe leave it as a reminder in case this needs revisiting for any reason?
There was a problem hiding this comment.
Indeed, but in that case I would still remove the word TODO:, since nothing needs to be done.
Anyway, I really appreciate that you took a look at this PR. Thanks!
I identified a few problems with the
stacklevelhandling code in the logging module:currentframewas not marked as 'private', which would be desirable.currentframewas different from the default implementation in that it did not skip any frames.findCaller(and it did not provide a measurable speed-up).stacklevelskipping did not use the same logic as in thewarningsmodule, despite the documentation describing the argument as "The name of this parameter mirrors the equivalent one in the warnings module."Regarding the last point, I did not know what to do in case more frames are to be skipped than the height of the stack. I listed three possibilities in the code which I think would all be sensible. The current behavior of using the top of the stack seems very odd to me.
https://bugs.python.org/issue45171