-
-
Notifications
You must be signed in to change notification settings - Fork 32k
bpo-45171: Fix stacklevel handling in logging. (GH-28287) #28287
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
f4f78ab
to
a7c6ccb
Compare
Lib/logging/__init__.py
Outdated
@@ -160,9 +160,9 @@ def addLevelName(level, levelName): | |||
_releaseLock() | |||
|
|||
if hasattr(sys, '_getframe'): | |||
currentframe = lambda: sys._getframe(3) | |||
_currentframe = lambda: sys._getframe(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
## 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
# warnings._is_internal_frame | ||
if filename == _srcfile or ( | ||
"importlib" in filename and "_bootstrap" in filename): | ||
): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
break
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or maybe leave it as a reminder in case this needs revisiting for any reason?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
stacklevel
handling code in the logging module:currentframe
was not marked as 'private', which would be desirable.currentframe
was different from the default implementation in that it did not skip any frames.findCaller
(and it did not provide a measurable speed-up).stacklevel
skipping did not use the same logic as in thewarnings
module, 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