diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6db1260f..20be2316 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,7 @@ - Add requirement for ``TERM`` environment variable not to be ``"dumb"`` to enable colorization (`#1287 `_, thanks `@snosov1 `_). - Make ``logger.catch()`` usable as an asynchronous context manager (`#1084 `_). - Make ``logger.catch()`` compatible with asynchronous generators (`#1302 `_). +- Add ``record["template"]`` that includes the raw, unformatted message (`#1349 `_). `0.7.3`_ (2024-12-06) diff --git a/loguru/__init__.pyi b/loguru/__init__.pyi index b15ac82e..b84dffb6 100644 --- a/loguru/__init__.pyi +++ b/loguru/__init__.pyi @@ -105,6 +105,7 @@ class Record(TypedDict): module: str name: Optional[str] process: RecordProcess + template: str thread: RecordThread time: datetime diff --git a/loguru/_handler.py b/loguru/_handler.py index 81a3dca0..c1aa9442 100644 --- a/loguru/_handler.py +++ b/loguru/_handler.py @@ -280,6 +280,7 @@ def _serialize_record(text, record): "module": record["module"], "name": record["name"], "process": {"id": record["process"].id, "name": record["process"].name}, + "template": record["template"], "thread": {"id": record["thread"].id, "name": record["thread"].name}, "time": {"repr": record["time"], "timestamp": record["time"].timestamp()}, }, diff --git a/loguru/_logger.py b/loguru/_logger.py index 6c23d7b7..7942ff76 100644 --- a/loguru/_logger.py +++ b/loguru/_logger.py @@ -2122,6 +2122,7 @@ def _log(self, level, from_decorator, options, message, args, kwargs): "module": splitext(file_name)[0], "name": name, "process": RecordProcess(process.ident, process.name), + "template": str(message), "thread": RecordThread(thread.ident, thread.name), "time": current_datetime, } diff --git a/tests/test_formatting.py b/tests/test_formatting.py index 4f48a0bc..b8d10cdc 100644 --- a/tests/test_formatting.py +++ b/tests/test_formatting.py @@ -195,6 +195,31 @@ def format(record): assert output == "[123]\n" +def test_template_vs_message_with_formatting(writer): + logger.add(writer, format="{template} | {message}") + logger.info("Hello {name}", name="World") + + assert writer.read() == "Hello {name} | Hello World\n" + + +@pytest.mark.parametrize( + ("message", "args", "kwargs", "expected_template", "expected_message"), + [ + ("{} + {} = {}", [1, 2, 3], {}, "{} + {} = {}", "1 + 2 = 3"), + ("{a} + {b} = {c}", [], {"a": 1, "b": 2, "c": 3}, "{a} + {b} = {c}", "1 + 2 = 3"), + ("{0} + {two} = {1}", [1, 3], {"two": 2}, "{0} + {two} = {1}", "1 + 2 = 3"), + ("{:.2f}", [1], {}, "{:.2f}", "1.00"), + ], +) +def test_template_preserves_unformatted_message( + writer, message, args, kwargs, expected_template, expected_message +): + logger.add(writer, format="{template} | {message}", colorize=False) + logger.info(message, *args, **kwargs) + + assert writer.read() == "{} | {}\n".format(expected_template, expected_message) + + @pytest.mark.parametrize("colors", [True, False]) def test_missing_positional_field_during_formatting(writer, colors): logger.add(writer)