Skip to content

Commit 36b88ff

Browse files
authored
fix: Do not capture SQL params for now (getsentry#503)
* fix: Do not capture SQL params for now * fix: Discard entire span tree if too large * ref: Send partial span tree instead of clearing it * fix: Update tests * feat: Add experiment to get back params
1 parent 5731fa3 commit 36b88ff

File tree

6 files changed

+100
-21
lines changed

6 files changed

+100
-21
lines changed

sentry_sdk/consts.py

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def __init__(
4949
# DO NOT ENABLE THIS RIGHT NOW UNLESS YOU WANT TO EXCEED YOUR EVENT QUOTA IMMEDIATELY
5050
traces_sample_rate=0.0, # type: float
5151
traceparent_v2=False, # type: bool
52+
_experiments={}, # type: Dict[str, Any]
5253
):
5354
# type: (...) -> None
5455
pass

sentry_sdk/hub.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,10 @@ def start_span(
450450
span.sampled = random.random() < sample_rate
451451

452452
if span.sampled:
453-
span.init_finished_spans()
453+
max_spans = (
454+
client and client.options["_experiments"].get("max_spans") or 1000
455+
)
456+
span.init_finished_spans(maxlen=max_spans)
454457

455458
return span
456459

sentry_sdk/tracing.py

+55-15
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,31 @@ def __iter__(self):
6464
yield k[len(self.prefix) :]
6565

6666

67+
class _SpanRecorder(object):
68+
__slots__ = ("maxlen", "finished_spans", "open_span_count")
69+
70+
def __init__(self, maxlen):
71+
# type: (int) -> None
72+
self.maxlen = maxlen
73+
self.open_span_count = 0 # type: int
74+
self.finished_spans = [] # type: List[Span]
75+
76+
def start_span(self, span):
77+
# type: (Span) -> None
78+
79+
# This is just so that we don't run out of memory while recording a lot
80+
# of spans. At some point we just stop and flush out the start of the
81+
# trace tree (i.e. the first n spans with the smallest
82+
# start_timestamp).
83+
self.open_span_count += 1
84+
if self.open_span_count > self.maxlen:
85+
span._span_recorder = None
86+
87+
def finish_span(self, span):
88+
# type: (Span) -> None
89+
self.finished_spans.append(span)
90+
91+
6792
class Span(object):
6893
__slots__ = (
6994
"trace_id",
@@ -78,7 +103,7 @@ class Span(object):
78103
"timestamp",
79104
"_tags",
80105
"_data",
81-
"_finished_spans",
106+
"_span_recorder",
82107
"hub",
83108
"_context_manager_state",
84109
)
@@ -107,16 +132,18 @@ def __init__(
107132
self.hub = hub
108133
self._tags = {} # type: Dict[str, str]
109134
self._data = {} # type: Dict[str, Any]
110-
self._finished_spans = None # type: Optional[List[Span]]
111135
self.start_timestamp = datetime.now()
112136

113137
#: End timestamp of span
114138
self.timestamp = None # type: Optional[datetime]
115139

116-
def init_finished_spans(self):
117-
# type: () -> None
118-
if self._finished_spans is None:
119-
self._finished_spans = []
140+
self._span_recorder = None # type: Optional[_SpanRecorder]
141+
142+
def init_finished_spans(self, maxlen):
143+
# type: (int) -> None
144+
if self._span_recorder is None:
145+
self._span_recorder = _SpanRecorder(maxlen)
146+
self._span_recorder.start_span(self)
120147

121148
def __repr__(self):
122149
# type: () -> str
@@ -162,7 +189,8 @@ def new_span(self, **kwargs):
162189
sampled=self.sampled,
163190
**kwargs
164191
)
165-
rv._finished_spans = self._finished_spans
192+
193+
rv._span_recorder = self._span_recorder
166194
return rv
167195

168196
@classmethod
@@ -252,11 +280,13 @@ def finish(self, hub=None):
252280

253281
self.timestamp = datetime.now()
254282

255-
if self._finished_spans is not None:
256-
self._finished_spans.append(self)
257-
258283
_maybe_create_breadcrumbs_from_span(hub, self)
259284

285+
if self._span_recorder is None:
286+
return None
287+
288+
self._span_recorder.finish_span(self)
289+
260290
if self.transaction is None:
261291
# If this has no transaction set we assume there's a parent
262292
# transaction for this span that would be flushed out eventually.
@@ -285,7 +315,9 @@ def finish(self, hub=None):
285315
"timestamp": self.timestamp,
286316
"start_timestamp": self.start_timestamp,
287317
"spans": [
288-
s.to_json() for s in (self._finished_spans or ()) if s is not self
318+
s.to_json()
319+
for s in self._span_recorder.finished_spans
320+
if s is not self
289321
],
290322
}
291323
)
@@ -354,11 +386,19 @@ def record_sql_queries(
354386
executemany, # type: bool
355387
):
356388
# type: (...) -> Generator[Span, None, None]
357-
if not params_list or params_list == [None]:
358-
params_list = None
359389

