Skip to content

Commit 5bb6ffc

Browse files
authored
feat(tracing): Make spans point to their transactions (getsentry#870)
1 parent 874a467 commit 5bb6ffc

File tree

2 files changed

+92
-4
lines changed

2 files changed

+92
-4
lines changed

sentry_sdk/tracing.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ class Span(object):
109109
"_span_recorder",
110110
"hub",
111111
"_context_manager_state",
112+
# TODO: rename this "transaction" once we fully and truly deprecate the
113+
# old "transaction" attribute (which was actually the transaction name)?
114+
"_containing_transaction",
112115
)
113116

114117
def __new__(cls, **kwargs):
@@ -164,6 +167,7 @@ def __init__(
164167
self.timestamp = None # type: Optional[datetime]
165168

166169
self._span_recorder = None # type: Optional[_SpanRecorder]
170+
self._containing_transaction = None # type: Optional[Transaction]
167171

168172
def init_span_recorder(self, maxlen):
169173
# type: (int) -> None
@@ -210,15 +214,20 @@ def start_child(self, **kwargs):
210214
Start a sub-span from the current span or transaction.
211215
212216
Takes the same arguments as the initializer of :py:class:`Span`. The
213-
trace id, sampling decision, and span recorder are inherited from the
214-
current span/transaction.
217+
trace id, sampling decision, transaction pointer, and span recorder are
218+
inherited from the current span/transaction.
215219
"""
216220
kwargs.setdefault("sampled", self.sampled)
217221

218222
rv = Span(
219223
trace_id=self.trace_id, span_id=None, parent_span_id=self.span_id, **kwargs
220224
)
221225

226+
if isinstance(self, Transaction):
227+
rv._containing_transaction = self
228+
else:
229+
rv._containing_transaction = self._containing_transaction
230+
222231
rv._span_recorder = recorder = self._span_recorder
223232
if recorder:
224233
recorder.add(rv)

tests/tracing/test_misc.py

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pytest
22

3-
from sentry_sdk import start_span, start_transaction
4-
from sentry_sdk.tracing import Transaction
3+
from sentry_sdk import Hub, start_span, start_transaction
4+
from sentry_sdk.tracing import Span, Transaction
55

66

77
def test_span_trimming(sentry_init, capture_events):
@@ -49,3 +49,82 @@ def test_transaction_method_signature(sentry_init, capture_events):
4949
with start_transaction(Transaction(name="c")):
5050
pass
5151
assert len(events) == 4
52+
53+
54+
def test_finds_transaction_on_scope(sentry_init):
55+
sentry_init(traces_sample_rate=1.0)
56+
57+
transaction = start_transaction(name="dogpark")
58+
59+
scope = Hub.current.scope
60+
61+
# See note in Scope class re: getters and setters of the `transaction`
62+
# property. For the moment, assigning to scope.transaction merely sets the
63+
# transaction name, rather than putting the transaction on the scope, so we
64+
# have to assign to _span directly.
65+
scope._span = transaction
66+
67+
# Reading scope.property, however, does what you'd expect, and returns the
68+
# transaction on the scope.
69+
assert scope.transaction is not None
70+
assert isinstance(scope.transaction, Transaction)
71+
assert scope.transaction.name == "dogpark"
72+
73+
# If the transaction is also set as the span on the scope, it can be found
74+
# by accessing _span, too.
75+
assert scope._span is not None
76+
assert isinstance(scope._span, Transaction)
77+
assert scope._span.name == "dogpark"
78+
79+
80+
def test_finds_transaction_when_decedent_span_is_on_scope(
81+
sentry_init,
82+
):
83+
sentry_init(traces_sample_rate=1.0)
84+
85+
transaction = start_transaction(name="dogpark")
86+
child_span = transaction.start_child(op="sniffing")
87+
88+
scope = Hub.current.scope
89+
scope._span = child_span
90+
91+
# this is the same whether it's the transaction itself or one of its
92+
# decedents directly attached to the scope
93+
assert scope.transaction is not None
94+
assert isinstance(scope.transaction, Transaction)
95+
assert scope.transaction.name == "dogpark"
96+
97+
# here we see that it is in fact the span on the scope, rather than the
98+
# transaction itself
99+
assert scope._span is not None
100+
assert isinstance(scope._span, Span)
101+
assert scope._span.op == "sniffing"
102+
103+
104+
def test_finds_orphan_span_on_scope(sentry_init):
105+
# this is deprecated behavior which may be removed at some point (along with
106+
# the start_span function)
107+
sentry_init(traces_sample_rate=1.0)
108+
109+
span = start_span(op="sniffing")
110+
111+
scope = Hub.current.scope
112+
scope._span = span
113+
114+
assert scope._span is not None
115+
assert isinstance(scope._span, Span)
116+
assert scope._span.op == "sniffing"
117+
118+
119+
def test_finds_non_orphan_span_on_scope(sentry_init):
120+
sentry_init(traces_sample_rate=1.0)
121+
122+
transaction = start_transaction(name="dogpark")
123+
child_span = transaction.start_child(op="sniffing")
124+
125+
scope = Hub.current.scope
126+
scope._span = child_span
127+
128+
assert scope._span is not None
129+
assert isinstance(scope._span, Span)
130+
assert scope._span.op == "sniffing"

0 commit comments

Comments
 (0)