Skip to content

Commit 724e61c

Browse files
committed
ref: Store tracked spans on start not finish
This matches the JS implementation. Without it, we cannot use the span recorder of a span to find its parent transaction. Note about test changes Instrumented subprocess methods are called in this order: __init__, communicate, wait. Because we now store the spans on start, that's the order we expect the spans to be in. The previous order was based on finish time. Grouping the assertion of "op" values together produces better output on failure, because one can easily detect what all the "op" values are, instead of being left with only the first one that is different. Similar to subprocess changes, the order of expected middleware spans in Django is now sorted by start time.
1 parent cf582f6 commit 724e61c

File tree

3 files changed

+32
-32
lines changed

3 files changed

+32
-32
lines changed

sentry_sdk/tracing.py

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -67,28 +67,26 @@ def __iter__(self):
6767

6868

6969
class _SpanRecorder(object):
70-
__slots__ = ("maxlen", "finished_spans", "open_span_count")
70+
"""Limits the number of spans recorded in a transaction."""
71+
72+
__slots__ = ("maxlen", "spans")
7173

7274
def __init__(self, maxlen):
7375
# type: (int) -> None
74-
self.maxlen = maxlen
75-
self.open_span_count = 0 # type: int
76-
self.finished_spans = [] # type: List[Span]
77-
78-
def start_span(self, span):
76+
# FIXME: this is `maxlen - 1` only to preserve historical behavior
77+
# enforced by tests.
78+
# Either this should be changed to `maxlen` or the JS SDK implementation
79+
# should be changed to match a consistent interpretation of what maxlen
80+
# limits: either transaction+spans or only child spans.
81+
self.maxlen = maxlen - 1
82+
self.spans = [] # type: List[Span]
83+
84+
def add(self, span):
7985
# type: (Span) -> None
80-
81-
# This is just so that we don't run out of memory while recording a lot
82-
# of spans. At some point we just stop and flush out the start of the
83-
# trace tree (i.e. the first n spans with the smallest
84-
# start_timestamp).
85-
self.open_span_count += 1
86-
if self.open_span_count > self.maxlen:
86+
if len(self.spans) > self.maxlen:
8787
span._span_recorder = None
88-
89-
def finish_span(self, span):
90-
# type: (Span) -> None
91-
self.finished_spans.append(span)
88+
else:
89+
self.spans.append(span)
9290

9391

9492
class Span(object):
@@ -157,7 +155,7 @@ def init_finished_spans(self, maxlen):
157155
# type: (int) -> None
158156
if self._span_recorder is None:
159157
self._span_recorder = _SpanRecorder(maxlen)
160-
self._span_recorder.start_span(self)
158+
self._span_recorder.add(self)
161159

162160
def __repr__(self):
163161
# type: () -> str
@@ -330,8 +328,6 @@ def finish(self, hub=None):
330328
if self._span_recorder is None:
331329
return None
332330

333-
self._span_recorder.finish_span(self)
334-
335331
if self.transaction is None:
336332
# If this has no transaction set we assume there's a parent
337333
# transaction for this span that would be flushed out eventually.
@@ -354,6 +350,12 @@ def finish(self, hub=None):
354350

355351
return None
356352

353+
finished_spans = [
354+
span.to_json(client)
355+
for span in self._span_recorder.spans
356+
if span is not self and span.timestamp is not None
357+
]
358+
357359
return hub.capture_event(
358360
{
359361
"type": "transaction",
@@ -362,11 +364,7 @@ def finish(self, hub=None):
362364
"tags": self._tags,
363365
"timestamp": self.timestamp,
364366
"start_timestamp": self.start_timestamp,
365-
"spans": [
366-
s.to_json(client)
367-
for s in self._span_recorder.finished_spans
368-
if s is not self
369-
],
367+
"spans": finished_spans,
370368
}
371369
)
372370

tests/integrations/django/test_basic.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -518,10 +518,10 @@ def test_middleware_spans(sentry_init, client, capture_events):
518518

519519
if DJANGO_VERSION >= (1, 10):
520520
reference_value = [
521-
"tests.integrations.django.myapp.settings.TestFunctionMiddleware.__call__",
522-
"tests.integrations.django.myapp.settings.TestMiddleware.__call__",
523-
"django.contrib.auth.middleware.AuthenticationMiddleware.__call__",
524521
"django.contrib.sessions.middleware.SessionMiddleware.__call__",
522+
"django.contrib.auth.middleware.AuthenticationMiddleware.__call__",
523+
"tests.integrations.django.myapp.settings.TestMiddleware.__call__",
524+
"tests.integrations.django.myapp.settings.TestFunctionMiddleware.__call__",
525525
]
526526
else:
527527
reference_value = [

tests/integrations/stdlib/test_subprocess.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,13 +140,15 @@ def test_subprocess_basic(
140140

141141
(
142142
subprocess_init_span,
143-
subprocess_wait_span,
144143
subprocess_communicate_span,
144+
subprocess_wait_span,
145145
) = transaction_event["spans"]
146146

147-
assert subprocess_init_span["op"] == "subprocess"
148-
assert subprocess_communicate_span["op"] == "subprocess.communicate"
149-
assert subprocess_wait_span["op"] == "subprocess.wait"
147+
assert (
148+
subprocess_init_span["op"],
149+
subprocess_communicate_span["op"],
150+
subprocess_wait_span["op"],
151+
) == ("subprocess", "subprocess.communicate", "subprocess.wait")
150152

151153
# span hierarchy
152154
assert (

0 commit comments

Comments
 (0)