diff --git a/docs/changelog.md b/docs/changelog.md index 466b0f7..4ea8000 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [4.2.0](https://github.com/nhairs/python-json-logger/compare/v4.1.0...v4.2.0) - UNRELEASED + +### Fixed +- Logging a `dict` no longer modifies it. `exc_info` and `stack_info` were previously added to the caller's `dict`. [#66](https://github.com/nhairs/python-json-logger/pull/66) + +Thanks @gaoflow + ## [4.1.0](https://github.com/nhairs/python-json-logger/compare/v4.0.0...v4.1.0) - 2026-03-29 ### Added diff --git a/docs/quickstart.md b/docs/quickstart.md index 853b435..56d2de8 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -79,6 +79,9 @@ logger.info({ !!! warning Be aware that if you log using a `dict`, other formatters may not be able to handle it. +!!! note + Your `dict` is not modified when the formatter adds fields such as `exc_info`. + You can also add additional message fields using the `extra` argument. ```python diff --git a/pyproject.toml b/pyproject.toml index 5a3e459..cb64e3b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "python-json-logger" -version = "4.1.0" +version = "4.2.0.dev1" description = "JSON Log Formatter for the Python Logging Package" authors = [ {name = "Zakaria Zajac", email = "zak@madzak.com"}, diff --git a/src/pythonjsonlogger/core.py b/src/pythonjsonlogger/core.py index d78873a..7217afb 100644 --- a/src/pythonjsonlogger/core.py +++ b/src/pythonjsonlogger/core.py @@ -236,14 +236,20 @@ def __init__( def format(self, record: logging.LogRecord) -> str: """Formats a log record and serializes to json + When `record.msg` is a `dict`, adding `exc_info` and `stack_info` does not + modify the caller's dict. + Args: record: the record to format + + *Changed in 4.2.0*: a `dict` `record.msg` is copied instead of modified + in place. """ message_dict: dict[str, Any] = {} # TODO: logging.LogRecord.msg and logging.LogRecord.message in typeshed # are always type of str. We shouldn't need to override that. if isinstance(record.msg, dict): - message_dict = record.msg + message_dict = record.msg.copy() record.message = "" else: record.message = record.getMessage() diff --git a/tests/test_formatters.py b/tests/test_formatters.py index 0ad4846..5d27646 100644 --- a/tests/test_formatters.py +++ b/tests/test_formatters.py @@ -393,6 +393,23 @@ def test_log_dict_defaults(env: LoggingEnvironment, class_: type[BaseJsonFormatt return +@pytest.mark.parametrize("class_", ALL_FORMATTERS) +def test_log_dict_not_modified(env: LoggingEnvironment, class_: type[BaseJsonFormatter]): + env.set_formatter(class_()) + + msg = {"text": "testing logging", "nested": {"more": "data"}} + try: + raise ValueError("test") + except ValueError: + env.logger.exception(msg, stack_info=True) + log_json = env.load_json() + + assert log_json["exc_info"] + assert log_json["stack_info"] + assert msg == {"text": "testing logging", "nested": {"more": "data"}} + return + + @pytest.mark.parametrize("class_", ALL_FORMATTERS) def test_log_extra(env: LoggingEnvironment, class_: type[BaseJsonFormatter]): env.set_formatter(class_())