diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bd257e76..4c1adb3d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -28,6 +28,8 @@ Changes: `#290 `_ - Fix isolation in ``structlog.contextvars``. `#302 `_ +- The default configuration and loggers are pickleable again. + `#301 `_ ---- diff --git a/src/structlog/_log_levels.py b/src/structlog/_log_levels.py index 5b7dae18..58942b9b 100644 --- a/src/structlog/_log_levels.py +++ b/src/structlog/_log_levels.py @@ -94,6 +94,18 @@ def make_filtering_bound_logger(min_level: int) -> Type[FilteringBoundLogger]: values. .. versionadded:: 20.2.0 + .. versionchanged:: 21.1.0 The returned loggers are now pickleable. + """ + + return _LEVEL_TO_FILTERING_LOGGER[min_level] + + +def _make_filtering_bound_logger(min_level: int) -> Type[FilteringBoundLogger]: + """ + Create a new `FilteringBoundLogger` that only logs *min_level* or higher. + + The logger is optimized such that log levels below *min_level* only consist + of a ``return None``. """ def make_method(level: int) -> Callable[..., Any]: @@ -124,3 +136,21 @@ def meth(self: Any, event: str, **kw: Any) -> Any: (BoundLoggerBase,), meths, ) + + +# Pre-create all possible filters to make them pickleable. +BoundLoggerFilteringAtNotset = _make_filtering_bound_logger(NOTSET) +BoundLoggerFilteringAtDebug = _make_filtering_bound_logger(DEBUG) +BoundLoggerFilteringAtInfo = _make_filtering_bound_logger(INFO) +BoundLoggerFilteringAtWarning = _make_filtering_bound_logger(WARNING) +BoundLoggerFilteringAtError = _make_filtering_bound_logger(ERROR) +BoundLoggerFilteringAtCritical = _make_filtering_bound_logger(CRITICAL) + +_LEVEL_TO_FILTERING_LOGGER = { + CRITICAL: BoundLoggerFilteringAtCritical, + ERROR: BoundLoggerFilteringAtError, + WARNING: BoundLoggerFilteringAtWarning, + INFO: BoundLoggerFilteringAtInfo, + DEBUG: BoundLoggerFilteringAtDebug, + NOTSET: BoundLoggerFilteringAtNotset, +} diff --git a/tests/test_log_levels.py b/tests/test_log_levels.py index edc288d9..1253475a 100644 --- a/tests/test_log_levels.py +++ b/tests/test_log_levels.py @@ -3,10 +3,12 @@ # repository for complete details. import logging +import pickle import pytest from structlog import make_filtering_bound_logger +from structlog._log_levels import _LEVEL_TO_NAME from structlog.testing import CapturingLogger @@ -53,3 +55,12 @@ def test_exception_passed(self, bl, cl): bl.exception("boom", exc_info=42) assert [("error", (), {"event": "boom", "exc_info": 42})] + + @pytest.mark.parametrize("level", tuple(_LEVEL_TO_NAME.keys())) + def test_pickle(self, level): + """ + FilteringBoundLogger are pickleable. + """ + bl = make_filtering_bound_logger(level) + + assert bl == pickle.loads(pickle.dumps(bl))