Skip to content

Commit af88eb3

Browse files
committed
Make logger template format safer to missing kwargs
1 parent cf989a8 commit af88eb3

File tree

3 files changed

+67
-2
lines changed

3 files changed

+67
-2
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ relay
2828
pip-wheel-metadata
2929
.mypy_cache
3030
.vscode/
31+
.claude/
3132

3233
# for running AWS Lambda tests using AWS SAM
3334
sam.template.yaml

sentry_sdk/logger.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# NOTE: this is the logger sentry exposes to users, not some generic logger.
22
import functools
33
import time
4-
from typing import Any
4+
from typing import Any, Dict
55

66
from sentry_sdk import get_client
77
from sentry_sdk.utils import safe_repr
@@ -18,10 +18,19 @@
1818
]
1919

2020

21+
class _dict_default_key(dict): # type: ignore[type-arg]
22+
"""dict that returns the key if missing."""
23+
24+
def __missing__(self, key):
25+
# type: (str) -> str
26+
return "{" + key + "}"
27+
28+
2129
def _capture_log(severity_text, severity_number, template, **kwargs):
2230
# type: (str, int, str, **Any) -> None
2331
client = get_client()
2432

33+
body = template
2534
attrs = {} # type: dict[str, str | bool | float | int]
2635
if "attributes" in kwargs:
2736
attrs.update(kwargs.pop("attributes"))
@@ -31,6 +40,12 @@ def _capture_log(severity_text, severity_number, template, **kwargs):
3140
# only attach template if there are parameters
3241
attrs["sentry.message.template"] = template
3342

43+
# try safely formatting the template
44+
try:
45+
body = template.format_map(_dict_default_key(kwargs))
46+
except (KeyError, ValueError):
47+
pass
48+
3449
attrs = {
3550
k: (
3651
v
@@ -51,7 +66,7 @@ def _capture_log(severity_text, severity_number, template, **kwargs):
5166
"severity_text": severity_text,
5267
"severity_number": severity_number,
5368
"attributes": attrs,
54-
"body": template.format(**kwargs),
69+
"body": body,
5570
"time_unix_nano": time.time_ns(),
5671
"trace_id": None,
5772
},

tests/test_logs.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,3 +397,52 @@ def test_auto_flush_logs_after_5s(sentry_init, capture_envelopes):
397397
return
398398

399399
raise AssertionError("1 logs was never flushed after 10 seconds")
400+
401+
402+
@minimum_python_37
403+
@pytest.mark.parametrize(
404+
"message,expected_body,params",
405+
[
406+
("any text with {braces} in it", "any text with {braces} in it", None),
407+
(
408+
'JSON data: {"key": "value", "number": 42}',
409+
'JSON data: {"key": "value", "number": 42}',
410+
None,
411+
),
412+
("Multiple {braces} {in} {message}", "Multiple {braces} {in} {message}", None),
413+
("Nested {{braces}}", "Nested {{braces}}", None),
414+
("Empty braces: {}", "Empty braces: {}", None),
415+
("Braces with params: {user}", "Braces with params: alice", {"user": "alice"}),
416+
(
417+
"Braces with partial params: {user1} {user2}",
418+
"Braces with partial params: alice {user2}",
419+
{"user1": "alice"},
420+
),
421+
],
422+
)
423+
def test_logs_with_literal_braces(
424+
sentry_init, capture_envelopes, message, expected_body, params
425+
):
426+
"""
427+
Test that log messages with literal braces (like JSON) work without crashing.
428+
This is a regression test for issue #4975.
429+
"""
430+
sentry_init(enable_logs=True)
431+
envelopes = capture_envelopes()
432+
433+
if params:
434+
sentry_sdk.logger.info(message, **params)
435+
else:
436+
sentry_sdk.logger.info(message)
437+
438+
get_client().flush()
439+
logs = envelopes_to_logs(envelopes)
440+
441+
assert len(logs) == 1
442+
assert logs[0]["body"] == expected_body
443+
444+
# Verify template is only stored when there are parameters
445+
if params:
446+
assert logs[0]["attributes"]["sentry.message.template"] == message
447+
else:
448+
assert "sentry.message.template" not in logs[0]["attributes"]

0 commit comments

Comments
 (0)