Skip to content

Commit 3224ef8

Browse files
committed
feat(redis): Respect data_collection.database_query_data option
Gate inclusion of non-key Redis command arguments in span descriptions and pipeline command data behind the data_collection.database_query_data option, which takes precedence over send_default_pii when set. When the option is disabled, non-key arguments are omitted from the command description; when unset, it defaults to enabled. Add test coverage for the enabled/disabled/default cases, precedence over send_default_pii, and the pipeline path. Refs PY-2587 Refs #6747
1 parent 4e9e97c commit 3224ef8

2 files changed

Lines changed: 184 additions & 2 deletions

File tree

sentry_sdk/integrations/redis/utils.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from typing import TYPE_CHECKING
22

3+
import sentry_sdk
34
from sentry_sdk.consts import SPANDATA
45
from sentry_sdk.integrations.redis.consts import (
56
_COMMANDS_INCLUDING_SENSITIVE_DATA,
@@ -11,7 +12,7 @@
1112
from sentry_sdk.scope import should_send_default_pii
1213
from sentry_sdk.traces import StreamedSpan
1314
from sentry_sdk.tracing import Span
14-
from sentry_sdk.utils import SENSITIVE_DATA_SUBSTITUTE
15+
from sentry_sdk.utils import SENSITIVE_DATA_SUBSTITUTE, has_data_collection_enabled
1516

1617
if TYPE_CHECKING:
1718
from typing import Any, Optional, Sequence, Union
@@ -22,6 +23,7 @@ def _get_safe_command(name: str, args: "Sequence[Any]") -> str:
2223

2324
name_low = name.lower()
2425
send_default_pii = should_send_default_pii()
26+
client_options = sentry_sdk.get_client().options
2527

2628
for i, arg in enumerate(args):
2729
if i > _MAX_NUM_ARGS:
@@ -35,7 +37,10 @@ def _get_safe_command(name: str, args: "Sequence[Any]") -> str:
3537
if arg_is_the_key:
3638
command_parts.append(repr(arg))
3739
else:
38-
if send_default_pii:
40+
if has_data_collection_enabled(client_options):
41+
if client_options["data_collection"]["database_query_data"]:
42+
command_parts.append(repr(arg))
43+
elif send_default_pii:
3944
command_parts.append(repr(arg))
4045
else:
4146
command_parts.append(SENSITIVE_DATA_SUBSTITUTE)

tests/integrations/redis/test_redis.py

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,72 @@ def test_redis_pipeline(
112112
}
113113

114114

115+
@pytest.mark.parametrize("span_streaming", [True, False])
116+
@pytest.mark.parametrize(
117+
"data_collection, expected_first_ten",
118+
[
119+
(
120+
{"database_query_data": False},
121+
["GET 'foo'", "SET 'bar'", "SET 'baz'"],
122+
),
123+
(
124+
{"database_query_data": True},
125+
["GET 'foo'", "SET 'bar' 1", "SET 'baz' 2"],
126+
),
127+
],
128+
)
129+
def test_redis_pipeline_data_collection(
130+
sentry_init,
131+
capture_events,
132+
capture_items,
133+
data_collection,
134+
expected_first_ten,
135+
span_streaming,
136+
):
137+
sentry_init(
138+
integrations=[RedisIntegration()],
139+
traces_sample_rate=1.0,
140+
trace_lifecycle="stream" if span_streaming else "static",
141+
_experiments={"data_collection": data_collection},
142+
)
143+
144+
connection = FakeStrictRedis()
145+
146+
if span_streaming:
147+
items = capture_items("span")
148+
with sentry_sdk.traces.start_span(name="custom parent"):
149+
pipeline = connection.pipeline(transaction=False)
150+
pipeline.get("foo")
151+
pipeline.set("bar", 1)
152+
pipeline.set("baz", 2)
153+
pipeline.execute()
154+
sentry_sdk.flush()
155+
156+
assert len(items) == 2
157+
pipeline_span, parent_span = items[0].payload, items[1].payload
158+
159+
assert parent_span["name"] == "custom parent"
160+
assert pipeline_span["name"] == "redis.pipeline.execute"
161+
assert pipeline_span["attributes"]["sentry.op"] == "db.redis"
162+
else:
163+
events = capture_events()
164+
with start_transaction():
165+
pipeline = connection.pipeline(transaction=False)
166+
pipeline.get("foo")
167+
pipeline.set("bar", 1)
168+
pipeline.set("baz", 2)
169+
pipeline.execute()
170+
171+
(event,) = events
172+
(span,) = event["spans"]
173+
assert span["op"] == "db.redis"
174+
assert span["description"] == "redis.pipeline.execute"
175+
assert span["data"]["redis.commands"] == {
176+
"count": 3,
177+
"first_ten": expected_first_ten,
178+
}
179+
180+
115181
@pytest.mark.parametrize("span_streaming", [True, False])
116182
def test_sensitive_data(sentry_init, capture_events, capture_items, span_streaming):
117183
# fakeredis does not support the AUTH command, so we need to mock it
@@ -201,6 +267,117 @@ def test_pii_data_redacted(sentry_init, capture_events, capture_items, span_stre
201267
assert spans[3]["description"] == "DEL 'somekey1' [Filtered]"
202268

203269

270+
@pytest.mark.parametrize("span_streaming", [True, False])
271+
@pytest.mark.parametrize(
272+
"data_collection, expected_description",
273+
[
274+
({"database_query_data": False}, "SET 'somekey1'"),
275+
({"database_query_data": True}, "SET 'somekey1' 'my secret string1'"),
276+
({}, "SET 'somekey1' 'my secret string1'"),
277+
],
278+
ids=[
279+
"database_query_data_disabled",
280+
"database_query_data_enabled",
281+
"database_query_data_not_provided_uses_defaults",
282+
],
283+
)
284+
def test_data_collection_database_query_data(
285+
sentry_init,
286+
capture_events,
287+
capture_items,
288+
span_streaming,
289+
data_collection,
290+
expected_description,
291+
):
292+
sentry_init(
293+
integrations=[RedisIntegration()],
294+
traces_sample_rate=1.0,
295+
trace_lifecycle="stream" if span_streaming else "static",
296+
_experiments={"data_collection": data_collection},
297+
)
298+
299+
connection = FakeStrictRedis()
300+
301+
if span_streaming:
302+
items = capture_items("span")
303+
with sentry_sdk.traces.start_span(name="custom parent"):
304+
connection.set("somekey1", "my secret string1")
305+
sentry_sdk.flush()
306+
307+
assert len(items) == 2
308+
set_span, parent = [item.payload for item in items]
309+
310+
assert parent["name"] == "custom parent"
311+
assert set_span["name"] == expected_description
312+
assert set_span["attributes"][SPANDATA.DB_QUERY_TEXT] == expected_description
313+
assert set_span["attributes"]["sentry.op"] == "db.redis"
314+
else:
315+
events = capture_events()
316+
with start_transaction():
317+
connection.set("somekey1", "my secret string1")
318+
319+
(event,) = events
320+
spans = event["spans"]
321+
assert spans[0]["op"] == "db.redis"
322+
assert spans[0]["description"] == expected_description
323+
324+
325+
@pytest.mark.parametrize("span_streaming", [True, False])
326+
@pytest.mark.parametrize(
327+
"data_collection, send_default_pii, expected_description",
328+
[
329+
({"database_query_data": False}, True, "SET 'somekey1'"),
330+
(
331+
{"database_query_data": True},
332+
False,
333+
"SET 'somekey1' 'my secret string1'",
334+
),
335+
],
336+
)
337+
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
338+
def test_database_query_data_takes_precedence_over_send_default_pii(
339+
sentry_init,
340+
capture_events,
341+
capture_items,
342+
span_streaming,
343+
data_collection,
344+
send_default_pii,
345+
expected_description,
346+
):
347+
sentry_init(
348+
integrations=[RedisIntegration()],
349+
traces_sample_rate=1.0,
350+
send_default_pii=send_default_pii,
351+
trace_lifecycle="stream" if span_streaming else "static",
352+
_experiments={"data_collection": data_collection},
353+
)
354+
355+
connection = FakeStrictRedis()
356+
357+
if span_streaming:
358+
items = capture_items("span")
359+
with sentry_sdk.traces.start_span(name="custom parent"):
360+
connection.set("somekey1", "my secret string1")
361+
sentry_sdk.flush()
362+
363+
assert len(items) == 2
364+
set_span, parent = [item.payload for item in items]
365+
366+
assert parent["name"] == "custom parent"
367+
assert set_span["name"] == expected_description
368+
assert set_span["attributes"][SPANDATA.DB_QUERY_TEXT] == expected_description
369+
assert set_span["attributes"]["sentry.op"] == "db.redis"
370+
else:
371+
events = capture_events()
372+
with start_transaction():
373+
connection.set("somekey1", "my secret string1")
374+
375+
(event,) = events
376+
spans = event["spans"]
377+
assert spans[0]["op"] == "db.redis"
378+
assert spans[0]["description"] == expected_description
379+
380+
204381
@pytest.mark.parametrize("span_streaming", [True, False])
205382
def test_pii_data_sent(sentry_init, capture_events, capture_items, span_streaming):
206383
sentry_init(

0 commit comments

Comments
 (0)