Skip to content

Commit c48cdca

Browse files
committed
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
1 parent 2557bbc commit c48cdca

3 files changed

Lines changed: 26 additions & 2 deletions

File tree

docs/changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
### Fixed
1010
- 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)
11+
- `$` style formats now support unbraced `$name` fields, not just `${name}`. [#18](https://github.com/nhairs/python-json-logger/issues/18)
1112

1213
Thanks @gaoflow
1314

src/pythonjsonlogger/core.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@
6161
RESERVED_ATTRS.sort()
6262

6363

64-
STYLE_STRING_TEMPLATE_REGEX = re.compile(r"\$\{(.+?)\}", re.IGNORECASE) # $ style
64+
STYLE_STRING_TEMPLATE_REGEX = re.compile(
65+
r"\$(?:\$|\{(?P<braced>.+?)\}|(?P<named>[_a-z][_a-z0-9]*))", re.IGNORECASE
66+
) # $ style
6567
STYLE_STRING_FORMAT_REGEX = re.compile(r"\{(.+?)\}", re.IGNORECASE) # { style
6668
STYLE_PERCENT_REGEX = re.compile(r"%\((.+?)\)", re.IGNORECASE) # % style
6769

@@ -302,7 +304,12 @@ def parse(self) -> list[str]:
302304
raise ValueError(f"Style {self._style!r} is not supported")
303305

304306
if isinstance(self._style, logging.StringTemplateStyle):
305-
formatter_style_pattern = STYLE_STRING_TEMPLATE_REGEX
307+
# String templates support both ${name} and $name, and $$ is an escaped literal
308+
return [
309+
match.group("braced") or match.group("named")
310+
for match in STYLE_STRING_TEMPLATE_REGEX.finditer(self._fmt)
311+
if match.group("braced") or match.group("named")
312+
]
306313

307314
elif isinstance(self._style, logging.StrFormatStyle):
308315
formatter_style_pattern = STYLE_STRING_FORMAT_REGEX

tests/test_formatters.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,22 @@ def test_percentage_format(env: LoggingEnvironment, class_: type[BaseJsonFormatt
167167
return
168168

169169

170+
@pytest.mark.parametrize("class_", ALL_FORMATTERS)
171+
def test_string_template_format(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
172+
# Note: string templates support both $name and ${name}, and $$ is an escaped literal $
173+
env.set_formatter(
174+
class_("$$literal $levelname ${message} $filename ${lineno} $asctime", style="$")
175+
)
176+
177+
msg = "testing logging format"
178+
env.logger.info(msg)
179+
log_json = env.load_json()
180+
181+
assert log_json["message"] == msg
182+
assert log_json.keys() == {"levelname", "message", "filename", "lineno", "asctime"}
183+
return
184+
185+
170186
@pytest.mark.parametrize("class_", ALL_FORMATTERS)
171187
def test_comma_format(env: LoggingEnvironment, class_: type[BaseJsonFormatter]):
172188
# Note: we have double comma `,,` to test handling "empty" names

0 commit comments

Comments
 (0)