360-
if paramstyle == "pyformat":
361-
paramstyle = "format"
390+
# TODO: Bring back capturing of params by default
391+
if hub.client and hub.client.options["_experiments"].get(
392+
"record_sql_params", False
393+
):
394+
if not params_list or params_list == [None]:
395+
params_list = None
396+
397+
if paramstyle == "pyformat":
398+
paramstyle = "format"
399+
else:
400+
params_list = None
401+
paramstyle = None
362402

363403
query = _format_sql(cursor, query)
364404

tests/integrations/django/test_basic.py

+22-4
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,9 @@ def test_sql_queries(sentry_init, capture_events, with_integration):
170170
sentry_init(
171171
integrations=[DjangoIntegration()] if with_integration else [],
172172
send_default_pii=True,
173+
_experiments={"record_sql_params": True},
173174
)
175+
174176
from django.db import connection
175177

176178
sql = connection.cursor()
@@ -193,7 +195,11 @@ def test_sql_queries(sentry_init, capture_events, with_integration):
193195

194196
@pytest.mark.django_db
195197
def test_sql_dict_query_params(sentry_init, capture_events):
196-
sentry_init(integrations=[DjangoIntegration()], send_default_pii=True)
198+
sentry_init(
199+
integrations=[DjangoIntegration()],
200+
send_default_pii=True,
201+
_experiments={"record_sql_params": True},
202+
)
197203

198204
from django.db import connections
199205

@@ -230,7 +236,11 @@ def test_sql_dict_query_params(sentry_init, capture_events):
230236
)
231237
@pytest.mark.django_db
232238
def test_sql_psycopg2_string_composition(sentry_init, capture_events, query):
233-
sentry_init(integrations=[DjangoIntegration()], send_default_pii=True)
239+
sentry_init(
240+
integrations=[DjangoIntegration()],
241+
send_default_pii=True,
242+
_experiments={"record_sql_params": True},
243+
)
234244
from django.db import connections
235245

236246
if "postgres" not in connections:
@@ -254,7 +264,11 @@ def test_sql_psycopg2_string_composition(sentry_init, capture_events, query):
254264

255265
@pytest.mark.django_db
256266
def test_sql_psycopg2_placeholders(sentry_init, capture_events):
257-
sentry_init(integrations=[DjangoIntegration()], send_default_pii=True)
267+
sentry_init(
268+
integrations=[DjangoIntegration()],
269+
send_default_pii=True,
270+
_experiments={"record_sql_params": True},
271+
)
258272
from django.db import connections
259273

260274
if "postgres" not in connections:
@@ -499,7 +513,11 @@ def test_does_not_capture_403(sentry_init, client, capture_events, endpoint):
499513

500514

501515
def test_middleware_spans(sentry_init, client, capture_events):
502-
sentry_init(integrations=[DjangoIntegration()], traces_sample_rate=1.0)
516+
sentry_init(
517+
integrations=[DjangoIntegration()],
518+
traces_sample_rate=1.0,
519+
_experiments={"record_sql_params": True},
520+
)
503521
events = capture_events()
504522

505523
_content, status, _headers = client.get(reverse("message"))

tests/integrations/sqlalchemy/test_sqlalchemy.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88

99

1010
def test_orm_queries(sentry_init, capture_events):
11-
sentry_init(integrations=[SqlalchemyIntegration()])
11+
sentry_init(
12+
integrations=[SqlalchemyIntegration()], _experiments={"record_sql_params": True}
13+
)
1214
events = capture_events()
1315

1416
Base = declarative_base()

tests/test_tracing.py

+15
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,18 @@ def foo():
127127
gc.collect()
128128

129129
assert len(references) == expected_refcount
130+
131+
132+
def test_span_trimming(sentry_init, capture_events):
133+
sentry_init(traces_sample_rate=1.0, _experiments={"max_spans": 3})
134+
events = capture_events()
135+
136+
with Hub.current.start_span(transaction="hi"):
137+
for i in range(10):
138+
with Hub.current.start_span(op="foo{}".format(i)):
139+
pass
140+
141+
event, = events
142+
span1, span2 = event["spans"]
143+
assert span1["op"] == "foo0"
144+
assert span2["op"] == "foo1"

0 commit comments

Comments
 (0)