diff --git a/CHANGELOG.md b/CHANGELOG.md index 6def409c..1884c3d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,11 @@ You can find our backwards-compatibility policy [here](https://github.com/hynek/ ## [Unreleased](https://github.com/hynek/structlog/compare/24.2.0...HEAD) +### Changed + +- `structlog.testing.capture_logs()` now maps the `exception` log level to `error` (as it's elsewhere). + [#628](https://github.com/hynek/structlog/pull/628) + ## [24.2.0](https://github.com/hynek/structlog/compare/24.1.0...24.2.0) - 2024-05-27 diff --git a/src/structlog/_log_levels.py b/src/structlog/_log_levels.py index df1e81ab..3b759f41 100644 --- a/src/structlog/_log_levels.py +++ b/src/structlog/_log_levels.py @@ -46,6 +46,18 @@ _NAME_TO_LEVEL = NAME_TO_LEVEL +def map_method_name(method_name: str) -> str: + # warn is just a deprecated alias in the stdlib. + if method_name == "warn": + return "warning" + + # Calling exception("") is the same as error("", exc_info=True) + if method_name == "exception": + return "error" + + return method_name + + def add_log_level( logger: logging.Logger, method_name: str, event_dict: EventDict ) -> EventDict: @@ -63,13 +75,7 @@ def add_log_level( .. versionchanged:: 24.1.0 Added mapping from "exception" to "error" """ - if method_name == "warn": - # The stdlib has an alias - method_name = "warning" - elif method_name == "exception": - # exception("") method is the same as error("", exc_info=True) - method_name = "error" - event_dict["level"] = method_name + event_dict["level"] = map_method_name(method_name) return event_dict diff --git a/src/structlog/testing.py b/src/structlog/testing.py index ba0c96f9..35ad87dd 100644 --- a/src/structlog/testing.py +++ b/src/structlog/testing.py @@ -17,6 +17,7 @@ from typing import Any, Generator, NamedTuple, NoReturn from ._config import configure, get_config +from ._log_levels import map_method_name from .exceptions import DropEvent from .typing import EventDict, WrappedLogger @@ -41,6 +42,10 @@ class LogCapture: :ivar List[structlog.typing.EventDict] entries: The captured log entries. .. versionadded:: 20.1.0 + + .. versionchanged:: 24.3.0 + Added mapping from "exception" to "error" + Added mapping from "warn" to "warning" """ entries: list[EventDict] @@ -51,7 +56,7 @@ def __init__(self) -> None: def __call__( self, _: WrappedLogger, method_name: str, event_dict: EventDict ) -> NoReturn: - event_dict["log_level"] = method_name + event_dict["log_level"] = map_method_name(method_name) self.entries.append(event_dict) raise DropEvent diff --git a/tests/test_testing.py b/tests/test_testing.py index 1283ddee..78d6246b 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -5,6 +5,8 @@ import pytest +import structlog + from structlog import get_config, get_logger, testing from structlog.testing import ( CapturedCall, @@ -85,6 +87,35 @@ def test_captures_bound_logers(self): } ] + def test_captures_log_level_mapping(self): + """ + exceptions and warn log levels are mapped like in regular loggers. + """ + structlog.configure( + processors=[ + structlog.stdlib.ProcessorFormatter.wrap_for_formatter, + ], + logger_factory=structlog.stdlib.LoggerFactory(), + wrapper_class=structlog.stdlib.BoundLogger, + ) + with testing.capture_logs() as logs: + get_logger().exception("hello", answer=42) + get_logger().warn("again", answer=23) + + assert [ + { + "event": "hello", + "answer": 42, + "exc_info": True, + "log_level": "error", + }, + { + "answer": 23, + "event": "again", + "log_level": "warning", + }, + ] == logs + class TestReturnLogger: # @pytest.mark.parametrize("method", stdlib_log_methods)