From 02a8f9e9acbe55efcbb7ebc3f821d3d2f9cca368 Mon Sep 17 00:00:00 2001 From: Vinay Sajip Date: Sun, 14 Sep 2014 21:29:11 +0100 Subject: 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. --- Doc/library/logging.rst | 13 +++++++++---- Lib/logging/__init__.py | 19 +++++++++---------- Lib/test/test_logging.py | 13 +++++++++++++ Misc/NEWS | 3 +++ 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/Doc/library/logging.rst b/Doc/library/logging.rst index 9983711..d0e2c27 100644 --- a/Doc/library/logging.rst +++ b/Doc/library/logging.rst @@ -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 use keywords in the format string, together with a single dictionary argument.) - There are three keyword arguments in *kwargs* which are inspected: *exc_info* - which, if it does not evaluate as false, causes exception information to be + There are three keyword arguments in *kwargs* which are inspected: + *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 - :func:`sys.exc_info`) is provided, it is used; otherwise, :func:`sys.exc_info` - is called to get the exception information. + :func:`sys.exc_info`) or an exception instance is provided, it is used; + otherwise, :func:`sys.exc_info` is called to get the exception information. The second optional keyword argument is *stack_info*, which defaults to ``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 The *stack_info* parameter was added. + .. versionchanged:: 3.5 + The *exc_info* parameter can now accept exception instances. + .. method:: Logger.info(msg, *args, **kwargs) diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py index 7fb3a35..7628d84 100644 --- a/Lib/logging/__init__.py +++ b/Lib/logging/__init__.py @@ -1302,12 +1302,11 @@ class Logger(Filterer): if self.isEnabledFor(ERROR): 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. """ - kwargs['exc_info'] = True - self.error(msg, *args, **kwargs) + self.error(msg, *args, exc_info=exc_info, **kwargs) def critical(self, msg, *args, **kwargs): """ @@ -1402,7 +1401,9 @@ class Logger(Filterer): else: # pragma: no cover fn, lno, func = "(unknown file)", 0, "(unknown function)" 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() record = self.makeRecord(self.name, level, fn, lno, msg, args, exc_info, func, extra, sinfo) @@ -1612,12 +1613,11 @@ class LoggerAdapter(object): """ 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. """ - kwargs["exc_info"] = True - self.log(ERROR, msg, *args, **kwargs) + self.log(ERROR, msg, *args, exc_info=exc_info, **kwargs) def critical(self, msg, *args, **kwargs): """ @@ -1796,14 +1796,13 @@ def error(msg, *args, **kwargs): basicConfig() 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 information. If the logger has no handlers, basicConfig() is called to add a console handler with a pre-defined format. """ - kwargs['exc_info'] = True - error(msg, *args, **kwargs) + error(msg, *args, exc_info=exc_info, **kwargs) def warning(msg, *args, **kwargs): """ diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index 070e636..7442381 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -3712,6 +3712,19 @@ class LoggerAdapterTest(unittest.TestCase): self.assertEqual(record.exc_info, (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): msg = 'critical test! %r' self.adapter.critical(msg, self.recording) diff --git a/Misc/NEWS b/Misc/NEWS index ea16479..3be837d 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -132,6 +132,9 @@ Core and Builtins 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 when it is run with pythonw.exe. -- cgit v0.12