mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
bpo-45171: Fix stacklevel handling in logging. (GH-28287)
This commit is contained in:
parent
5fd8c574e0
commit
5ca6d7469b
3 changed files with 51 additions and 33 deletions
|
@ -159,8 +159,8 @@ def addLevelName(level, levelName):
|
||||||
finally:
|
finally:
|
||||||
_releaseLock()
|
_releaseLock()
|
||||||
|
|
||||||
if hasattr(sys, '_getframe'):
|
if hasattr(sys, "_getframe"):
|
||||||
currentframe = lambda: sys._getframe(3)
|
currentframe = lambda: sys._getframe(1)
|
||||||
else: #pragma: no cover
|
else: #pragma: no cover
|
||||||
def currentframe():
|
def currentframe():
|
||||||
"""Return the frame object for the caller's stack frame."""
|
"""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 = os.path.normcase(addLevelName.__code__.co_filename)
|
||||||
|
|
||||||
# _srcfile is only used in conjunction with sys._getframe().
|
# _srcfile is only used in conjunction with sys._getframe().
|
||||||
# To provide compatibility with older versions of Python, set _srcfile
|
# Setting _srcfile to None will prevent findCaller() from being called. This
|
||||||
# to None if _getframe() is not available; this value will prevent
|
# way, you can avoid the overhead of fetching caller information.
|
||||||
# findCaller() from being called. You can also do this if you want to avoid
|
|
||||||
# the overhead of fetching caller information, even when _getframe() is
|
# The following is based on warnings._is_internal_frame. It makes sure that
|
||||||
# available.
|
# frames of the import mechanism are skipped when logging at module level and
|
||||||
#if not hasattr(sys, '_getframe'):
|
# using a stacklevel value greater than one.
|
||||||
# _srcfile = None
|
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):
|
def _checkLevel(level):
|
||||||
|
@ -1558,33 +1563,31 @@ class Logger(Filterer):
|
||||||
f = currentframe()
|
f = currentframe()
|
||||||
#On some versions of IronPython, currentframe() returns None if
|
#On some versions of IronPython, currentframe() returns None if
|
||||||
#IronPython isn't run with -X:Frames.
|
#IronPython isn't run with -X:Frames.
|
||||||
if f is not None:
|
if f is None:
|
||||||
f = f.f_back
|
return "(unknown file)", 0, "(unknown function)", None
|
||||||
orig_f = f
|
while stacklevel > 0:
|
||||||
while f and stacklevel > 1:
|
next_f = f.f_back
|
||||||
f = f.f_back
|
if next_f is None:
|
||||||
stacklevel -= 1
|
##TODO: We've got options here
|
||||||
if not f:
|
## If we want to use the last (deepest) frame:
|
||||||
f = orig_f
|
break
|
||||||
rv = "(unknown file)", 0, "(unknown function)", None
|
## If we want to mimic the warnings module:
|
||||||
while hasattr(f, "f_code"):
|
#return ("sys", 1, "(unknown function)", None)
|
||||||
co = f.f_code
|
## If we want to be pedantic:
|
||||||
filename = os.path.normcase(co.co_filename)
|
#raise ValueError("call stack is not deep enough")
|
||||||
if filename == _srcfile:
|
f = next_f
|
||||||
f = f.f_back
|
if not _is_internal_frame(f):
|
||||||
continue
|
stacklevel -= 1
|
||||||
sinfo = None
|
co = f.f_code
|
||||||
if stack_info:
|
sinfo = None
|
||||||
sio = io.StringIO()
|
if stack_info:
|
||||||
sio.write('Stack (most recent call last):\n')
|
with io.StringIO() as sio:
|
||||||
|
sio.write("Stack (most recent call last):\n")
|
||||||
traceback.print_stack(f, file=sio)
|
traceback.print_stack(f, file=sio)
|
||||||
sinfo = sio.getvalue()
|
sinfo = sio.getvalue()
|
||||||
if sinfo[-1] == '\n':
|
if sinfo[-1] == '\n':
|
||||||
sinfo = sinfo[:-1]
|
sinfo = sinfo[:-1]
|
||||||
sio.close()
|
return co.co_filename, f.f_lineno, co.co_name, sinfo
|
||||||
rv = (co.co_filename, f.f_lineno, co.co_name, sinfo)
|
|
||||||
break
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def makeRecord(self, name, level, fn, lno, msg, args, exc_info,
|
def makeRecord(self, name, level, fn, lno, msg, args, exc_info,
|
||||||
func=None, extra=None, sinfo=None):
|
func=None, extra=None, sinfo=None):
|
||||||
|
|
|
@ -5050,9 +5050,10 @@ class LoggerTest(BaseTest, AssertErrorMessage):
|
||||||
|
|
||||||
def test_find_caller_with_stacklevel(self):
|
def test_find_caller_with_stacklevel(self):
|
||||||
the_level = 1
|
the_level = 1
|
||||||
|
trigger = self.logger.warning
|
||||||
|
|
||||||
def innermost():
|
def innermost():
|
||||||
self.logger.warning('test', stacklevel=the_level)
|
trigger('test', stacklevel=the_level)
|
||||||
|
|
||||||
def inner():
|
def inner():
|
||||||
innermost()
|
innermost()
|
||||||
|
@ -5074,6 +5075,16 @@ class LoggerTest(BaseTest, AssertErrorMessage):
|
||||||
self.assertEqual(records[-1].funcName, 'outer')
|
self.assertEqual(records[-1].funcName, 'outer')
|
||||||
self.assertGreater(records[-1].lineno, lineno)
|
self.assertGreater(records[-1].lineno, lineno)
|
||||||
lineno = records[-1].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
|
the_level += 1
|
||||||
outer()
|
outer()
|
||||||
self.assertEqual(records[-1].funcName, 'test_find_caller_with_stacklevel')
|
self.assertEqual(records[-1].funcName, 'test_find_caller_with_stacklevel')
|
||||||
|
|
|
@ -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`.
|
Loading…
Add table
Add a link
Reference in a new issue