Skip to content

Commit d517cde

Browse files
authored
Improve ConsoleRenderer ergonomics (hynek#749)
* Add structlog.dev.get_active_console_renderer() * Allow setting exception_formatter
1 parent 11e0872 commit d517cde

File tree

7 files changed

+155
-4
lines changed

7 files changed

+155
-4
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,19 @@ You can find our backwards-compatibility policy [here](https://github.com/hynek/
1717

1818
### Added
1919

20+
- Added `structlog.dev.get_active_console_renderer()` that returns the currently active `structlog.dev.ConsoleRenderer()`.
21+
[#749](https://github.com/hynek/structlog/pull/749)
22+
23+
- `structlog.dev.ConsoleRenderer()` now supports setting the `exception_formatter` attribute.
24+
25+
You can now disable the pretty-printing of exceptions by setting it to `structlog.dev.plain_traceback`:
26+
27+
```python
28+
cr = structlog.dev.get_active_console_renderer()
29+
cr.exception_formatter = structlog.dev.plain_traceback
30+
```
31+
[#749](https://github.com/hynek/structlog/pull/749)
32+
2033
- Added `structlog.dev.ConsoleRenderer.get_default_column_styles` for reuse the default column styles.
2134
[#741](https://github.com/hynek/structlog/pull/741)
2235

docs/api.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,11 @@ API Reference
7676
.. automodule:: structlog.dev
7777

7878
.. autoclass:: ConsoleRenderer
79-
:members: get_default_level_styles, get_default_column_styles
79+
:members: get_default_level_styles, get_default_column_styles, exception_formatter
80+
81+
.. autofunction:: get_active_console_renderer
82+
.. autoexception:: NoConsoleRendererConfiguredError
83+
.. autoexception:: MultipleConsoleRenderersConfiguredError
8084

8185
.. autoclass:: Column
8286
.. autoclass:: ColumnFormatter(typing.Protocol)

docs/console-output.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,11 @@ It's possible to override this behavior by setting two standard environment vari
121121

122122
## Disabling exception pretty-printing
123123

124-
If you prefer the default terse Exception rendering, but still want Rich installed, you can disable the pretty-printing by instantiating {class}`structlog.dev.ConsoleRenderer()` yourself and passing `exception_formatter=structlog.dev.plain_traceback`.
124+
If you prefer the default terse Exception rendering, but still want Rich installed, you can disable the auto-enabled pretty-printing by configuring your {class}`~structlog.dev.ConsoleRenderer` to use {class}`structlog.dev.plain_traceback`.
125+
126+
You can either instantiate {class}`structlog.dev.ConsoleRenderer()` yourself and pass `exception_formatter=structlog.dev.plain_traceback`, or set the `exception_formatter` attribute of the active console renderer to it:
127+
128+
```python
129+
cr = structlog.dev.get_active_console_renderer()
130+
cr.exception_formatter = structlog.dev.plain_traceback
131+
```

src/structlog/dev.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
)
3131

3232
from ._frames import _format_exception
33+
from .exceptions import (
34+
MultipleConsoleRenderersConfiguredError,
35+
NoConsoleRendererConfiguredError,
36+
)
3337
from .processors import _figure_out_exc_info
3438
from .typing import EventDict, ExceptionRenderer, ExcInfo, WrappedLogger
3539

@@ -463,6 +467,13 @@ class ConsoleRenderer:
463467
*after* the log line. If Rich_ or better-exceptions_ are present, in colors
464468
and with extra context.
465469
470+
Tip:
471+
Since `ConsoleRenderer` is mainly a development helper, it is less
472+
strict about immutability than the rest of *structlog* for better
473+
ergonomics. Notably, the currently active instance can be obtained by
474+
calling `get_active_console_renderer()` and it offers properties to
475+
configure its behavior after instantiation.
476+
466477
Args:
467478
columns:
468479
A list of `Column` objects defining both the order and format of
@@ -829,6 +840,22 @@ def get_default_level_styles(colors: bool = True) -> dict[str, str]:
829840
"notset": styles.level_notset,
830841
}
831842

843+
@property
844+
def exception_formatter(self) -> ExceptionRenderer:
845+
"""
846+
The exception formatter used by this console renderer.
847+
848+
.. versionadded:: 25.5.0
849+
"""
850+
return self._exception_formatter
851+
852+
@exception_formatter.setter
853+
def exception_formatter(self, value: ExceptionRenderer) -> None:
854+
"""
855+
.. versionadded:: 25.5.0
856+
"""
857+
self._exception_formatter = value
858+
832859

833860
_SENTINEL = object()
834861

@@ -852,3 +879,35 @@ def set_exc_info(
852879
event_dict["exc_info"] = True
853880

854881
return event_dict
882+
883+
884+
def get_active_console_renderer() -> ConsoleRenderer:
885+
"""
886+
If *structlog* is configured to use `ConsoleRenderer`, it's returned.
887+
888+
It does not have to be the last processor.
889+
890+
Raises:
891+
NoConsoleRendererConfiguredError:
892+
If no ConsoleRenderer is found in the current configuration.
893+
894+
MultipleConsoleRenderersConfiguredError:
895+
If more than one is found in the current configuration. This is
896+
almost certainly a bug.
897+
898+
.. versionadded:: 25.5.0
899+
"""
900+
from ._config import get_config
901+
902+
cr = None
903+
for p in get_config()["processors"]:
904+
if isinstance(p, ConsoleRenderer):
905+
if cr is not None:
906+
raise MultipleConsoleRenderersConfiguredError
907+
908+
cr = p
909+
910+
if cr is None:
911+
raise NoConsoleRendererConfiguredError
912+
913+
return cr

src/structlog/exceptions.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,20 @@ class DropEvent(BaseException):
1616
1717
Derives from BaseException because it's technically not an error.
1818
"""
19+
20+
21+
class NoConsoleRendererConfiguredError(Exception):
22+
"""
23+
A user asked for the current `structlog.dev.ConsoleRenderer` but none is
24+
configured.
25+
26+
.. versionadded:: 25.5.0
27+
"""
28+
29+
30+
class MultipleConsoleRenderersConfiguredError(Exception):
31+
"""
32+
A user asked for the current `structlog.dev.ConsoleRenderer` and more than one is configured.
33+
34+
.. versionadded:: 25.5.0
35+
"""

tests/test_dev.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
import pytest
1313

14+
import structlog
15+
1416
from structlog import dev
1517

1618

@@ -682,6 +684,18 @@ def test_does_not_modify_styles(self):
682684

683685
assert copy == styles
684686

687+
def test_exception_formatter_property(self, cr):
688+
"""
689+
The exception formatter can be set and retrieved without
690+
re-instantiating ConsoleRenderer.
691+
"""
692+
sentinel = object()
693+
694+
cr.exception_formatter = sentinel
695+
696+
assert sentinel is cr.exception_formatter
697+
assert sentinel is cr._exception_formatter
698+
685699

686700
class TestSetExcInfo:
687701
def test_wrong_name(self):
@@ -820,3 +834,40 @@ def test_no_style(self):
820834
assert "[critical]" == dev.LogLevelColumnFormatter(None, "foo")(
821835
"", "critical"
822836
)
837+
838+
839+
class TestGetActiveConsoleRenderer:
840+
def test_ok(self):
841+
"""
842+
If there's an active ConsoleRenderer, it's returned.
843+
"""
844+
assert (
845+
structlog.get_config()["processors"][-1]
846+
is dev.get_active_console_renderer()
847+
)
848+
849+
def test_no_console_renderer(self):
850+
"""
851+
If no ConsoleRenderer is configured, raise
852+
NoConsoleRendererConfiguredError.
853+
"""
854+
structlog.configure(processors=[])
855+
856+
with pytest.raises(
857+
structlog.exceptions.NoConsoleRendererConfiguredError
858+
):
859+
dev.get_active_console_renderer()
860+
861+
def test_multiple_console_renderers(self):
862+
"""
863+
If multiple ConsoleRenderers are configured, raise
864+
MultipleConsoleRenderersConfiguredError because it's most likely a bug.
865+
"""
866+
structlog.configure(
867+
processors=[dev.ConsoleRenderer(), dev.ConsoleRenderer()]
868+
)
869+
870+
with pytest.raises(
871+
structlog.exceptions.MultipleConsoleRenderersConfiguredError
872+
):
873+
dev.get_active_console_renderer()

tox.ini

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ commands = cog -rP README.md docs/index.md
8383

8484
[testenv:pre-commit]
8585
skip_install = true
86-
deps = pre-commit
87-
commands = pre-commit run --all-files
86+
deps = prek
87+
commands = prek run --all-files
8888

8989

9090
[testenv:mypy-pkg]

0 commit comments

Comments
 (0)