From 53388862c295feedb9ddf366af6d1b652b77d54d Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Sat, 29 Jul 2023 16:53:41 +0200 Subject: [PATCH] Don't crash in exception() if outside of an exception (#533) --- CHANGELOG.md | 3 ++- src/structlog/_frames.py | 3 +++ src/structlog/dev.py | 3 ++- tests/test_dev.py | 13 +++++++++++++ tests/test_processors.py | 12 +++++++++++- 5 files changed, 31 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80dfe728..ae8d7ff0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,7 +32,8 @@ You can find our backwards-compatibility policy [here](https://github.com/hynek/ - `FilteringBoundLogger.exception()` and `FilteringBoundLogger.aexception()` now support positional argument formatting like the rest of the methods. [#531](https://github.com/hynek/structlog/issues/531) - +- `structlog.processors.format_exc_info()` and `structlog.dev.ConsoleRenderer` do not crash anymore when told to format a non-existent exception. + [#533](https://github.com/hynek/structlog/issues/533) ## [23.1.0](https://github.com/hynek/structlog/compare/22.3.0...23.1.0) - 2023-04-06 diff --git a/src/structlog/_frames.py b/src/structlog/_frames.py index 6982463d..7db6a486 100644 --- a/src/structlog/_frames.py +++ b/src/structlog/_frames.py @@ -20,6 +20,9 @@ def _format_exception(exc_info: ExcInfo) -> str: Shamelessly stolen from stdlib's logging module. """ + if exc_info == (None, None, None): # type: ignore[comparison-overlap] + return "MISSING" + sio = StringIO() traceback.print_exception(exc_info[0], exc_info[1], exc_info[2], None, sio) diff --git a/src/structlog/dev.py b/src/structlog/dev.py index 8e9ecee7..603dde2d 100644 --- a/src/structlog/dev.py +++ b/src/structlog/dev.py @@ -412,7 +412,8 @@ def __call__( # noqa: PLR0912 if exc_info: exc_info = _figure_out_exc_info(exc_info) - self._exception_formatter(sio, exc_info) + if exc_info != (None, None, None): + self._exception_formatter(sio, exc_info) elif exc is not None: if self._exception_formatter is not plain_traceback: warnings.warn( diff --git a/tests/test_dev.py b/tests/test_dev.py index dfbed0cb..b99966c3 100644 --- a/tests/test_dev.py +++ b/tests/test_dev.py @@ -510,6 +510,19 @@ def test_pickle(self, repr_native_str, force_colors, proto): pickle.dumps(r, proto) )(None, None, {"event": "foo"}) + def test_no_exception(self): + """ + If there is no exception, don't blow up. + """ + r = dev.ConsoleRenderer(colors=False) + + assert ( + "hi" + == r( + None, None, {"event": "hi", "exc_info": (None, None, None)} + ).rstrip() + ) + class TestSetExcInfo: def test_wrong_name(self): diff --git a/tests/test_processors.py b/tests/test_processors.py index 122e6d4d..59d5657c 100644 --- a/tests/test_processors.py +++ b/tests/test_processors.py @@ -560,7 +560,17 @@ def test_format_exception(self): except ValueError as e: a = format_exc_info(None, None, {"exc_info": e}) b = ExceptionRenderer()(None, None, {"exc_info": e}) - assert a == b + + assert a == b + + @pytest.mark.parametrize("ei", [True, (None, None, None)]) + def test_no_exception(self, ei): + """ + A missing exception does not blow up. + """ + assert {"exception": "MISSING"} == format_exc_info( + None, None, {"exc_info": ei} + ) class TestUnicodeEncoder: