Skip to content

Commit 665447a

Browse files
ericapisaniclaude
andcommitted
ref(asyncio): Migrate integration to span-first
Update the asyncio integration to use sentry_sdk.traces.start_span() when span streaming is enabled, storing op and origin as span attributes instead of top-level fields. Add span_streaming parametrization to test_create_task to cover both the legacy static path and the new streaming path, asserting on segment and span structure in each mode. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent b99f017 commit 665447a

2 files changed

Lines changed: 91 additions & 38 deletions

File tree

sentry_sdk/integrations/asyncio.py

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
import sys
21
import functools
2+
import sys
33

44
import sentry_sdk
55
from sentry_sdk.consts import OP
6-
from sentry_sdk.integrations import Integration, DidNotEnable
6+
from sentry_sdk.integrations import DidNotEnable, Integration
77
from sentry_sdk.integrations._wsgi_common import nullcontext
8+
from sentry_sdk.tracing_utils import has_span_streaming_enabled
9+
from sentry_sdk.transport import AsyncHttpTransport
810
from sentry_sdk.utils import (
911
event_from_exception,
12+
is_internal_task,
1013
logger,
1114
reraise,
12-
is_internal_task,
1315
)
14-
from sentry_sdk.transport import AsyncHttpTransport
1516

1617
try:
1718
import asyncio
@@ -22,8 +23,8 @@
2223
from typing import TYPE_CHECKING
2324

2425
if TYPE_CHECKING:
25-
from typing import Any, Callable, TypeVar
2626
from collections.abc import Coroutine
27+
from typing import Any, Callable, TypeVar
2728

2829
from sentry_sdk._types import ExcInfo
2930

