Skip to content

Commit 651c3b2

Browse files
Made cache.key span data field a list (#3110)
* Made cache.key span data field a list --------- Co-authored-by: Ivana Kellyerova <[email protected]>
1 parent bb918fb commit 651c3b2

File tree

6 files changed

+150
-56
lines changed

6 files changed

+150
-56
lines changed

Diff for: sentry_sdk/integrations/django/caching.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import functools
22
from typing import TYPE_CHECKING
3-
from sentry_sdk.integrations.redis.utils import _get_safe_key
3+
from sentry_sdk.integrations.redis.utils import _get_safe_key, _key_as_string
44
from urllib3.util import parse_url as urlparse
55

66
from django import VERSION as DJANGO_VERSION
@@ -30,7 +30,7 @@
3030

3131
def _get_span_description(method_name, args, kwargs):
3232
# type: (str, tuple[Any], dict[str, Any]) -> str
33-
return _get_safe_key(method_name, args, kwargs)
33+
return _key_as_string(_get_safe_key(method_name, args, kwargs))
3434

3535

3636
def _patch_cache_method(cache, method_name, address, port):
@@ -61,7 +61,7 @@ def _instrument_call(
6161
span.set_data(SPANDATA.NETWORK_PEER_PORT, port)
6262

6363
key = _get_safe_key(method_name, args, kwargs)
64-
if key != "":
64+
if key is not None:
6565
span.set_data(SPANDATA.CACHE_KEY, key)
6666

6767
item_size = None

Diff for: sentry_sdk/integrations/redis/modules/caches.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from sentry_sdk._types import TYPE_CHECKING
66
from sentry_sdk.consts import OP, SPANDATA
7-
from sentry_sdk.integrations.redis.utils import _get_safe_key
7+
from sentry_sdk.integrations.redis.utils import _get_safe_key, _key_as_string
88
from sentry_sdk.utils import capture_internal_exceptions
99

1010
GET_COMMANDS = ("get", "mget")
@@ -30,10 +30,11 @@ def _get_op(name):
3030
def _compile_cache_span_properties(redis_command, args, kwargs, integration):
3131
# type: (str, tuple[Any, ...], dict[str, Any], RedisIntegration) -> dict[str, Any]
3232
key = _get_safe_key(redis_command, args, kwargs)
33+
key_as_string = _key_as_string(key)
3334

3435
is_cache_key = False
3536
for prefix in integration.cache_prefixes:
36-
if key.startswith(prefix):
37+
if key_as_string.startswith(prefix):
3738
is_cache_key = True
3839
break
3940

@@ -47,6 +48,7 @@ def _compile_cache_span_properties(redis_command, args, kwargs, integration):
4748
redis_command, args, kwargs, integration
4849
),
4950
"key": key,
51+
"key_as_string": key_as_string,
5052
"redis_command": redis_command.lower(),
5153
"is_cache_key": is_cache_key,
5254
"value": value,
@@ -57,7 +59,7 @@ def _compile_cache_span_properties(redis_command, args, kwargs, integration):
5759

5860
def _get_cache_span_description(redis_command, args, kwargs, integration):
5961
# type: (str, tuple[Any, ...], dict[str, Any], RedisIntegration) -> str
60-
description = _get_safe_key(redis_command, args, kwargs)
62+
description = _key_as_string(_get_safe_key(redis_command, args, kwargs))
6163

6264
data_should_be_truncated = (
6365
integration.max_data_size and len(description) > integration.max_data_size

Diff for: sentry_sdk/integrations/redis/utils.py

+43-21
Original file line numberDiff line numberDiff line change
@@ -44,38 +44,60 @@ def _get_safe_command(name, args):
4444
return command
4545

4646

47+
def _safe_decode(key):
48+
# type: (Any) -> str
49+
if isinstance(key, bytes):
50+
try:
51+
return key.decode()
52+
except UnicodeDecodeError:
53+
return ""
54+
55+
return key
56+
57+
58+
def _key_as_string(key):
59+
# type: (Any) -> str
60+
if isinstance(key, (dict, list, tuple)):
61+
key = ", ".join(_safe_decode(x) for x in key)
62+
elif isinstance(key, bytes):
63+
key = _safe_decode(key)
64+
elif key is None:
65+
key = ""
66+
else:
67+
key = str(key)
68+
69+
return key
70+
71+
4772
def _get_safe_key(method_name, args, kwargs):
48-
# type: (str, Optional[tuple[Any, ...]], Optional[dict[str, Any]]) -> str
73+
# type: (str, Optional[tuple[Any, ...]], Optional[dict[str, Any]]) -> Optional[tuple[str, ...]]
4974
"""
50-
Gets the keys (or keys) from the given method_name.
75+
Gets the key (or keys) from the given method_name.
5176
The method_name could be a redis command or a django caching command
5277
"""
53-
key = ""
78+
key = None
79+
5480
if args is not None and method_name.lower() in _MULTI_KEY_COMMANDS:
5581
# for example redis "mget"
56-
key = ", ".join(x.decode() if isinstance(x, bytes) else x for x in args)
82+
key = tuple(args)
5783

5884
elif args is not None and len(args) >= 1:
5985
# for example django "set_many/get_many" or redis "get"
60-
key = args[0].decode() if isinstance(args[0], bytes) else args[0]
86+
if isinstance(args[0], (dict, list, tuple)):
87+
key = tuple(args[0])
88+
else:
89+
key = (args[0],)
6190

6291
elif kwargs is not None and "key" in kwargs:
63-
# this is a legacy case for older versions of django (I guess)
64-
key = (
65-
kwargs["key"].decode()
66-
if isinstance(kwargs["key"], bytes)
67-
else kwargs["key"]
68-
)
69-
70-
if isinstance(key, dict):
71-
# Django caching set_many() has a dictionary {"key": "data", "key2": "data2"}
72-
# as argument. In this case only return the keys of the dictionary (to not leak data)
73-
key = ", ".join(x.decode() if isinstance(x, bytes) else x for x in key.keys())
74-
75-
if isinstance(key, list):
76-
key = ", ".join(x.decode() if isinstance(x, bytes) else x for x in key)
77-
78-
return str(key)
92+
# this is a legacy case for older versions of Django
93+
if isinstance(kwargs["key"], (list, tuple)):
94+
if len(kwargs["key"]) > 0:
95+
key = tuple(kwargs["key"])
96+
else:
97+
if kwargs["key"] is not None:
98+
key = (kwargs["key"],)
99+
100+
return key
79101

80102

81103
def _parse_rediscluster_command(command):

Diff for: tests/integrations/django/test_cache_module.py

+13-12
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import pytest
21
import os
32
import random
3+
import uuid
44

5+
import pytest
56
from django import VERSION as DJANGO_VERSION
6-
77
from werkzeug.test import Client
88

99
try:
@@ -198,7 +198,7 @@ def test_cache_spans_middleware(
198198
"views.decorators.cache.cache_header."
199199
)
200200
assert first_event["spans"][0]["data"]["network.peer.address"] is not None
201-
assert first_event["spans"][0]["data"]["cache.key"].startswith(
201+
assert first_event["spans"][0]["data"]["cache.key"][0].startswith(
202202
"views.decorators.cache.cache_header."
203203
)
204204
assert not first_event["spans"][0]["data"]["cache.hit"]
@@ -209,7 +209,7 @@ def test_cache_spans_middleware(
209209
"views.decorators.cache.cache_header."
210210
)
211211
assert first_event["spans"][1]["data"]["network.peer.address"] is not None
212-
assert first_event["spans"][1]["data"]["cache.key"].startswith(
212+
assert first_event["spans"][1]["data"]["cache.key"][0].startswith(
213213
"views.decorators.cache.cache_header."
214214
)
215215
assert "cache.hit" not in first_event["spans"][1]["data"]
@@ -220,7 +220,7 @@ def test_cache_spans_middleware(
220220
"views.decorators.cache.cache_header."
221221
)
222222
assert second_event["spans"][0]["data"]["network.peer.address"] is not None
223-
assert second_event["spans"][0]["data"]["cache.key"].startswith(
223+
assert second_event["spans"][0]["data"]["cache.key"][0].startswith(
224224
"views.decorators.cache.cache_header."
225225
)
226226
assert not second_event["spans"][0]["data"]["cache.hit"]
@@ -231,7 +231,7 @@ def test_cache_spans_middleware(
231231
"views.decorators.cache.cache_page."
232232
)
233233
assert second_event["spans"][1]["data"]["network.peer.address"] is not None
234-
assert second_event["spans"][1]["data"]["cache.key"].startswith(
234+
assert second_event["spans"][1]["data"]["cache.key"][0].startswith(
235235
"views.decorators.cache.cache_page."
236236
)
237237
assert second_event["spans"][1]["data"]["cache.hit"]
@@ -264,7 +264,7 @@ def test_cache_spans_decorator(sentry_init, client, capture_events, use_django_c
264264
"views.decorators.cache.cache_header."
265265
)
266266
assert first_event["spans"][0]["data"]["network.peer.address"] is not None
267-
assert first_event["spans"][0]["data"]["cache.key"].startswith(
267+
assert first_event["spans"][0]["data"]["cache.key"][0].startswith(
268268
"views.decorators.cache.cache_header."
269269
)
270270
assert not first_event["spans"][0]["data"]["cache.hit"]
@@ -275,7 +275,7 @@ def test_cache_spans_decorator(sentry_init, client, capture_events, use_django_c
275275
"views.decorators.cache.cache_header."
276276
)
277277
assert first_event["spans"][1]["data"]["network.peer.address"] is not None
278-
assert first_event["spans"][1]["data"]["cache.key"].startswith(
278+
assert first_event["spans"][1]["data"]["cache.key"][0].startswith(
279279
"views.decorators.cache.cache_header."
280280
)
281281
assert "cache.hit" not in first_event["spans"][1]["data"]
@@ -286,7 +286,7 @@ def test_cache_spans_decorator(sentry_init, client, capture_events, use_django_c
286286
"views.decorators.cache.cache_page."
287287
)
288288
assert second_event["spans"][1]["data"]["network.peer.address"] is not None
289-
assert second_event["spans"][1]["data"]["cache.key"].startswith(
289+
assert second_event["spans"][1]["data"]["cache.key"][0].startswith(
290290
"views.decorators.cache.cache_page."
291291
)
292292
assert second_event["spans"][1]["data"]["cache.hit"]
@@ -322,7 +322,7 @@ def test_cache_spans_templatetag(
322322
"template.cache.some_identifier."
323323
)
324324
assert first_event["spans"][0]["data"]["network.peer.address"] is not None
325-
assert first_event["spans"][0]["data"]["cache.key"].startswith(
325+
assert first_event["spans"][0]["data"]["cache.key"][0].startswith(
326326
"template.cache.some_identifier."
327327
)
328328
assert not first_event["spans"][0]["data"]["cache.hit"]
@@ -333,7 +333,7 @@ def test_cache_spans_templatetag(
333333
"template.cache.some_identifier."
334334
)
335335
assert first_event["spans"][1]["data"]["network.peer.address"] is not None
336-
assert first_event["spans"][1]["data"]["cache.key"].startswith(
336+
assert first_event["spans"][1]["data"]["cache.key"][0].startswith(
337337
"template.cache.some_identifier."
338338
)
339339
assert "cache.hit" not in first_event["spans"][1]["data"]
@@ -344,7 +344,7 @@ def test_cache_spans_templatetag(
344344
"template.cache.some_identifier."
345345
)
346346
assert second_event["spans"][0]["data"]["network.peer.address"] is not None
347-
assert second_event["spans"][0]["data"]["cache.key"].startswith(
347+
assert second_event["spans"][0]["data"]["cache.key"][0].startswith(
348348
"template.cache.some_identifier."
349349
)
350350
assert second_event["spans"][0]["data"]["cache.hit"]
@@ -358,6 +358,7 @@ def test_cache_spans_templatetag(
358358
("get", None, None, ""),
359359
("get", [], {}, ""),
360360
("get", ["bla", "blub", "foo"], {}, "bla"),
361+
("get", [uuid.uuid4().bytes], {}, ""),
361362
(
362363
"get_many",
363364
[["bla1", "bla2", "bla3"], "blub", "foo"],

Diff for: tests/integrations/redis/test_redis_cache_module.py

+77-14
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
import uuid
2+
13
import pytest
24

35
import fakeredis
46
from fakeredis import FakeStrictRedis
57

68
from sentry_sdk.integrations.redis import RedisIntegration
7-
from sentry_sdk.integrations.redis.utils import _get_safe_key
9+
from sentry_sdk.integrations.redis.utils import _get_safe_key, _key_as_string
810
from sentry_sdk.utils import parse_version
911
import sentry_sdk
1012

@@ -137,7 +139,9 @@ def test_cache_data(sentry_init, capture_events):
137139

138140
assert spans[0]["op"] == "cache.get"
139141
assert spans[0]["description"] == "mycachekey"
140-
assert spans[0]["data"]["cache.key"] == "mycachekey"
142+
assert spans[0]["data"]["cache.key"] == [
143+
"mycachekey",
144+
]
141145
assert spans[0]["data"]["cache.hit"] == False # noqa: E712
142146
assert "cache.item_size" not in spans[0]["data"]
143147
# very old fakeredis can not handle port and/or host.
@@ -155,7 +159,9 @@ def test_cache_data(sentry_init, capture_events):
155159

156160
assert spans[2]["op"] == "cache.put"
157161
assert spans[2]["description"] == "mycachekey"
158-
assert spans[2]["data"]["cache.key"] == "mycachekey"
162+
assert spans[2]["data"]["cache.key"] == [
163+
"mycachekey",
164+
]
159165
assert "cache.hit" not in spans[1]["data"]
160166
assert spans[2]["data"]["cache.item_size"] == 18
161167
# very old fakeredis can not handle port.
@@ -173,7 +179,9 @@ def test_cache_data(sentry_init, capture_events):
173179

174180
assert spans[4]["op"] == "cache.get"
175181
assert spans[4]["description"] == "mycachekey"
176-
assert spans[4]["data"]["cache.key"] == "mycachekey"
182+
assert spans[4]["data"]["cache.key"] == [
183+
"mycachekey",
184+
]
177185
assert spans[4]["data"]["cache.hit"] == True # noqa: E712
178186
assert spans[4]["data"]["cache.item_size"] == 18
179187
# very old fakeredis can not handle port.
@@ -193,17 +201,72 @@ def test_cache_data(sentry_init, capture_events):
193201
@pytest.mark.parametrize(
194202
"method_name,args,kwargs,expected_key",
195203
[
196-
(None, None, None, ""),
197-
("", None, None, ""),
198-
("set", ["bla", "valuebla"], None, "bla"),
199-
("setex", ["bla", 10, "valuebla"], None, "bla"),
200-
("get", ["bla"], None, "bla"),
201-
("mget", ["bla", "blub", "foo"], None, "bla, blub, foo"),
202-
("set", [b"bla", "valuebla"], None, "bla"),
203-
("setex", [b"bla", 10, "valuebla"], None, "bla"),
204-
("get", [b"bla"], None, "bla"),
205-
("mget", [b"bla", "blub", "foo"], None, "bla, blub, foo"),
204+
(None, None, None, None),
205+
("", None, None, None),
206+
("set", ["bla", "valuebla"], None, ("bla",)),
207+
("setex", ["bla", 10, "valuebla"], None, ("bla",)),
208+
("get", ["bla"], None, ("bla",)),
209+
("mget", ["bla", "blub", "foo"], None, ("bla", "blub", "foo")),
210+
("set", [b"bla", "valuebla"], None, (b"bla",)),
211+
("setex", [b"bla", 10, "valuebla"], None, (b"bla",)),
212+
("get", [b"bla"], None, (b"bla",)),
213+
("mget", [b"bla", "blub", "foo"], None, (b"bla", "blub", "foo")),
214+
("not-important", None, {"something": "bla"}, None),
215+
("not-important", None, {"key": None}, None),
216+
("not-important", None, {"key": "bla"}, ("bla",)),
217+
("not-important", None, {"key": b"bla"}, (b"bla",)),
218+
("not-important", None, {"key": []}, None),
219+
(
220+
"not-important",
221+
None,
222+
{
223+
"key": [
224+
"bla",
225+
]
226+
},
227+
("bla",),
228+
),
229+
(
230+
"not-important",
231+
None,
232+
{"key": [b"bla", "blub", "foo"]},
233+
(b"bla", "blub", "foo"),
234+
),
235+
(
236+
"not-important",
237+
None,
238+
{"key": b"\x00c\x0f\xeaC\xe1L\x1c\xbff\xcb\xcc\xc1\xed\xc6\t"},
239+
(b"\x00c\x0f\xeaC\xe1L\x1c\xbff\xcb\xcc\xc1\xed\xc6\t",),
240+
),
241+
(
242+
"get",
243+
[b"\x00c\x0f\xeaC\xe1L\x1c\xbff\xcb\xcc\xc1\xed\xc6\t"],
244+
None,
245+
(b"\x00c\x0f\xeaC\xe1L\x1c\xbff\xcb\xcc\xc1\xed\xc6\t",),
246+
),
206247
],
207248
)
208249
def test_get_safe_key(method_name, args, kwargs, expected_key):
209250
assert _get_safe_key(method_name, args, kwargs) == expected_key
251+
252+
253+
@pytest.mark.parametrize(
254+
"key,expected_key",
255+
[
256+
(None, ""),
257+
(("bla",), "bla"),
258+
(("bla", "blub", "foo"), "bla, blub, foo"),
259+
((b"bla",), "bla"),
260+
((b"bla", "blub", "foo"), "bla, blub, foo"),
261+
(
262+
[
263+
"bla",
264+
],
265+
"bla",
266+
),
267+
(["bla", "blub", "foo"], "bla, blub, foo"),
268+
([uuid.uuid4().bytes], ""),
269+
],
270+
)
271+
def test_key_as_string(key, expected_key):
272+
assert _key_as_string(key) == expected_key

0 commit comments

Comments
 (0)