-
-
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -159,8 +159,8 @@ def addLevelName(level, levelName): | |
finally: | ||
_releaseLock() | ||
|
||
if hasattr(sys, '_getframe'): | ||
currentframe = lambda: sys._getframe(3) | ||
if hasattr(sys, "_getframe"): | ||
currentframe = lambda: sys._getframe(1) | ||
else: #pragma: no cover | ||
def currentframe(): | ||
"""Return the frame object for the caller's stack frame.""" | ||
|
@@ -184,13 +184,18 @@ def currentframe(): | |
_srcfile = os.path.normcase(addLevelName.__code__.co_filename) | ||
|
||
# _srcfile is only used in conjunction with sys._getframe(). | ||
# To provide compatibility with older versions of Python, set _srcfile | ||
# to None if _getframe() is not available; this value will prevent | ||
# findCaller() from being called. You can also do this if you want to avoid | ||
# the overhead of fetching caller information, even when _getframe() is | ||
# available. | ||
#if not hasattr(sys, '_getframe'): | ||
# _srcfile = None | ||
# Setting _srcfile to None will prevent findCaller() from being called. This | ||
# way, you can avoid the overhead of fetching caller information. | ||
|
||
# The following is based on warnings._is_internal_frame. It makes sure that | ||
# frames of the import mechanism are skipped when logging at module level and | ||
# using a stacklevel value greater than one. | ||
def _is_internal_frame(frame): | ||
"""Signal whether the frame is a CPython or logging module internal.""" | ||
filename = os.path.normcase(frame.f_code.co_filename) | ||
return filename == _srcfile or ( | ||
"importlib" in filename and "_bootstrap" in filename | ||
) | ||
|
||
|
||
def _checkLevel(level): | ||
|
@@ -1558,33 +1563,31 @@ def findCaller(self, stack_info=False, stacklevel=1): | |
f = currentframe() | ||
#On some versions of IronPython, currentframe() returns None if | ||
#IronPython isn't run with -X:Frames. | ||
if f is not None: | ||
f = f.f_back | ||
orig_f = f | ||
while f and stacklevel > 1: | ||
f = f.f_back | ||
stacklevel -= 1 | ||
if not f: | ||
f = orig_f | ||
rv = "(unknown file)", 0, "(unknown function)", None | ||
while hasattr(f, "f_code"): | ||
co = f.f_code | ||
filename = os.path.normcase(co.co_filename) | ||
if filename == _srcfile: | ||
f = f.f_back | ||
continue | ||
sinfo = None | ||
if stack_info: | ||
sio = io.StringIO() | ||
sio.write('Stack (most recent call last):\n') | ||
if f is None: | ||
return "(unknown file)", 0, "(unknown function)", None | ||
while stacklevel > 0: | ||
next_f = f.f_back | ||
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 commentThe reason will be displayed to describe this comment to others. Learn more. So far,
Comment on lines
+1570
to
+1577
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
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 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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. Indeed, but in that case I would still remove the word Anyway, I really appreciate that you took a look at this PR. Thanks! |
||
f = next_f | ||
if not _is_internal_frame(f): | ||
stacklevel -= 1 | ||
co = f.f_code | ||
sinfo = None | ||
if stack_info: | ||
with io.StringIO() as sio: | ||
sio.write("Stack (most recent call last):\n") | ||
traceback.print_stack(f, file=sio) | ||
sinfo = sio.getvalue() | ||
if sinfo[-1] == '\n': | ||
sinfo = sinfo[:-1] | ||
sio.close() | ||
rv = (co.co_filename, f.f_lineno, co.co_name, sinfo) | ||
break | ||
return rv | ||
return co.co_filename, f.f_lineno, co.co_name, sinfo | ||
|
||
def makeRecord(self, name, level, fn, lno, msg, args, exc_info, | ||
func=None, extra=None, sinfo=None): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
Fix handling of the ``stacklevel`` argument to logging functions in the | ||
:mod:`logging` module so that it is consistent accross all logging functions | ||
and, as advertised, similar to the ``stacklevel`` argument used in | ||
:meth:`~warnings.warn`. |
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 notNone
. Thef_code
attribute is prescribed by the CPython data model and thewarnings
module doesn't check for it's presence either (it does check for stack exhaustion/None
, like the new code here).