Skip to content

Commit 4e9e97c

Browse files
authored
feat(pymongo): Respect data_collection.database_query_data option (#6939)
Use the data_collection.database_query_data option to decide whether to strip PII from captured MongoDB command data, falling back to send_default_pii when the new option is not configured. Refs PY-2587 Refs #6747
1 parent e39424c commit 4e9e97c

2 files changed

Lines changed: 203 additions & 2 deletions

File tree

sentry_sdk/integrations/pymongo.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from sentry_sdk.traces import SpanStatus, StreamedSpan
99
from sentry_sdk.tracing import Span
1010
from sentry_sdk.tracing_utils import has_span_streaming_enabled
11-
from sentry_sdk.utils import capture_internal_exceptions
11+
from sentry_sdk.utils import capture_internal_exceptions, has_data_collection_enabled
1212

1313
try:
1414
from pymongo import monitoring
@@ -146,7 +146,10 @@ def started(self, event: "CommandStartedEvent") -> None:
146146
db_name = event.database_name
147147

148148
lsid = command.pop("lsid", None)
149-
if not should_send_default_pii():
149+
if has_data_collection_enabled(client.options):
150+
if not client.options["data_collection"]["database_query_data"]:
151+
command = _strip_pii(command)
152+
elif not should_send_default_pii():
150153
command = _strip_pii(command)
151154

152155
query = json.dumps(command, default=str)

tests/integrations/pymongo/test_pymongo.py

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,87 @@ def test_transactions(sentry_init, capture_events, mongo_server, with_pii):
110110
assert insert_fail["tags"]["status"] == "internal_error"
111111

112112

113+
DATA_COLLECTION_DATABASE_QUERY_DATA_USE_CASES = [
114+
pytest.param(
115+
{"_experiments": {"data_collection": {"database_query_data": True}}},
116+
True,
117+
id="query_data_enabled",
118+
),
119+
pytest.param(
120+
{"_experiments": {"data_collection": {"database_query_data": False}}},
121+
False,
122+
id="query_data_disabled",
123+
),
124+
pytest.param(
125+
{"_experiments": {"data_collection": {}}},
126+
True,
127+
id="query_data_default",
128+
),
129+
pytest.param(
130+
{
131+
"send_default_pii": False,
132+
"_experiments": {"data_collection": {"database_query_data": True}},
133+
},
134+
True,
135+
id="data_collection_overrides_pii_off",
136+
),
137+
pytest.param(
138+
{
139+
"send_default_pii": True,
140+
"_experiments": {"data_collection": {"database_query_data": False}},
141+
},
142+
False,
143+
id="data_collection_overrides_pii_on",
144+
),
145+
]
146+
147+
148+
@pytest.mark.parametrize(
149+
"init_kwargs,expect_query_values", DATA_COLLECTION_DATABASE_QUERY_DATA_USE_CASES
150+
)
151+
def test_transactions_with_data_collection(
152+
sentry_init, capture_events, mongo_server, init_kwargs, expect_query_values
153+
):
154+
sentry_init(
155+
integrations=[PyMongoIntegration()],
156+
traces_sample_rate=1.0,
157+
**init_kwargs,
158+
)
159+
events = capture_events()
160+
161+
connection = MongoClient(mongo_server.uri)
162+
163+
with start_transaction():
164+
list(
165+
connection["test_db"]["test_collection"].find({"foobar": 1})
166+
) # force query execution
167+
connection["test_db"]["test_collection"].insert_one({"foo": 2})
168+
try:
169+
connection["test_db"]["erroneous"].insert_many([{"bar": 3}, {"baz": 4}])
170+
pytest.fail("Request should raise")
171+
except Exception:
172+
pass
173+
174+
(event,) = events
175+
(find, insert_success, insert_fail) = event["spans"]
176+
177+
assert find["description"].startswith('{"find')
178+
assert insert_success["description"].startswith('{"insert')
179+
assert insert_fail["description"].startswith('{"insert')
180+
181+
if expect_query_values:
182+
assert "1" in find["description"]
183+
assert "2" in insert_success["description"]
184+
assert "3" in insert_fail["description"] and "4" in insert_fail["description"]
185+
else:
186+
assert "1" not in find["description"]
187+
assert "2" not in insert_success["description"]
188+
assert (
189+
"3" not in insert_fail["description"]
190+
and "4" not in insert_fail["description"]
191+
)
192+
193+
113194
@pytest.mark.parametrize("with_pii", [False, True])
114195
def test_segment_span_streaming(sentry_init, capture_items, mongo_server, with_pii):
115196
sentry_init(
@@ -179,6 +260,57 @@ def test_segment_span_streaming(sentry_init, capture_items, mongo_server, with_p
179260
assert insert_fail["status"] == "error"
180261

181262

263+
@pytest.mark.parametrize(
264+
"init_kwargs,expect_query_values", DATA_COLLECTION_DATABASE_QUERY_DATA_USE_CASES
265+
)
266+
def test_segment_span_streaming_with_data_collection(
267+
sentry_init, capture_items, mongo_server, init_kwargs, expect_query_values
268+
):
269+
sentry_init(
270+
integrations=[PyMongoIntegration()],
271+
traces_sample_rate=1.0,
272+
trace_lifecycle="stream",
273+
**init_kwargs,
274+
)
275+
items = capture_items("span")
276+
277+
connection = MongoClient(mongo_server.uri)
278+
279+
with sentry_sdk.traces.start_span(name="test_segment"):
280+
list(
281+
connection["test_db"]["test_collection"].find({"foobar": 1})
282+
) # force query execution
283+
connection["test_db"]["test_collection"].insert_one({"foo": 2})
284+
try:
285+
connection["test_db"]["erroneous"].insert_many([{"bar": 3}, {"baz": 4}])
286+
pytest.fail("Request should raise")
287+
except Exception:
288+
pass
289+
sentry_sdk.flush()
290+
291+
spans = [item.payload for item in items]
292+
assert len(spans) == 4
293+
294+
(find, insert_success, insert_fail, segment) = spans
295+
assert segment["name"] == "test_segment"
296+
297+
assert find["name"].startswith('{"find')
298+
assert insert_success["name"].startswith('{"insert')
299+
assert insert_fail["name"].startswith('{"insert')
300+
301+
for span in find, insert_success, insert_fail:
302+
assert span["attributes"][SPANDATA.DB_QUERY_TEXT] == span["name"]
303+
304+
if expect_query_values:
305+
assert "1" in find["name"]
306+
assert "2" in insert_success["name"]
307+
assert "3" in insert_fail["name"] and "4" in insert_fail["name"]
308+
else:
309+
assert "1" not in find["name"]
310+
assert "2" not in insert_success["name"]
311+
assert "3" not in insert_fail["name"] and "4" not in insert_fail["name"]
312+
313+
182314
@pytest.mark.parametrize("with_pii", [False, True])
183315
def test_breadcrumbs(sentry_init, capture_events, mongo_server, with_pii):
184316
sentry_init(
@@ -216,6 +348,38 @@ def test_breadcrumbs(sentry_init, capture_events, mongo_server, with_pii):
216348
}
217349

218350

351+
@pytest.mark.parametrize(
352+
"init_kwargs,expect_query_values", DATA_COLLECTION_DATABASE_QUERY_DATA_USE_CASES
353+
)
354+
def test_breadcrumbs_with_data_collection(
355+
sentry_init, capture_events, mongo_server, init_kwargs, expect_query_values
356+
):
357+
sentry_init(
358+
integrations=[PyMongoIntegration()],
359+
traces_sample_rate=1.0,
360+
**init_kwargs,
361+
)
362+
events = capture_events()
363+
364+
connection = MongoClient(mongo_server.uri)
365+
366+
list(
367+
connection["test_db"]["test_collection"].find({"foobar": 1})
368+
) # force query execution
369+
capture_message("hi")
370+
371+
(event,) = events
372+
(crumb,) = event["breadcrumbs"]["values"]
373+
374+
assert crumb["category"] == "query"
375+
assert crumb["message"].startswith('{"find')
376+
if expect_query_values:
377+
assert "1" in crumb["message"]
378+
else:
379+
assert "1" not in crumb["message"]
380+
assert crumb["type"] == "db"
381+
382+
219383
@pytest.mark.parametrize("with_pii", [False, True])
220384
def test_breadcrumbs_span_streaming(sentry_init, capture_items, mongo_server, with_pii):
221385
sentry_init(
@@ -257,6 +421,40 @@ def test_breadcrumbs_span_streaming(sentry_init, capture_items, mongo_server, wi
257421
assert data[SPANDATA.SERVER_PORT] == mongo_server.port
258422

259423

424+
@pytest.mark.parametrize(
425+
"init_kwargs,expect_query_values", DATA_COLLECTION_DATABASE_QUERY_DATA_USE_CASES
426+
)
427+
def test_breadcrumbs_span_streaming_with_data_collection(
428+
sentry_init, capture_items, mongo_server, init_kwargs, expect_query_values
429+
):
430+
sentry_init(
431+
integrations=[PyMongoIntegration()],
432+
traces_sample_rate=1.0,
433+
trace_lifecycle="stream",
434+
**init_kwargs,
435+
)
436+
items = capture_items("event")
437+
438+
connection = MongoClient(mongo_server.uri)
439+
440+
list(
441+
connection["test_db"]["test_collection"].find({"foobar": 1})
442+
) # force query execution
443+
capture_message("hi")
444+
445+
event = items[0].payload
446+
(crumb,) = event["breadcrumbs"]["values"]
447+
448+
assert crumb["category"] == "query"
449+
assert crumb["message"].startswith('{"find')
450+
if expect_query_values:
451+
assert "1" in crumb["message"]
452+
else:
453+
assert "1" not in crumb["message"]
454+
assert crumb["type"] == "db"
455+
assert crumb["data"]["db.query.text"] == crumb["message"]
456+
457+
260458
@pytest.mark.parametrize(
261459
"testcase",
262460
[

0 commit comments

Comments
 (0)