Skip to content

Commit ec88aa9

Browse files
fix(profiling): Update active thread for asgi (#3669)
Ensure the handling thread is set on the transaction for asgi transactions not just main thread. --------- Co-authored-by: Anton Pirker <[email protected]>
1 parent f5e964f commit ec88aa9

File tree

10 files changed

+150
-69
lines changed

10 files changed

+150
-69
lines changed

sentry_sdk/integrations/django/asgi.py

+4
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@ def wrap_async_view(callback):
172172
@functools.wraps(callback)
173173
async def sentry_wrapped_callback(request, *args, **kwargs):
174174
# type: (Any, *Any, **Any) -> Any
175+
current_scope = sentry_sdk.get_current_scope()
176+
if current_scope.transaction is not None:
177+
current_scope.transaction.update_active_thread()
178+
175179
sentry_scope = sentry_sdk.get_isolation_scope()
176180
if sentry_scope.profile is not None:
177181
sentry_scope.profile.update_active_thread_id()

sentry_sdk/integrations/django/views.py

+4
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ def _wrap_sync_view(callback):
7676
@functools.wraps(callback)
7777
def sentry_wrapped_callback(request, *args, **kwargs):
7878
# type: (Any, *Any, **Any) -> Any
79+
current_scope = sentry_sdk.get_current_scope()
80+
if current_scope.transaction is not None:
81+
current_scope.transaction.update_active_thread()
82+
7983
sentry_scope = sentry_sdk.get_isolation_scope()
8084
# set the active thread id to the handler thread for sync views
8185
# this isn't necessary for async views since that runs on main

sentry_sdk/integrations/fastapi.py

+5
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,14 @@ def _sentry_get_request_handler(*args, **kwargs):
8888
@wraps(old_call)
8989
def _sentry_call(*args, **kwargs):
9090
# type: (*Any, **Any) -> Any
91+
current_scope = sentry_sdk.get_current_scope()
92+
if current_scope.transaction is not None:
93+
current_scope.transaction.update_active_thread()
94+
9195
sentry_scope = sentry_sdk.get_isolation_scope()
9296
if sentry_scope.profile is not None:
9397
sentry_scope.profile.update_active_thread_id()
98+
9499
return old_call(*args, **kwargs)
95100

96101
dependant.call = _sentry_call

sentry_sdk/integrations/quart.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import asyncio
22
import inspect
3-
import threading
43
from functools import wraps
54

65
import sentry_sdk
@@ -122,11 +121,13 @@ def decorator(old_func):
122121
@ensure_integration_enabled(QuartIntegration, old_func)
123122
def _sentry_func(*args, **kwargs):
124123
# type: (*Any, **Any) -> Any
125-
scope = sentry_sdk.get_isolation_scope()
126-
if scope.profile is not None:
127-
scope.profile.active_thread_id = (
128-
threading.current_thread().ident
129-
)
124+
current_scope = sentry_sdk.get_current_scope()
125+
if current_scope.transaction is not None:
126+
current_scope.transaction.update_active_thread()
127+
128+
sentry_scope = sentry_sdk.get_isolation_scope()
129+
if sentry_scope.profile is not None:
130+
sentry_scope.profile.update_active_thread_id()
130131

131132
return old_func(*args, **kwargs)
132133

sentry_sdk/integrations/starlette.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -487,8 +487,11 @@ def _sentry_sync_func(*args, **kwargs):
487487
if integration is None:
488488
return old_func(*args, **kwargs)
489489

490-
sentry_scope = sentry_sdk.get_isolation_scope()
490+
current_scope = sentry_sdk.get_current_scope()
491+
if current_scope.transaction is not None:
492+
current_scope.transaction.update_active_thread()
491493

494+
sentry_scope = sentry_sdk.get_isolation_scope()
492495
if sentry_scope.profile is not None:
493496
sentry_scope.profile.update_active_thread_id()
494497

sentry_sdk/tracing.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -329,8 +329,7 @@ def __init__(
329329
self._span_recorder = None # type: Optional[_SpanRecorder]
330330
self._local_aggregator = None # type: Optional[LocalAggregator]
331331

332-
thread_id, thread_name = get_current_thread_meta()
333-
self.set_thread(thread_id, thread_name)
332+
self.update_active_thread()
334333
self.set_profiler_id(get_profiler_id())
335334

336335
# TODO this should really live on the Transaction class rather than the Span
@@ -732,6 +731,11 @@ def get_profile_context(self):
732731
"profiler_id": profiler_id,
733732
}
734733

734+
def update_active_thread(self):
735+
# type: () -> None
736+
thread_id, thread_name = get_current_thread_meta()
737+
self.set_thread(thread_id, thread_name)
738+
735739

736740
class Transaction(Span):
737741
"""The Transaction is the root element that holds all the spans

tests/integrations/django/asgi/test_asgi.py

+21-10
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,16 @@ async def test_async_views(sentry_init, capture_events, application):
104104
@pytest.mark.skipif(
105105
django.VERSION < (3, 1), reason="async views have been introduced in Django 3.1"
106106
)
107-
async def test_active_thread_id(sentry_init, capture_envelopes, endpoint, application):
107+
async def test_active_thread_id(
108+
sentry_init, capture_envelopes, teardown_profiling, endpoint, application
109+
):
108110
with mock.patch(
109111
"sentry_sdk.profiler.transaction_profiler.PROFILE_MINIMUM_SAMPLES", 0
110112
):
111113
sentry_init(
112114
integrations=[DjangoIntegration()],
113115
traces_sample_rate=1.0,
114-
_experiments={"profiles_sample_rate": 1.0},
116+
profiles_sample_rate=1.0,
115117
)
116118

117119
envelopes = capture_envelopes()
@@ -121,17 +123,26 @@ async def test_active_thread_id(sentry_init, capture_envelopes, endpoint, applic
121123
await comm.wait()
122124

123125
assert response["status"] == 200, response["body"]
124-
assert len(envelopes) == 1
125126

126-
profiles = [item for item in envelopes[0].items if item.type == "profile"]
127-
assert len(profiles) == 1
127+
assert len(envelopes) == 1
128+
129+
profiles = [item for item in envelopes[0].items if item.type == "profile"]
130+
assert len(profiles) == 1
131+
132+
data = json.loads(response["body"])
133+
134+
for item in profiles:
135+
transactions = item.payload.json["transactions"]
136+
assert len(transactions) == 1
137+
assert str(data["active"]) == transactions[0]["active_thread_id"]
128138

129-
data = json.loads(response["body"])
139+
transactions = [item for item in envelopes[0].items if item.type == "transaction"]
140+
assert len(transactions) == 1
130141

131-
for profile in profiles:
132-
transactions = profile.payload.json["transactions"]
133-
assert len(transactions) == 1
134-
assert str(data["active"]) == transactions[0]["active_thread_id"]
142+
for item in transactions:
143+
transaction = item.payload.json
144+
trace_context = transaction["contexts"]["trace"]
145+
assert str(data["active"]) == trace_context["data"]["thread.id"]
135146

136147

137148
@pytest.mark.asyncio

tests/integrations/fastapi/test_fastapi.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ def test_legacy_setup(
184184
def test_active_thread_id(sentry_init, capture_envelopes, teardown_profiling, endpoint):
185185
sentry_init(
186186
traces_sample_rate=1.0,
187-
_experiments={"profiles_sample_rate": 1.0},
187+
profiles_sample_rate=1.0,
188188
)
189189
app = fastapi_app_factory()
190190
asgi_app = SentryAsgiMiddleware(app)
@@ -203,11 +203,19 @@ def test_active_thread_id(sentry_init, capture_envelopes, teardown_profiling, en
203203
profiles = [item for item in envelopes[0].items if item.type == "profile"]
204204
assert len(profiles) == 1
205205

206-
for profile in profiles:
207-
transactions = profile.payload.json["transactions"]
206+
for item in profiles:
207+
transactions = item.payload.json["transactions"]
208208
assert len(transactions) == 1
209209
assert str(data["active"]) == transactions[0]["active_thread_id"]
210210

211+
transactions = [item for item in envelopes[0].items if item.type == "transaction"]
212+
assert len(transactions) == 1
213+
214+
for item in transactions:
215+
transaction = item.payload.json
216+
trace_context = transaction["contexts"]["trace"]
217+
assert str(data["active"]) == trace_context["data"]["thread.id"]
218+
211219

212220
@pytest.mark.asyncio
213221
async def test_original_request_not_scrubbed(sentry_init, capture_events):

0 commit comments

Comments
 (0)