Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ You can find our backwards-compatibility policy [here](https://github.com/hynek/
- Support for Python 3.14 and Python 3.13.4.
[#723](https://github.com/hynek/structlog/pull/723)

### Fixed

- `structlog.processors.ExceptionPrettyPrinter` now respects the *exception_formatter* arguments instead of always using the default formatter.


## [25.3.0](https://github.com/hynek/structlog/compare/25.2.0...25.3.0) - 2025-04-25

Expand Down
17 changes: 13 additions & 4 deletions src/structlog/processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -626,16 +626,21 @@ def _figure_out_exc_info(v: Any) -> ExcInfo | None:

class ExceptionPrettyPrinter:
"""
Pretty print exceptions and remove them from the ``event_dict``.
Pretty print exceptions rendered by *exception_formatter* and remove them
from the ``event_dict``.

Args:
file: Target file for output (default: ``sys.stdout``).
exception_formatter:
A callable that is used to format the exception from the
``exc_info`` field into the ``exception`` field.

This processor is mostly for development and testing so you can read
exceptions properly formatted.

It behaves like `format_exc_info` except it removes the exception data from
the event dictionary after printing it.
It behaves like `format_exc_info`, except that it removes the exception data
from the event dictionary after printing it using the passed
*exception_formatter*, which defaults to Python's built-in traceback formatting.

It's tolerant to having `format_exc_info` in front of itself in the
processor chain but doesn't require it. In other words, it handles both
Expand All @@ -645,13 +650,17 @@ class ExceptionPrettyPrinter:

.. versionchanged:: 16.0.0
Added support for passing exceptions as ``exc_info`` on Python 3.

.. versionchanged:: 25.4.0
Fixed *exception_formatter* so that it overrides the default if set.
"""

def __init__(
self,
file: TextIO | None = None,
exception_formatter: ExceptionTransformer = _format_exception,
) -> None:
self.format_exception = exception_formatter
if file is not None:
self._file = file
else:
Expand All @@ -664,7 +673,7 @@ def __call__(
if exc is None:
exc_info = _figure_out_exc_info(event_dict.pop("exc_info", None))
if exc_info:
exc = _format_exception(exc_info)
exc = self.format_exception(exc_info)

if exc:
print(exc, file=self._file)
Expand Down
22 changes: 21 additions & 1 deletion tests/processors/test_processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
format_exc_info,
)
from structlog.stdlib import ProcessorFormatter
from structlog.typing import EventDict
from structlog.typing import EventDict, ExcInfo

from ..additional_frame import additional_frame

Expand Down Expand Up @@ -125,6 +125,26 @@ def test_prints_exception(self, sio):
assert "test_prints_exception" in out
assert "raise ValueError" in out

def test_uses_exception_formatter(self, sio):
"""
If an `exception_formatter` is passed, use that to render the
exception rather than the default.
"""

def formatter(exc_info: ExcInfo) -> str:
return f"error: {exc_info}"

epp = ExceptionPrettyPrinter(file=sio, exception_formatter=formatter)
try:
raise ValueError
except ValueError as e:
epp(None, None, {"exc_info": True})
formatted = formatter(e)

out = sio.getvalue()

assert formatted in out

def test_removes_exception_after_printing(self, sio):
"""
After pretty printing `exception` is removed from the event_dict.
Expand Down