Closes #20537: logging methods now accept an exception instance as well as a Boolean value or exception tuple. Thanks to Yury Selivanov for the patch.

This commit is contained in:
Vinay Sajip 2014-09-14 21:29:11 +01:00
parent 4ff91eb5e3
commit 02a8f9e9ac
4 changed files with 34 additions and 14 deletions

View file

@ -155,11 +155,13 @@ 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: *exc_info* There are three keyword arguments in *kwargs* which are inspected:
which, if it does not evaluate as false, causes exception information to be *exc_info*, *stack_info*, and *extra*.
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
:func:`sys.exc_info`) is provided, it is used; otherwise, :func:`sys.exc_info` :func:`sys.exc_info`) or an exception instance is provided, it is used;
is called to get the exception information. otherwise, :func:`sys.exc_info` is called to get the exception information.
The second optional keyword argument is *stack_info*, which defaults to The second optional keyword argument is *stack_info*, which defaults to
``False``. If true, stack information is added to the logging ``False``. If true, stack information is added to the logging
@ -216,6 +218,9 @@ is the module's name in the Python package namespace.
.. versionadded:: 3.2 .. versionadded:: 3.2
The *stack_info* parameter was added. The *stack_info* parameter was added.
.. versionchanged:: 3.5
The *exc_info* parameter can now accept exception instances.
.. method:: Logger.info(msg, *args, **kwargs) .. method:: Logger.info(msg, *args, **kwargs)

View file

@ -1302,12 +1302,11 @@ class Logger(Filterer):
if self.isEnabledFor(ERROR): if self.isEnabledFor(ERROR):
self._log(ERROR, msg, args, **kwargs) self._log(ERROR, msg, args, **kwargs)
def exception(self, msg, *args, **kwargs): def exception(self, msg, *args, exc_info=True, **kwargs):
""" """
Convenience method for logging an ERROR with exception information. Convenience method for logging an ERROR with exception information.
""" """
kwargs['exc_info'] = True self.error(msg, *args, exc_info=exc_info, **kwargs)
self.error(msg, *args, **kwargs)
def critical(self, msg, *args, **kwargs): def critical(self, msg, *args, **kwargs):
""" """
@ -1402,7 +1401,9 @@ class Logger(Filterer):
else: # pragma: no cover else: # pragma: no cover
fn, lno, func = "(unknown file)", 0, "(unknown function)" fn, lno, func = "(unknown file)", 0, "(unknown function)"
if exc_info: if exc_info:
if not isinstance(exc_info, tuple): if isinstance(exc_info, BaseException):
exc_info = (type(exc_info), exc_info, exc_info.__traceback__)
elif not isinstance(exc_info, tuple):
exc_info = sys.exc_info() exc_info = sys.exc_info()
record = self.makeRecord(self.name, level, fn, lno, msg, args, record = self.makeRecord(self.name, level, fn, lno, msg, args,
exc_info, func, extra, sinfo) exc_info, func, extra, sinfo)
@ -1612,12 +1613,11 @@ class LoggerAdapter(object):
""" """
self.log(ERROR, msg, *args, **kwargs) self.log(ERROR, msg, *args, **kwargs)
def exception(self, msg, *args, **kwargs): def exception(self, msg, *args, exc_info=True, **kwargs):
""" """
Delegate an exception call to the underlying logger. Delegate an exception call to the underlying logger.
""" """
kwargs["exc_info"] = True self.log(ERROR, msg, *args, exc_info=exc_info, **kwargs)
self.log(ERROR, msg, *args, **kwargs)
def critical(self, msg, *args, **kwargs): def critical(self, msg, *args, **kwargs):
""" """
@ -1796,14 +1796,13 @@ def error(msg, *args, **kwargs):
basicConfig() basicConfig()
root.error(msg, *args, **kwargs) root.error(msg, *args, **kwargs)
def exception(msg, *args, **kwargs): def exception(msg, *args, exc_info=True, **kwargs):
""" """
Log a message with severity 'ERROR' on the root logger, with exception Log a message with severity 'ERROR' on the root logger, with exception
information. If the logger has no handlers, basicConfig() is called to add information. If the logger has no handlers, basicConfig() is called to add
a console handler with a pre-defined format. a console handler with a pre-defined format.
""" """
kwargs['exc_info'] = True error(msg, *args, exc_info=exc_info, **kwargs)
error(msg, *args, **kwargs)
def warning(msg, *args, **kwargs): def warning(msg, *args, **kwargs):
""" """

View file

@ -3712,6 +3712,19 @@ class LoggerAdapterTest(unittest.TestCase):
self.assertEqual(record.exc_info, self.assertEqual(record.exc_info,
(exc.__class__, exc, exc.__traceback__)) (exc.__class__, exc, exc.__traceback__))
def test_exception_excinfo(self):
try:
1 / 0
except ZeroDivisionError as e:
exc = e
self.adapter.exception('exc_info test', exc_info=exc)
self.assertEqual(len(self.recording.records), 1)
record = self.recording.records[0]
self.assertEqual(record.exc_info,
(exc.__class__, exc, exc.__traceback__))
def test_critical(self): def test_critical(self):
msg = 'critical test! %r' msg = 'critical test! %r'
self.adapter.critical(msg, self.recording) self.adapter.critical(msg, self.recording)

View file

@ -132,6 +132,9 @@ Core and Builtins
Library Library
------- -------
- Issue #20537: logging methods now accept an exception instance as well as a
Boolean value or exception tuple. Thanks to Yury Selivanov for the patch.
- Issue #22384: An exception in Tkinter callback no longer crashes the program - Issue #22384: An exception in Tkinter callback no longer crashes the program
when it is run with pythonw.exe. when it is run with pythonw.exe.