@@ -142,22 +143,31 @@ def _sentry_task_factory(
142143
@_wrap_coroutine(coro)
143144
async def _task_with_sentry_span_creation() -> "Any":
144145
result = None
145-
146-
integration = sentry_sdk.get_client().get_integration(
147-
AsyncioIntegration
148-
)
146+
client = sentry_sdk.get_client()
147+
integration = client.get_integration(AsyncioIntegration)
149148
task_spans = integration.task_spans if integration else False
150149

150+
span_ctx = None
151+
is_span_streaming_enabled = has_span_streaming_enabled(client.options)
152+
151153
with sentry_sdk.isolation_scope():
152-
with (
153-
sentry_sdk.start_span(
154-
op=OP.FUNCTION,
155-
name=get_name(coro),
156-
origin=AsyncioIntegration.origin,
157-
)
158-
if task_spans
159-
else nullcontext()
160-
):
154+
if task_spans:
155+
if is_span_streaming_enabled:
156+
span_ctx = sentry_sdk.traces.start_span(
157+
name=get_name(coro),
158+
attributes={
159+
"sentry.op": OP.FUNCTION,
160+
"sentry.origin": AsyncioIntegration.origin,
161+
},
162+
)
163+
else:
164+
span_ctx = sentry_sdk.start_span(
165+
op=OP.FUNCTION,
166+
name=get_name(coro),
167+
origin=AsyncioIntegration.origin,
168+
)
169+
170+
with span_ctx if span_ctx else nullcontext():
161171
try:
162172
result = await coro
163173
except StopAsyncIteration as e:

tests/integrations/asyncio/test_asyncio.py

Lines changed: 63 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
from sentry_sdk.consts import OP
1313
from sentry_sdk.integrations.asyncio import (
1414
AsyncioIntegration,
15-
patch_asyncio,
1615
enable_asyncio_integration,
16+
patch_asyncio,
1717
)
1818

1919
try:
@@ -63,22 +63,30 @@ def get_sentry_task_factory(mock_get_running_loop):
6363

6464
@minimum_python_38
6565
@pytest.mark.asyncio(loop_scope="module")
66+
@pytest.mark.parametrize("span_streaming", [True, False])
6667
async def test_create_task(
6768
sentry_init,
6869
capture_events,
70+
capture_items,
71+
span_streaming,
6972
):
7073
sentry_init(
7174
traces_sample_rate=1.0,
7275
send_default_pii=True,
7376
integrations=[
7477
AsyncioIntegration(),
7578
],
79+
_experiments={
80+
"trace_lifecycle": "stream" if span_streaming else "static",
81+
},
7682
)
7783

78-
events = capture_events()
84+
if span_streaming:
85+
items = capture_items("span")
7986

80-
with sentry_sdk.start_transaction(name="test_transaction_for_create_task"):
81-
with sentry_sdk.start_span(op="root", name="not so important"):
87+
with sentry_sdk.traces.start_span(
88+
name="not so important", attributes={"sentry.op": "root"}
89+
):
8290
foo_task = asyncio.create_task(foo())
8391
bar_task = asyncio.create_task(bar())
8492

@@ -91,26 +99,61 @@ async def test_create_task(
9199

92100
await asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
93101

94-
sentry_sdk.flush()
102+
else:
103+
events = capture_events()
95104

96-
(transaction_event,) = events
105+
with sentry_sdk.start_transaction(name="test_transaction_for_create_task"):
106+
with sentry_sdk.start_span(op="root", name="not so important"):
107+
foo_task = asyncio.create_task(foo())
108+
bar_task = asyncio.create_task(bar())
97109

98-
assert transaction_event["spans"][0]["op"] == "root"
99-
assert transaction_event["spans"][0]["description"] == "not so important"
110+
if hasattr(foo_task.get_coro(), "__name__"):
111+
assert foo_task.get_coro().__name__ == "foo"
112+
if hasattr(bar_task.get_coro(), "__name__"):
113+
assert bar_task.get_coro().__name__ == "bar"
100114

101-
assert transaction_event["spans"][1]["op"] == OP.FUNCTION
102-
assert transaction_event["spans"][1]["description"] == "foo"
103-
assert (
104-
transaction_event["spans"][1]["parent_span_id"]
105-
== transaction_event["spans"][0]["span_id"]
106-
)
115+
tasks = [foo_task, bar_task]
107116

108-
assert transaction_event["spans"][2]["op"] == OP.FUNCTION
109-
assert transaction_event["spans"][2]["description"] == "bar"
110-
assert (
111-
transaction_event["spans"][2]["parent_span_id"]
112-
== transaction_event["spans"][0]["span_id"]
113-
)
117+
await asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
118+
119+
sentry_sdk.flush()
120+
121+
if span_streaming:
122+
segment = items.pop().payload
123+
124+
assert segment["is_segment"] is True
125+
assert segment["name"] == "not so important"
126+
assert segment["attributes"]["sentry.op"] == "root"
127+
128+
spans = [item.payload for item in items]
129+
assert len(spans) == 2
130+
131+
assert spans[0]["attributes"]["sentry.op"] == OP.FUNCTION
132+
assert spans[0]["name"] == "foo"
133+
assert spans[0]["parent_span_id"] == segment["span_id"]
134+
135+
assert spans[1]["attributes"]["sentry.op"] == OP.FUNCTION
136+
assert spans[1]["name"] == "bar"
137+
assert spans[1]["parent_span_id"] == segment["span_id"]
138+
else:
139+
(transaction_event,) = events
140+
141+
assert transaction_event["spans"][0]["op"] == "root"
142+
assert transaction_event["spans"][0]["description"] == "not so important"
143+
144+
assert transaction_event["spans"][1]["op"] == OP.FUNCTION
145+
assert transaction_event["spans"][1]["description"] == "foo"
146+
assert (
147+
transaction_event["spans"][1]["parent_span_id"]
148+
== transaction_event["spans"][0]["span_id"]
149+
)
150+
151+
assert transaction_event["spans"][2]["op"] == OP.FUNCTION
152+
assert transaction_event["spans"][2]["description"] == "bar"
153+
assert (
154+
transaction_event["spans"][2]["parent_span_id"]
155+
== transaction_event["spans"][0]["span_id"]
156+
)
114157

115158

116159
@minimum_python_38

0 commit comments

Comments
 (0)