bpo-45171: Fix stacklevel handling in logging. (GH-28287)

This commit is contained in:
Jouke Witteveen 2022-03-27 15:49:28 +02:00 committed by GitHub
parent 5fd8c574e0
commit 5ca6d7469b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 51 additions and 33 deletions

View File

@ -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 @@ else: #pragma: no cover
_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 @@ class Logger(Filterer):
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")
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):

View File

@ -5050,9 +5050,10 @@ class LoggerTest(BaseTest, AssertErrorMessage):
def test_find_caller_with_stacklevel(self):
the_level = 1
trigger = self.logger.warning
def innermost():
self.logger.warning('test', stacklevel=the_level)
trigger('test', stacklevel=the_level)
def inner():
innermost()
@ -5074,6 +5075,16 @@ class LoggerTest(BaseTest, AssertErrorMessage):
self.assertEqual(records[-1].funcName, 'outer')
self.assertGreater(records[-1].lineno, lineno)
lineno = records[-1].lineno
trigger = self.logger.warn
outer()
self.assertEqual(records[-1].funcName, 'outer')
root_logger = logging.getLogger()
root_logger.addHandler(self.recording)
trigger = logging.warning
outer()
self.assertEqual(records[-1].funcName, 'outer')
root_logger.removeHandler(self.recording)
trigger = self.logger.warning
the_level += 1
outer()
self.assertEqual(records[-1].funcName, 'test_find_caller_with_stacklevel')

View File

@ -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`.