Skip to content

Commit 435e856

Browse files
authored
Add session tracking to ASGI integration (#1329)
* test(wsgi): Test for correct session aggregates in wsgi * test(asgi): added failing test * feat(asgi): auto session tracking
1 parent 3720466 commit 435e856

File tree

3 files changed

+118
-33
lines changed

3 files changed

+118
-33
lines changed

sentry_sdk/integrations/asgi.py

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from sentry_sdk._types import MYPY
1313
from sentry_sdk.hub import Hub, _should_send_default_pii
1414
from sentry_sdk.integrations._wsgi_common import _filter_headers
15+
from sentry_sdk.sessions import auto_session_tracking
1516
from sentry_sdk.utils import (
1617
ContextVar,
1718
event_from_exception,
@@ -119,37 +120,38 @@ async def _run_app(self, scope, callback):
119120
_asgi_middleware_applied.set(True)
120121
try:
121122
hub = Hub(Hub.current)
122-
with hub:
123-
with hub.configure_scope() as sentry_scope:
124-
sentry_scope.clear_breadcrumbs()
125-
sentry_scope._name = "asgi"
126-
processor = partial(self.event_processor, asgi_scope=scope)
127-
sentry_scope.add_event_processor(processor)
128-
129-
ty = scope["type"]
130-
131-
if ty in ("http", "websocket"):
132-
transaction = Transaction.continue_from_headers(
133-
self._get_headers(scope),
134-
op="{}.server".format(ty),
135-
)
136-
else:
137-
transaction = Transaction(op="asgi.server")
138-
139-
transaction.name = _DEFAULT_TRANSACTION_NAME
140-
transaction.set_tag("asgi.type", ty)
141-
142-
with hub.start_transaction(
143-
transaction, custom_sampling_context={"asgi_scope": scope}
144-
):
145-
# XXX: Would be cool to have correct span status, but we
146-
# would have to wrap send(). That is a bit hard to do with
147-
# the current abstraction over ASGI 2/3.
148-
try:
149-
return await callback()
150-
except Exception as exc:
151-
_capture_exception(hub, exc)
152-
raise exc from None
123+
with auto_session_tracking(hub, session_mode="request"):
124+
with hub:
125+
with hub.configure_scope() as sentry_scope:
126+
sentry_scope.clear_breadcrumbs()
127+
sentry_scope._name = "asgi"
128+
processor = partial(self.event_processor, asgi_scope=scope)
129+
sentry_scope.add_event_processor(processor)
130+
131+
ty = scope["type"]
132+
133+
if ty in ("http", "websocket"):
134+
transaction = Transaction.continue_from_headers(
135+
self._get_headers(scope),
136+
op="{}.server".format(ty),
137+
)
138+
else:
139+
transaction = Transaction(op="asgi.server")
140+
141+
transaction.name = _DEFAULT_TRANSACTION_NAME
142+
transaction.set_tag("asgi.type", ty)
143+
144+
with hub.start_transaction(
145+
transaction, custom_sampling_context={"asgi_scope": scope}
146+
):
147+
# XXX: Would be cool to have correct span status, but we
148+
# would have to wrap send(). That is a bit hard to do with
149+
# the current abstraction over ASGI 2/3.
150+
try:
151+
return await callback()
152+
except Exception as exc:
153+
_capture_exception(hub, exc)
154+
raise exc from None
153155
finally:
154156
_asgi_middleware_applied.set(False)
155157

tests/integrations/asgi/test_asgi.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
from collections import Counter
12
import sys
23

34
import pytest
45
from sentry_sdk import Hub, capture_message, last_event_id
6+
import sentry_sdk
57
from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
68
from starlette.applications import Starlette
79
from starlette.responses import PlainTextResponse
@@ -39,7 +41,7 @@ def test_sync_request_data(sentry_init, app, capture_events):
3941
events = capture_events()
4042

4143
client = TestClient(app)
42-
response = client.get("/sync-message?foo=bar", headers={"Foo": u"ä"})
44+
response = client.get("/sync-message?foo=bar", headers={"Foo": "ä"})
4345

4446
assert response.status_code == 200
4547

@@ -292,3 +294,41 @@ def test_x_real_ip(sentry_init, app, capture_events):
292294

293295
(event,) = events
294296
assert event["request"]["env"] == {"REMOTE_ADDR": "1.2.3.4"}
297+
298+
299+
def test_auto_session_tracking_with_aggregates(app, sentry_init, capture_envelopes):
300+
"""
301+
Test for correct session aggregates in auto session tracking.
302+
"""
303+
304+
@app.route("/dogs/are/great/")
305+
@app.route("/trigger/an/error/")
306+
def great_dogs_handler(request):
307+
if request["path"] != "/dogs/are/great/":
308+
1 / 0
309+
return PlainTextResponse("dogs are great")
310+
311+
sentry_init(traces_sample_rate=1.0)
312+
envelopes = capture_envelopes()
313+
314+
app = SentryAsgiMiddleware(app)
315+
client = TestClient(app, raise_server_exceptions=False)
316+
client.get("/dogs/are/great/")
317+
client.get("/dogs/are/great/")
318+
client.get("/trigger/an/error/")
319+
320+
sentry_sdk.flush()
321+
322+
count_item_types = Counter()
323+
for envelope in envelopes:
324+
count_item_types[envelope.items[0].type] += 1
325+
326+
assert count_item_types["transaction"] == 3
327+
assert count_item_types["event"] == 1
328+
assert count_item_types["sessions"] == 1
329+
assert len(envelopes) == 5
330+
331+
session_aggregates = envelopes[-1].items[0].payload.json["aggregates"]
332+
assert session_aggregates[0]["exited"] == 2
333+
assert session_aggregates[0]["crashed"] == 1
334+
assert len(session_aggregates) == 1

tests/integrations/wsgi/test_wsgi.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import sentry_sdk
55
from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
6+
from collections import Counter
67

78
try:
89
from unittest import mock # python 3.3 and above
@@ -219,7 +220,6 @@ def app(environ, start_response):
219220

220221
traces_sampler = mock.Mock(return_value=True)
221222
sentry_init(send_default_pii=True, traces_sampler=traces_sampler)
222-
223223
app = SentryWsgiMiddleware(app)
224224
envelopes = capture_envelopes()
225225

@@ -236,3 +236,46 @@ def app(environ, start_response):
236236
aggregates = sess_event["aggregates"]
237237
assert len(aggregates) == 1
238238
assert aggregates[0]["exited"] == 1
239+
240+
241+
def test_auto_session_tracking_with_aggregates(sentry_init, capture_envelopes):
242+
"""
243+
Test for correct session aggregates in auto session tracking.
244+
"""
245+
246+
def sample_app(environ, start_response):
247+
if environ["REQUEST_URI"] != "/dogs/are/great/":
248+
1 / 0
249+
250+
start_response("200 OK", [])
251+
return ["Go get the ball! Good dog!"]
252+
253+
traces_sampler = mock.Mock(return_value=True)
254+
sentry_init(send_default_pii=True, traces_sampler=traces_sampler)
255+
app = SentryWsgiMiddleware(sample_app)
256+
envelopes = capture_envelopes()
257+
assert len(envelopes) == 0
258+
259+
client = Client(app)
260+
client.get("/dogs/are/great/")
261+
client.get("/dogs/are/great/")
262+
try:
263+
client.get("/trigger/an/error/")
264+
except ZeroDivisionError:
265+
pass
266+
267+
sentry_sdk.flush()
268+
269+
count_item_types = Counter()
270+
for envelope in envelopes:
271+
count_item_types[envelope.items[0].type] += 1
272+
273+
assert count_item_types["transaction"] == 3
274+
assert count_item_types["event"] == 1
275+
assert count_item_types["sessions"] == 1
276+
assert len(envelopes) == 5
277+
278+
session_aggregates = envelopes[-1].items[0].payload.json["aggregates"]
279+
assert session_aggregates[0]["exited"] == 2
280+
assert session_aggregates[0]["crashed"] == 1
281+
assert len(session_aggregates) == 1

0 commit comments

Comments
 (0)