Skip to content

Commit a2dff73

Browse files
authored
Fix pymongo (#3688)
1 parent f02c204 commit a2dff73

File tree

4 files changed

+52
-53
lines changed

4 files changed

+52
-53
lines changed

MIGRATION_GUIDE.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh
1010
### Changed
1111

1212
- The SDK now supports Python 3.7 and higher.
13+
- Transaction names can no longer contain commas and equals signs. If present, these characters will be stripped.
1314
- `sentry_sdk.start_span` now only takes keyword arguments.
1415
- `sentry_sdk.start_span` no longer takes an explicit `span` argument.
1516
- The `Span()` constructor does not accept a `hub` parameter anymore.
@@ -24,6 +25,8 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh
2425

2526
- Spans no longer have a `description`. Use `name` instead.
2627
- Dropped support for Python 3.6.
28+
- The PyMongo integration no longer sets tags. The data is still accessible via span attributes.
29+
- The PyMongo integration doesn't set `operation_ids` anymore. The individual IDs (`operation_id`, `request_id`, `session_id`) are now accessible as separate span attributes.
2730
- `sentry_sdk.metrics` and associated metrics APIs have been removed as Sentry no longer accepts metrics data in this form. See https://sentry.zendesk.com/hc/en-us/articles/26369339769883-Upcoming-API-Changes-to-Metrics
2831
- The experimental options `enable_metrics`, `before_emit_metric` and `metric_code_locations` have been removed.
2932
- When setting span status, the HTTP status code is no longer automatically added as a tag.

sentry_sdk/integrations/pymongo.py

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import copy
2-
import json
32

43
import sentry_sdk
54
from sentry_sdk.consts import SPANSTATUS, SPANDATA, OP
65
from sentry_sdk.integrations import DidNotEnable, Integration
76
from sentry_sdk.scope import should_send_default_pii
87
from sentry_sdk.tracing import Span
9-
from sentry_sdk.utils import capture_internal_exceptions
8+
from sentry_sdk.utils import capture_internal_exceptions, _serialize_span_attribute
109

1110
try:
1211
from pymongo import monitoring
@@ -127,56 +126,49 @@ def started(self, event):
127126
command.pop("$clusterTime", None)
128127
command.pop("$signature", None)
129128

130-
tags = {
131-
"db.name": event.database_name,
129+
data = {
130+
SPANDATA.DB_NAME: event.database_name,
132131
SPANDATA.DB_SYSTEM: "mongodb",
133132
SPANDATA.DB_OPERATION: event.command_name,
134133
SPANDATA.DB_MONGODB_COLLECTION: command.get(event.command_name),
135134
}
136135

137136
try:
138-
tags["net.peer.name"] = event.connection_id[0]
139-
tags["net.peer.port"] = str(event.connection_id[1])
137+
data["net.peer.name"] = event.connection_id[0]
138+
data["net.peer.port"] = str(event.connection_id[1])
140139
except TypeError:
141140
pass
142141

143-
data = {"operation_ids": {}} # type: Dict[str, Any]
144-
data["operation_ids"]["operation"] = event.operation_id
145-
data["operation_ids"]["request"] = event.request_id
146-
147-
data.update(_get_db_data(event))
148-
149142
try:
150143
lsid = command.pop("lsid")["id"]
151-
data["operation_ids"]["session"] = str(lsid)
144+
data["session_id"] = str(lsid)
152145
except KeyError:
153146
pass
154147

155148
if not should_send_default_pii():
156149
command = _strip_pii(command)
157150

158-
query = json.dumps(command, default=str)
151+
query = _serialize_span_attribute(command)
159152
span = sentry_sdk.start_span(
160153
op=OP.DB,
161154
name=query,
162155
origin=PyMongoIntegration.origin,
163156
)
164157

165-
for tag, value in tags.items():
166-
# set the tag for backwards-compatibility.
167-
# TODO: remove the set_tag call in the next major release!
168-
span.set_tag(tag, value)
169-
170-
span.set_data(tag, value)
171-
172-
for key, value in data.items():
173-
span.set_data(key, value)
174-
175158
with capture_internal_exceptions():
176159
sentry_sdk.add_breadcrumb(
177-
message=query, category="query", type=OP.DB, data=tags
160+
message=query, category="query", type=OP.DB, data=data
178161
)
179162

163+
for key, value in data.items():
164+
span.set_attribute(key, value)
165+
166+
for key, value in _get_db_data(event).items():
167+
span.set_attribute(key, value)
168+
169+
span.set_attribute("operation_id", event.operation_id)
170+
span.set_attribute("request_id", event.request_id)
171+
180172
self._ongoing_operations[self._operation_key(event)] = span.__enter__()
181173

182174
def failed(self, event):

sentry_sdk/tracing.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1189,11 +1189,6 @@ class POTelSpan:
11891189
OTel span wrapper providing compatibility with the old span interface.
11901190
"""
11911191

1192-
# XXX Maybe it makes sense to repurpose the existing Span class for this.
1193-
# For now I'm keeping this class separate to have a clean slate.
1194-
1195-
# XXX The wrapper itself should have as little state as possible
1196-
11971192
def __init__(
11981193
self,
11991194
*,
@@ -1225,14 +1220,14 @@ def __init__(
12251220
# OTel timestamps have nanosecond precision
12261221
start_timestamp = convert_to_otel_timestamp(start_timestamp)
12271222

1228-
self._otel_span = tracer.start_span(
1229-
name or description or op or "", start_time=start_timestamp
1230-
)
1223+
span_name = self._sanitize_name(name or description or op or "")
1224+
self._otel_span = tracer.start_span(span_name, start_time=start_timestamp)
12311225

12321226
self.origin = origin or DEFAULT_SPAN_ORIGIN
12331227
self.op = op
12341228
self.description = description
1235-
self.name = name
1229+
self.name = span_name
1230+
12361231
if status is not None:
12371232
self.set_status(status)
12381233

@@ -1602,6 +1597,10 @@ def set_context(self, key, value):
16021597

16031598
self.set_attribute(f"{SentrySpanAttribute.CONTEXT}.{key}", value)
16041599

1600+
def _sanitize_name(self, name):
1601+
"""No commas and equals allowed in tracestate."""
1602+
return name.replace(",", "").replace("=", "")
1603+
16051604

16061605
if TYPE_CHECKING:
16071606

tests/integrations/pymongo/test_pymongo.py

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import re
2+
13
from sentry_sdk import capture_message, start_transaction
24
from sentry_sdk.consts import SPANDATA
35
from sentry_sdk.integrations.pymongo import PyMongoIntegration, _strip_pii
@@ -49,7 +51,7 @@ def test_transactions(sentry_init, capture_events, mongo_server, with_pii):
4951
(event,) = events
5052
(find, insert_success, insert_fail) = event["spans"]
5153

52-
common_tags = {
54+
common_data = {
5355
"db.name": "test_db",
5456
"db.system": "mongodb",
5557
"net.peer.name": mongo_server.host,
@@ -60,31 +62,24 @@ def test_transactions(sentry_init, capture_events, mongo_server, with_pii):
6062
assert span["data"][SPANDATA.DB_NAME] == "test_db"
6163
assert span["data"][SPANDATA.SERVER_ADDRESS] == "localhost"
6264
assert span["data"][SPANDATA.SERVER_PORT] == mongo_server.port
63-
for field, value in common_tags.items():
64-
assert span["tags"][field] == value
65+
for field, value in common_data.items():
6566
assert span["data"][field] == value
6667

6768
assert find["op"] == "db"
6869
assert insert_success["op"] == "db"
6970
assert insert_fail["op"] == "db"
7071

7172
assert find["data"]["db.operation"] == "find"
72-
assert find["tags"]["db.operation"] == "find"
7373
assert insert_success["data"]["db.operation"] == "insert"
74-
assert insert_success["tags"]["db.operation"] == "insert"
7574
assert insert_fail["data"]["db.operation"] == "insert"
76-
assert insert_fail["tags"]["db.operation"] == "insert"
7775

7876
assert find["description"].startswith('{"find')
79-
assert insert_success["description"].startswith('{"insert')
80-
assert insert_fail["description"].startswith('{"insert')
77+
assert re.match("^{['\"]insert.*", insert_success["description"])
78+
assert re.match("^{['\"]insert.*", insert_fail["description"])
8179

8280
assert find["data"][SPANDATA.DB_MONGODB_COLLECTION] == "test_collection"
83-
assert find["tags"][SPANDATA.DB_MONGODB_COLLECTION] == "test_collection"
8481
assert insert_success["data"][SPANDATA.DB_MONGODB_COLLECTION] == "test_collection"
85-
assert insert_success["tags"][SPANDATA.DB_MONGODB_COLLECTION] == "test_collection"
8682
assert insert_fail["data"][SPANDATA.DB_MONGODB_COLLECTION] == "erroneous"
87-
assert insert_fail["tags"][SPANDATA.DB_MONGODB_COLLECTION] == "erroneous"
8883
if with_pii:
8984
assert "1" in find["description"]
9085
assert "2" in insert_success["description"]
@@ -99,16 +94,22 @@ def test_transactions(sentry_init, capture_events, mongo_server, with_pii):
9994
and "4" not in insert_fail["description"]
10095
)
10196

102-
assert find["tags"]["status"] == "ok"
103-
assert insert_success["tags"]["status"] == "ok"
104-
assert insert_fail["tags"]["status"] == "internal_error"
105-
10697

107-
@pytest.mark.parametrize("with_pii", [False, True])
108-
def test_breadcrumbs(sentry_init, capture_events, mongo_server, with_pii):
98+
@pytest.mark.parametrize(
99+
"with_pii,traces_sample_rate",
100+
[
101+
[False, 0.0],
102+
[False, 1.0],
103+
[True, 0.0],
104+
[True, 1.0],
105+
],
106+
)
107+
def test_breadcrumbs(
108+
sentry_init, capture_events, mongo_server, with_pii, traces_sample_rate
109+
):
109110
sentry_init(
110111
integrations=[PyMongoIntegration()],
111-
traces_sample_rate=1.0,
112+
traces_sample_rate=traces_sample_rate,
112113
send_default_pii=with_pii,
113114
)
114115
events = capture_events()
@@ -120,7 +121,11 @@ def test_breadcrumbs(sentry_init, capture_events, mongo_server, with_pii):
120121
) # force query execution
121122
capture_message("hi")
122123

123-
(event,) = events
124+
if traces_sample_rate:
125+
event = events[1]
126+
else:
127+
event = events[0]
128+
124129
(crumb,) = event["breadcrumbs"]["values"]
125130

126131
assert crumb["category"] == "query"

0 commit comments

Comments
 (0)