Set response status code in transaction "response" context. (#2312) · gitcommit90/sentry-python@46c24ea · GitHub
Skip to content

Commit 46c24ea

Browse files
authored
Set response status code in transaction "response" context. (getsentry#2312)
Make sure that the HTTP response status code be set in the transactions "response" context. This works in WSGI (was already calling set_http_status.) Also added this to ASGI projects. Fixes getsentry#2289
1 parent 838368c commit 46c24ea

7 files changed

Lines changed: 217 additions & 39 deletions

File tree

sentry_sdk/integrations/asgi.py

Lines changed: 30 additions & 9 deletions

sentry_sdk/tracing.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,11 @@ def set_context(self, key, value):
663663
# type: (str, Any) -> None
664664
self._contexts[key] = value
665665

666+
def set_http_status(self, http_status):
667+
# type: (int) -> None
668+
super(Transaction, self).set_http_status(http_status)
669+
self.set_context("response", {"status_code": http_status})
670+
666671
def to_json(self):
667672
# type: () -> Dict[str, Any]
668673
rv = super(Transaction, self).to_json()

tests/integrations/asgi/test_asgi.py

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,11 @@ async def app(scope, receive, send):
4848

4949
@pytest.fixture
5050
def asgi3_app_with_error():
51+
async def send_with_error(event):
52+
1 / 0
53+
5154
async def app(scope, receive, send):
52-
await send(
55+
await send_with_error(
5356
{
5457
"type": "http.response.start",
5558
"status": 200,
@@ -58,10 +61,7 @@ async def app(scope, receive, send):
5861
],
5962
}
6063
)
61-
62-
1 / 0
63-
64-
await send(
64+
await send_with_error(
6565
{
6666
"type": "http.response.body",
6767
"body": b"Hello, world!",
@@ -167,9 +167,9 @@ async def test_capture_transaction_with_error(
167167
sentry_init(send_default_pii=True, traces_sample_rate=1.0)
168168
app = SentryAsgiMiddleware(asgi3_app_with_error)
169169

170+
events = capture_events()
170171
with pytest.raises(ZeroDivisionError):
171172
async with TestClient(app) as client:
172-
events = capture_events()
173173
await client.get("/")
174174

175175
(error_event, transaction_event) = events
@@ -395,38 +395,35 @@ async def test_auto_session_tracking_with_aggregates(
395395
(
396396
"/message",
397397
"endpoint",
398-
"tests.integrations.asgi.test_asgi.asgi3_app_with_error.<locals>.app",
398+
"tests.integrations.asgi.test_asgi.asgi3_app.<locals>.app",
399399
"component",
400400
),
401401
],
402402
)
403403
@pytest.mark.asyncio
404404
async def test_transaction_style(
405405
sentry_init,
406-
asgi3_app_with_error,
406+
asgi3_app,
407407
capture_events,
408408
url,
409409
transaction_style,
410410
expected_transaction,
411411
expected_source,
412412
):
413413
sentry_init(send_default_pii=True, traces_sample_rate=1.0)
414-
app = SentryAsgiMiddleware(
415-
asgi3_app_with_error, transaction_style=transaction_style
416-
)
414+
app = SentryAsgiMiddleware(asgi3_app, transaction_style=transaction_style)
417415

418416
scope = {
419-
"endpoint": asgi3_app_with_error,
417+
"endpoint": asgi3_app,
420418
"route": url,
421419
"client": ("127.0.0.1", 60457),
422420
}
423421

424-
with pytest.raises(ZeroDivisionError):
425-
async with TestClient(app, scope=scope) as client:
426-
events = capture_events()
427-
await client.get(url)
422+
async with TestClient(app, scope=scope) as client:
423+
events = capture_events()
424+
await client.get(url)
428425

429-
(_, transaction_event) = events
426+
(transaction_event,) = events
430427

431428
assert transaction_event["transaction"] == expected_transaction
432429
assert transaction_event["transaction_info"] == {"source": expected_source}

tests/integrations/fastapi/test_fastapi.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@
2222
def fastapi_app_factory():
2323
app = FastAPI()
2424

25+
@app.get("/error")
26+
async def _error():
27+
capture_message("Hi")
28+
1 / 0
29+
return {"message": "Hi"}
30+
2531
@app.get("/message")
2632
async def _message():
2733
capture_message("Hi")
@@ -218,3 +224,101 @@ async def _error(request: Request):
218224
event = events[0]
219225
assert event["request"]["data"] == {"password": "[Filtered]"}
220226
assert event["request"]["headers"]["authorization"] == "[Filtered]"
227+
228+
229+
@pytest.mark.asyncio
230+
def test_response_status_code_ok_in_transaction_context(sentry_init, capture_envelopes):
231+
"""
232+
Tests that the response status code is added to the transaction "response" context.
233+
"""
234+
sentry_init(
235+
integrations=[StarletteIntegration(), FastApiIntegration()],
236+
traces_sample_rate=1.0,
237+
release="demo-release",
238+
)
239+
240+
envelopes = capture_envelopes()
241+
242+
app = fastapi_app_factory()
243+
244+
client = TestClient(app)
245+
client.get("/message")
246+
247+
(_, transaction_envelope) = envelopes
248+
transaction = transaction_envelope.get_transaction_event()
249+
250+
assert transaction["type"] == "transaction"
251+
assert len(transaction["contexts"]) > 0
252+
assert (
253+
"response" in transaction["contexts"].keys()
254+
), "Response context not found in transaction"
255+
assert transaction["contexts"]["response"]["status_code"] == 200
256+
257+
258+
@pytest.mark.asyncio
259+
def test_response_status_code_error_in_transaction_context(
260+
sentry_init,
261+
capture_envelopes,
262+
):
263+
"""
264+
Tests that the response status code is added to the transaction "response" context.
265+
"""
266+
sentry_init(
267+
integrations=[StarletteIntegration(), FastApiIntegration()],
268+
traces_sample_rate=1.0,
269+
release="demo-release",
270+
)
271+
272+
envelopes = capture_envelopes()
273+
274+
app = fastapi_app_factory()
275+
276+
client = TestClient(app)
277+
with pytest.raises(ZeroDivisionError):
278+
client.get("/error")
279+
280+
(
281+
_,
282+
_,
283+
transaction_envelope,
284+
) = envelopes
285+
transaction = transaction_envelope.get_transaction_event()
286+
287+
assert transaction["type"] == "transaction"
288+
assert len(transaction["contexts"]) > 0
289+
assert (
290+
"response" in transaction["contexts"].keys()
291+
), "Response context not found in transaction"
292+
assert transaction["contexts"]["response"]["status_code"] == 500
293+
294+
295+
@pytest.mark.asyncio
296+
def test_response_status_code_not_found_in_transaction_context(
297+
sentry_init,
298+
capture_envelopes,
299+
):
300+
"""
301+
Tests that the response status code is added to the transaction "response" context.
302+
"""
303+
sentry_init(
304+
integrations=[StarletteIntegration(), FastApiIntegration()],
305+
traces_sample_rate=1.0,
306+
release="demo-release",
307+
)
308+
309+
envelopes = capture_envelopes()
310+
311+
app = fastapi_app_factory()
312+
313+
client = TestClient(app)
314+
client.get("/non-existing-route-123")
315+
316+
(transaction_envelope,) = envelopes
317+
transaction = transaction_envelope.get_transaction_event()
318+
319+
assert transaction["type"] == "transaction"
320+
assert len(transaction["contexts"]) > 0
321+
assert (
322+
"response" in transaction["contexts"].keys()
323+
), "Response context not found in transaction"
324+
assert transaction["contexts"]["response"]["status_code"] == 404

tests/integrations/flask/test_flask.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -912,3 +912,61 @@ def error():
912912
assert (
913913
event["contexts"]["replay"]["replay_id"] == "12312012123120121231201212312012"
914914
)
915+
916+
917+
def test_response_status_code_ok_in_transaction_context(
918+
sentry_init, capture_envelopes, app
919+
):
920+
"""
921+
Tests that the response status code is added to the transaction context.
922+
This also works for when there is an Exception during the request, but somehow the test flask app doesn't seem to trigger that.
923+
"""
924+
sentry_init(
925+
integrations=[flask_sentry.FlaskIntegration()],
926+
traces_sample_rate=1.0,
927+
release="demo-release",
928+
)
929+
930+
envelopes = capture_envelopes()
931+
932+
client = app.test_client()
933+
client.get("/message")
934+
935+
Hub.current.client.flush()
936+
937+
(_, transaction_envelope, _) = envelopes
938+
transaction = transaction_envelope.get_transaction_event()
939+
940+
assert transaction["type"] == "transaction"
941+
assert len(transaction["contexts"]) > 0
942+
assert (
943+
"response" in transaction["contexts"].keys()
944+
), "Response context not found in transaction"
945+
assert transaction["contexts"]["response"]["status_code"] == 200
946+
947+
948+
def test_response_status_code_not_found_in_transaction_context(
949+
sentry_init, capture_envelopes, app
950+
):
951+
sentry_init(
952+
integrations=[flask_sentry.FlaskIntegration()],
953+
traces_sample_rate=1.0,
954+
release="demo-release",
955+
)
956+
957+
envelopes = capture_envelopes()
958+
959+
client = app.test_client()
960+
client.get("/not-existing-route")
961+
962+
Hub.current.client.flush()
963+
964+
(transaction_envelope, _) = envelopes
965+
transaction = transaction_envelope.get_transaction_event()
966+
967+
assert transaction["type"] == "transaction"
968+
assert len(transaction["contexts"]) > 0
969+
assert (
970+
"response" in transaction["contexts"].keys()
971+
), "Response context not found in transaction"
972+
assert transaction["contexts"]["response"]["status_code"] == 404

tests/integrations/starlette/test_starlette.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -700,9 +700,7 @@ def test_middleware_callback_spans(sentry_init, capture_events):
700700
},
701701
{
702702
"op": "middleware.starlette.send",
703-
"description": "_ASGIAdapter.send.<locals>.send"
704-
if STARLETTE_VERSION < (0, 21)
705-
else "_TestClientTransport.handle_request.<locals>.send",
703+
"description": "SentryAsgiMiddleware._run_app.<locals>._sentry_wrapped_send",
706704
"tags": {"starlette.middleware_name": "ServerErrorMiddleware"},
707705
},
708706
{
@@ -717,9 +715,7 @@ def test_middleware_callback_spans(sentry_init, capture_events):
717715
},
718716
{
719717
"op": "middleware.starlette.send",
720-
"description": "_ASGIAdapter.send.<locals>.send"
721-
if STARLETTE_VERSION < (0, 21)
722-
else "_TestClientTransport.handle_request.<locals>.send",
718+
"description": "SentryAsgiMiddleware._run_app.<locals>._sentry_wrapped_send",
723719
"tags": {"starlette.middleware_name": "ServerErrorMiddleware"},
724720
},
725721
]
@@ -793,9 +789,7 @@ def test_middleware_partial_receive_send(sentry_init, capture_events):
793789
},
794790
{
795791
"op": "middleware.starlette.send",
796-
"description": "_ASGIAdapter.send.<locals>.send"
797-
if STARLETTE_VERSION < (0, 21)
798-
else "_TestClientTransport.handle_request.<locals>.send",
792+
"description": "SentryAsgiMiddleware._run_app.<locals>._sentry_wrapped_send",
799793
"tags": {"starlette.middleware_name": "ServerErrorMiddleware"},
800794
},
801795
{

tests/integrations/starlite/test_starlite.py

Lines changed: 3 additions & 4 deletions

0 commit comments

Comments
 (0)