mirror of
				https://github.com/python/cpython.git
				synced 2025-11-04 07:31:38 +00:00 
			
		
		
		
	bpo-33165: Added stacklevel parameter to logging APIs. (GH-7424)
This commit is contained in:
		
							parent
							
								
									9ef1b0690b
								
							
						
					
					
						commit
						dde9fdbe45
					
				
					 4 changed files with 77 additions and 19 deletions
				
			
		| 
						 | 
					@ -160,8 +160,8 @@ is the module's name in the Python package namespace.
 | 
				
			||||||
      *msg* using the string formatting operator. (Note that this means that you can
 | 
					      *msg* using the string formatting operator. (Note that this means that you can
 | 
				
			||||||
      use keywords in the format string, together with a single dictionary argument.)
 | 
					      use keywords in the format string, together with a single dictionary argument.)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      There are three keyword arguments in *kwargs* which are inspected:
 | 
					      There are four keyword arguments in *kwargs* which are inspected:
 | 
				
			||||||
      *exc_info*, *stack_info*, and *extra*.
 | 
					      *exc_info*, *stack_info*, *stacklevel* and *extra*.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      If *exc_info* does not evaluate as false, it causes exception information to be
 | 
					      If *exc_info* does not evaluate as false, it causes exception information to be
 | 
				
			||||||
      added to the logging message. If an exception tuple (in the format returned by
 | 
					      added to the logging message. If an exception tuple (in the format returned by
 | 
				
			||||||
| 
						 | 
					@ -188,11 +188,19 @@ is the module's name in the Python package namespace.
 | 
				
			||||||
      This mimics the ``Traceback (most recent call last):`` which is used when
 | 
					      This mimics the ``Traceback (most recent call last):`` which is used when
 | 
				
			||||||
      displaying exception frames.
 | 
					      displaying exception frames.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      The third keyword argument is *extra* which can be used to pass a
 | 
					      The third optional keyword argument is *stacklevel*, which defaults to ``1``.
 | 
				
			||||||
      dictionary which is used to populate the __dict__ of the LogRecord created for
 | 
					      If greater than 1, the corresponding number of stack frames are skipped
 | 
				
			||||||
      the logging event with user-defined attributes. These custom attributes can then
 | 
					      when computing the line number and function name set in the :class:`LogRecord`
 | 
				
			||||||
      be used as you like. For example, they could be incorporated into logged
 | 
					      created for the logging event. This can be used in logging helpers so that
 | 
				
			||||||
      messages. For example::
 | 
					      the function name, filename and line number recorded are not the information
 | 
				
			||||||
 | 
					      for the helper function/method, but rather its caller. The name of this
 | 
				
			||||||
 | 
					      parameter mirrors the equivalent one in the :mod:`warnings` module.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      The fourth keyword argument is *extra* which can be used to pass a
 | 
				
			||||||
 | 
					      dictionary which is used to populate the __dict__ of the :class:`LogRecord`
 | 
				
			||||||
 | 
					      created for the logging event with user-defined attributes. These custom
 | 
				
			||||||
 | 
					      attributes can then be used as you like. For example, they could be
 | 
				
			||||||
 | 
					      incorporated into logged messages. For example::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
         FORMAT = '%(asctime)-15s %(clientip)s %(user)-8s %(message)s'
 | 
					         FORMAT = '%(asctime)-15s %(clientip)s %(user)-8s %(message)s'
 | 
				
			||||||
         logging.basicConfig(format=FORMAT)
 | 
					         logging.basicConfig(format=FORMAT)
 | 
				
			||||||
| 
						 | 
					@ -213,9 +221,9 @@ is the module's name in the Python package namespace.
 | 
				
			||||||
      If you choose to use these attributes in logged messages, you need to exercise
 | 
					      If you choose to use these attributes in logged messages, you need to exercise
 | 
				
			||||||
      some care. In the above example, for instance, the :class:`Formatter` has been
 | 
					      some care. In the above example, for instance, the :class:`Formatter` has been
 | 
				
			||||||
      set up with a format string which expects 'clientip' and 'user' in the attribute
 | 
					      set up with a format string which expects 'clientip' and 'user' in the attribute
 | 
				
			||||||
      dictionary of the LogRecord. If these are missing, the message will not be
 | 
					      dictionary of the :class:`LogRecord`. If these are missing, the message will
 | 
				
			||||||
      logged because a string formatting exception will occur. So in this case, you
 | 
					      not be logged because a string formatting exception will occur. So in this case,
 | 
				
			||||||
      always need to pass the *extra* dictionary with these keys.
 | 
					      you always need to pass the *extra* dictionary with these keys.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      While this might be annoying, this feature is intended for use in specialized
 | 
					      While this might be annoying, this feature is intended for use in specialized
 | 
				
			||||||
      circumstances, such as multi-threaded servers where the same code executes in
 | 
					      circumstances, such as multi-threaded servers where the same code executes in
 | 
				
			||||||
| 
						 | 
					@ -230,6 +238,9 @@ is the module's name in the Python package namespace.
 | 
				
			||||||
      .. versionchanged:: 3.5
 | 
					      .. versionchanged:: 3.5
 | 
				
			||||||
         The *exc_info* parameter can now accept exception instances.
 | 
					         The *exc_info* parameter can now accept exception instances.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      .. versionadded:: 3.8
 | 
				
			||||||
 | 
					         The *stacklevel* parameter was added.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   .. method:: Logger.info(msg, *args, **kwargs)
 | 
					   .. method:: Logger.info(msg, *args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -300,12 +311,19 @@ is the module's name in the Python package namespace.
 | 
				
			||||||
      Removes the specified handler *hdlr* from this logger.
 | 
					      Removes the specified handler *hdlr* from this logger.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   .. method:: Logger.findCaller(stack_info=False)
 | 
					   .. method:: Logger.findCaller(stack_info=False, stacklevel=1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      Finds the caller's source filename and line number. Returns the filename, line
 | 
					      Finds the caller's source filename and line number. Returns the filename, line
 | 
				
			||||||
      number, function name and stack information as a 4-element tuple. The stack
 | 
					      number, function name and stack information as a 4-element tuple. The stack
 | 
				
			||||||
      information is returned as ``None`` unless *stack_info* is ``True``.
 | 
					      information is returned as ``None`` unless *stack_info* is ``True``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      The *stacklevel* parameter is passed from code calling the :meth:`debug`
 | 
				
			||||||
 | 
					      and other APIs. If greater than 1, the excess is used to skip stack frames
 | 
				
			||||||
 | 
					      before determining the values to be returned. This will generally be useful
 | 
				
			||||||
 | 
					      when calling logging APIs from helper/wrapper code, so that the information
 | 
				
			||||||
 | 
					      in the event log refers not to the helper/wrapper code, but to the code that
 | 
				
			||||||
 | 
					      calls it.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   .. method:: Logger.handle(record)
 | 
					   .. method:: Logger.handle(record)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -646,9 +664,9 @@ sophisticated criteria than levels, they get to see every record which is
 | 
				
			||||||
processed by the handler or logger they're attached to: this can be useful if
 | 
					processed by the handler or logger they're attached to: this can be useful if
 | 
				
			||||||
you want to do things like counting how many records were processed by a
 | 
					you want to do things like counting how many records were processed by a
 | 
				
			||||||
particular logger or handler, or adding, changing or removing attributes in
 | 
					particular logger or handler, or adding, changing or removing attributes in
 | 
				
			||||||
the LogRecord being processed. Obviously changing the LogRecord needs to be
 | 
					the :class:`LogRecord` being processed. Obviously changing the LogRecord needs
 | 
				
			||||||
done with some care, but it does allow the injection of contextual information
 | 
					to be done with some care, but it does allow the injection of contextual
 | 
				
			||||||
into logs (see :ref:`filters-contextual`).
 | 
					information into logs (see :ref:`filters-contextual`).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. _log-record:
 | 
					.. _log-record:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -702,13 +720,13 @@ wire).
 | 
				
			||||||
      be used.
 | 
					      be used.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   .. versionchanged:: 3.2
 | 
					   .. versionchanged:: 3.2
 | 
				
			||||||
      The creation of a ``LogRecord`` has been made more configurable by
 | 
					      The creation of a :class:`LogRecord` has been made more configurable by
 | 
				
			||||||
      providing a factory which is used to create the record. The factory can be
 | 
					      providing a factory which is used to create the record. The factory can be
 | 
				
			||||||
      set using :func:`getLogRecordFactory` and :func:`setLogRecordFactory`
 | 
					      set using :func:`getLogRecordFactory` and :func:`setLogRecordFactory`
 | 
				
			||||||
      (see this for the factory's signature).
 | 
					      (see this for the factory's signature).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   This functionality can be used to inject your own values into a
 | 
					   This functionality can be used to inject your own values into a
 | 
				
			||||||
   LogRecord at creation time. You can use the following pattern::
 | 
					   :class:`LogRecord` at creation time. You can use the following pattern::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      old_factory = logging.getLogRecordFactory()
 | 
					      old_factory = logging.getLogRecordFactory()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1397,7 +1397,7 @@ def log(self, level, msg, *args, **kwargs):
 | 
				
			||||||
        if self.isEnabledFor(level):
 | 
					        if self.isEnabledFor(level):
 | 
				
			||||||
            self._log(level, msg, args, **kwargs)
 | 
					            self._log(level, msg, args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def findCaller(self, stack_info=False):
 | 
					    def findCaller(self, stack_info=False, stacklevel=1):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Find the stack frame of the caller so that we can note the source
 | 
					        Find the stack frame of the caller so that we can note the source
 | 
				
			||||||
        file name, line number and function name.
 | 
					        file name, line number and function name.
 | 
				
			||||||
| 
						 | 
					@ -1407,6 +1407,12 @@ def findCaller(self, stack_info=False):
 | 
				
			||||||
        #IronPython isn't run with -X:Frames.
 | 
					        #IronPython isn't run with -X:Frames.
 | 
				
			||||||
        if f is not None:
 | 
					        if f is not None:
 | 
				
			||||||
            f = f.f_back
 | 
					            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
 | 
					        rv = "(unknown file)", 0, "(unknown function)", None
 | 
				
			||||||
        while hasattr(f, "f_code"):
 | 
					        while hasattr(f, "f_code"):
 | 
				
			||||||
            co = f.f_code
 | 
					            co = f.f_code
 | 
				
			||||||
| 
						 | 
					@ -1442,7 +1448,8 @@ def makeRecord(self, name, level, fn, lno, msg, args, exc_info,
 | 
				
			||||||
                rv.__dict__[key] = extra[key]
 | 
					                rv.__dict__[key] = extra[key]
 | 
				
			||||||
        return rv
 | 
					        return rv
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False):
 | 
					    def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False,
 | 
				
			||||||
 | 
					             stacklevel=1):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Low-level logging routine which creates a LogRecord and then calls
 | 
					        Low-level logging routine which creates a LogRecord and then calls
 | 
				
			||||||
        all the handlers of this logger to handle the record.
 | 
					        all the handlers of this logger to handle the record.
 | 
				
			||||||
| 
						 | 
					@ -1453,7 +1460,7 @@ def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False):
 | 
				
			||||||
            #exception on some versions of IronPython. We trap it here so that
 | 
					            #exception on some versions of IronPython. We trap it here so that
 | 
				
			||||||
            #IronPython can use logging.
 | 
					            #IronPython can use logging.
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                fn, lno, func, sinfo = self.findCaller(stack_info)
 | 
					                fn, lno, func, sinfo = self.findCaller(stack_info, stacklevel)
 | 
				
			||||||
            except ValueError: # pragma: no cover
 | 
					            except ValueError: # pragma: no cover
 | 
				
			||||||
                fn, lno, func = "(unknown file)", 0, "(unknown function)"
 | 
					                fn, lno, func = "(unknown file)", 0, "(unknown function)"
 | 
				
			||||||
        else: # pragma: no cover
 | 
					        else: # pragma: no cover
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4057,6 +4057,37 @@ def test_find_caller_with_stack_info(self):
 | 
				
			||||||
        self.assertEqual(len(called), 1)
 | 
					        self.assertEqual(len(called), 1)
 | 
				
			||||||
        self.assertEqual('Stack (most recent call last):\n', called[0])
 | 
					        self.assertEqual('Stack (most recent call last):\n', called[0])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_find_caller_with_stacklevel(self):
 | 
				
			||||||
 | 
					        the_level = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def innermost():
 | 
				
			||||||
 | 
					            self.logger.warning('test', stacklevel=the_level)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def inner():
 | 
				
			||||||
 | 
					            innermost()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def outer():
 | 
				
			||||||
 | 
					            inner()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        records = self.recording.records
 | 
				
			||||||
 | 
					        outer()
 | 
				
			||||||
 | 
					        self.assertEqual(records[-1].funcName, 'innermost')
 | 
				
			||||||
 | 
					        lineno = records[-1].lineno
 | 
				
			||||||
 | 
					        the_level += 1
 | 
				
			||||||
 | 
					        outer()
 | 
				
			||||||
 | 
					        self.assertEqual(records[-1].funcName, 'inner')
 | 
				
			||||||
 | 
					        self.assertGreater(records[-1].lineno, lineno)
 | 
				
			||||||
 | 
					        lineno = records[-1].lineno
 | 
				
			||||||
 | 
					        the_level += 1
 | 
				
			||||||
 | 
					        outer()
 | 
				
			||||||
 | 
					        self.assertEqual(records[-1].funcName, 'outer')
 | 
				
			||||||
 | 
					        self.assertGreater(records[-1].lineno, lineno)
 | 
				
			||||||
 | 
					        lineno = records[-1].lineno
 | 
				
			||||||
 | 
					        the_level += 1
 | 
				
			||||||
 | 
					        outer()
 | 
				
			||||||
 | 
					        self.assertEqual(records[-1].funcName, 'test_find_caller_with_stacklevel')
 | 
				
			||||||
 | 
					        self.assertGreater(records[-1].lineno, lineno)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_make_record_with_extra_overwrite(self):
 | 
					    def test_make_record_with_extra_overwrite(self):
 | 
				
			||||||
        name = 'my record'
 | 
					        name = 'my record'
 | 
				
			||||||
        level = 13
 | 
					        level = 13
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					Added a stacklevel parameter to logging calls to allow use of wrapper/helper
 | 
				
			||||||
 | 
					functions for logging APIs.
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue