From b9ad41250d1b526eb867a00b389e8a8e7436856f Mon Sep 17 00:00:00 2001 From: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com> Date: Sat, 1 Aug 2026 21:11:09 -0700 Subject: [PATCH 1/3] fix(core): support unbraced $name fields in $ style formats BaseJsonFormatter.parse() matched only ${name} for StringTemplateStyle, so a format like "$asctime $message" produced no fields at all and the resulting log records were missing every requested attribute. Python's string.Template accepts both $name and ${name}. The regex now matches both forms and skips the $$ escape, and parse() picks whichever group matched. Closes #18 --- docs/changelog.md | 1 + src/pythonjsonlogger/core.py | 11 +++++++++-- tests/test_formatters.py | 16 ++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 00fb3b3..d18ca6d 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### 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) +- `$` style formats now support unbraced `$name` fields, not just `${name}`. [#18](https://github.com/nhairs/python-json-logger/issues/18) Thanks @gaoflow, @prateek-dagar diff --git a/src/pythonjsonlogger/core.py b/src/pythonjsonlogger/core.py index 8807aed..a201080 100644 --- a/src/pythonjsonlogger/core.py +++ b/src/pythonjsonlogger/core.py @@ -61,7 +61,9 @@ RESERVED_ATTRS.sort() -STYLE_STRING_TEMPLATE_REGEX = re.compile(r"\$\{(.+?)\}", re.IGNORECASE) # $ style +STYLE_STRING_TEMPLATE_REGEX = re.compile( + r"\$(?:\$|\{(?P.+?)\}|(?P[_a-z][_a-z0-9]*))", re.IGNORECASE +) # $ style STYLE_STRING_FORMAT_REGEX = re.compile(r"\{(.+?)\}", re.IGNORECASE) # { style STYLE_PERCENT_REGEX = re.compile(r"%\((.+?)\)", re.IGNORECASE) # % style @@ -302,7 +304,12 @@ def parse(self) -> list[str]: raise ValueError(f"Style {self._style!r} is not supported") if isinstance(self._style, logging.StringTemplateStyle): - formatter_style_pattern = STYLE_STRING_TEMPLATE_REGEX + # String templates support both ${name} and $name, and $$ is an escaped literal + return [ + match.group("braced") or match.group("named") + for match in STYLE_STRING_TEMPLATE_REGEX.finditer(self._fmt) + if match.group("braced") or match.group("named") + ] elif isinstance(self._style, logging.StrFormatStyle): formatter_style_pattern = STYLE_STRING_FORMAT_REGEX diff --git a/tests/test_formatters.py b/tests/test_formatters.py index 01e2cf5..bc234ec 100644 --- a/tests/test_formatters.py +++ b/tests/test_formatters.py @@ -167,6 +167,22 @@ def test_percentage_format(env: LoggingEnvironment, class_: type[BaseJsonFormatt return +@pytest.mark.parametrize("class_", ALL_FORMATTERS) +def test_string_template_format(env: LoggingEnvironment, class_: type[BaseJsonFormatter]): + # Note: string templates support both $name and ${name}, and $$ is an escaped literal $ + env.set_formatter( + class_("$$literal $levelname ${message} $filename ${lineno} $asctime", style="$") + ) + + msg = "testing logging format" + env.logger.info(msg) + log_json = env.load_json() + + assert log_json["message"] == msg + assert log_json.keys() == {"levelname", "message", "filename", "lineno", "asctime"} + return + + @pytest.mark.parametrize("class_", ALL_FORMATTERS) def test_comma_format(env: LoggingEnvironment, class_: type[BaseJsonFormatter]): # Note: we have double comma `,,` to test handling "empty" names From 81d368f24c4fc6dadaced6a7ae1d38217fc880eb Mon Sep 17 00:00:00 2001 From: Nicholas Hairs Date: Sat, 15 Aug 2026 20:59:02 +1000 Subject: [PATCH 2/3] Fix lint errors --- src/pythonjsonlogger/core.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/pythonjsonlogger/core.py b/src/pythonjsonlogger/core.py index a201080..c084027 100644 --- a/src/pythonjsonlogger/core.py +++ b/src/pythonjsonlogger/core.py @@ -311,18 +311,15 @@ def parse(self) -> list[str]: if match.group("braced") or match.group("named") ] - elif isinstance(self._style, logging.StrFormatStyle): - formatter_style_pattern = STYLE_STRING_FORMAT_REGEX + if isinstance(self._style, logging.StrFormatStyle): + return STYLE_STRING_FORMAT_REGEX.findall(self._fmt) - elif isinstance(self._style, logging.PercentStyle): + if isinstance(self._style, logging.PercentStyle): # PercentStyle is parent class of StringTemplateStyle and StrFormatStyle # so it must be checked last. - formatter_style_pattern = STYLE_PERCENT_REGEX + return STYLE_PERCENT_REGEX.findall(self._fmt) - else: - raise ValueError(f"Style {self._style!r} is not supported") - - return formatter_style_pattern.findall(self._fmt) + raise ValueError(f"Style {self._style!r} is not supported") def serialize_log_record(self, log_data: LogData) -> str: """Returns the final representation of the data to be logged From 75338210db15f487594e5a965fd4bbddd8b50cb4 Mon Sep 17 00:00:00 2001 From: Nicholas Hairs Date: Sat, 15 Aug 2026 21:05:51 +1000 Subject: [PATCH 3/3] Update docs "thanks" --- docs/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index d18ca6d..34de13a 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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) - `$` style formats now support unbraced `$name` fields, not just `${name}`. [#18](https://github.com/nhairs/python-json-logger/issues/18) -Thanks @gaoflow, @prateek-dagar +Thanks @gaoflow, @prateek-dagar, @Sanjays2402 ## [4.1.0](https://github.com/nhairs/python-json-logger/compare/v4.0.0...v4.1.0) - 2026-03-29