diff --git a/sentry_sdk/integrations/litestar.py b/sentry_sdk/integrations/litestar.py index 465424024c..6f05144e78 100644 --- a/sentry_sdk/integrations/litestar.py +++ b/sentry_sdk/integrations/litestar.py @@ -2,7 +2,7 @@ from copy import deepcopy import sentry_sdk -from sentry_sdk.consts import OP +from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.integrations import ( _DEFAULT_FAILED_REQUEST_STATUS_CODES, DidNotEnable, @@ -12,6 +12,7 @@ from sentry_sdk.integrations.logging import ignore_logger from sentry_sdk.scope import should_send_default_pii from sentry_sdk.tracing import SOURCE_FOR_STYLE, TransactionSource +from sentry_sdk.tracing_utils import has_span_streaming_enabled from sentry_sdk.utils import ( ensure_integration_enabled, event_from_exception, @@ -157,52 +158,103 @@ async def _create_span_call( receive: "Receive", send: "Send", ) -> None: - if sentry_sdk.get_client().get_integration(LitestarIntegration) is None: + client = sentry_sdk.get_client() + if client.get_integration(LitestarIntegration) is None: return await old_call(self, scope, receive, send) middleware_name = self.__class__.__name__ - with sentry_sdk.start_span( - op=OP.MIDDLEWARE_LITESTAR, - name=middleware_name, - origin=LitestarIntegration.origin, - ) as middleware_span: - middleware_span.set_tag("litestar.middleware_name", middleware_name) - - # Creating spans for the "receive" callback - async def _sentry_receive( - *args: "Any", **kwargs: "Any" - ) -> "Union[HTTPReceiveMessage, WebSocketReceiveMessage]": - if sentry_sdk.get_client().get_integration(LitestarIntegration) is None: - return await receive(*args, **kwargs) - with sentry_sdk.start_span( - op=OP.MIDDLEWARE_LITESTAR_RECEIVE, - name=getattr(receive, "__qualname__", str(receive)), - origin=LitestarIntegration.origin, - ) as span: - span.set_tag("litestar.middleware_name", middleware_name) - return await receive(*args, **kwargs) - - receive_name = getattr(receive, "__name__", str(receive)) - receive_patched = receive_name == "_sentry_receive" - new_receive = _sentry_receive if not receive_patched else receive - - # Creating spans for the "send" callback - async def _sentry_send(message: "Message") -> None: - if sentry_sdk.get_client().get_integration(LitestarIntegration) is None: - return await send(message) - with sentry_sdk.start_span( - op=OP.MIDDLEWARE_LITESTAR_SEND, - name=getattr(send, "__qualname__", str(send)), - origin=LitestarIntegration.origin, - ) as span: - span.set_tag("litestar.middleware_name", middleware_name) - return await send(message) - - send_name = getattr(send, "__name__", str(send)) - send_patched = send_name == "_sentry_send" - new_send = _sentry_send if not send_patched else send - - return await old_call(self, scope, new_receive, new_send) + if has_span_streaming_enabled(client.options): + with sentry_sdk.traces.start_span( + name=middleware_name, + attributes={ + "sentry.op": OP.MIDDLEWARE_LITESTAR, + "sentry.origin": LitestarIntegration.origin, + }, + ) as middleware_span: + middleware_span.set_attribute(SPANDATA.MIDDLEWARE_NAME, middleware_name) + + # Creating spans for the "receive" callback + async def _sentry_receive( + *args: "Any", **kwargs: "Any" + ) -> "Union[HTTPReceiveMessage, WebSocketReceiveMessage]": + if client.get_integration(LitestarIntegration) is None: + return await receive(*args, **kwargs) + with sentry_sdk.traces.start_span( + name=getattr(receive, "__qualname__", str(receive)), + attributes={ + "sentry.op": OP.MIDDLEWARE_LITESTAR_RECEIVE, + "sentry.origin": LitestarIntegration.origin, + }, + ) as span: + span.set_attribute(SPANDATA.MIDDLEWARE_NAME, middleware_name) + return await receive(*args, **kwargs) + + receive_name = getattr(receive, "__name__", str(receive)) + receive_patched = receive_name == "_sentry_receive" + new_receive = _sentry_receive if not receive_patched else receive + + # Creating spans for the "send" callback + async def _sentry_send(message: "Message") -> None: + if client.get_integration(LitestarIntegration) is None: + return await send(message) + with sentry_sdk.traces.start_span( + name=getattr(send, "__qualname__", str(send)), + attributes={ + "sentry.op": OP.MIDDLEWARE_LITESTAR_SEND, + "sentry.origin": LitestarIntegration.origin, + }, + ) as span: + span.set_attribute(SPANDATA.MIDDLEWARE_NAME, middleware_name) + return await send(message) + + send_name = getattr(send, "__name__", str(send)) + send_patched = send_name == "_sentry_send" + new_send = _sentry_send if not send_patched else send + + return await old_call(self, scope, new_receive, new_send) + else: + with sentry_sdk.start_span( + op=OP.MIDDLEWARE_LITESTAR, + name=middleware_name, + origin=LitestarIntegration.origin, + ) as middleware_span: + middleware_span.set_tag("litestar.middleware_name", middleware_name) + + # Creating spans for the "receive" callback + async def _sentry_receive( + *args: "Any", **kwargs: "Any" + ) -> "Union[HTTPReceiveMessage, WebSocketReceiveMessage]": + if client.get_integration(LitestarIntegration) is None: + return await receive(*args, **kwargs) + with sentry_sdk.start_span( + op=OP.MIDDLEWARE_LITESTAR_RECEIVE, + name=getattr(receive, "__qualname__", str(receive)), + origin=LitestarIntegration.origin, + ) as span: + span.set_tag("litestar.middleware_name", middleware_name) + return await receive(*args, **kwargs) + + receive_name = getattr(receive, "__name__", str(receive)) + receive_patched = receive_name == "_sentry_receive" + new_receive = _sentry_receive if not receive_patched else receive + + # Creating spans for the "send" callback + async def _sentry_send(message: "Message") -> None: + if client.get_integration(LitestarIntegration) is None: + return await send(message) + with sentry_sdk.start_span( + op=OP.MIDDLEWARE_LITESTAR_SEND, + name=getattr(send, "__qualname__", str(send)), + origin=LitestarIntegration.origin, + ) as span: + span.set_tag("litestar.middleware_name", middleware_name) + return await send(message) + + send_name = getattr(send, "__name__", str(send)) + send_patched = send_name == "_sentry_send" + new_send = _sentry_send if not send_patched else send + + return await old_call(self, scope, new_receive, new_send) not_yet_patched = old_call.__name__ not in ["_create_span_call"] diff --git a/tests/integrations/litestar/test_litestar.py b/tests/integrations/litestar/test_litestar.py index 8fe6383d98..057bd23dcb 100644 --- a/tests/integrations/litestar/test_litestar.py +++ b/tests/integrations/litestar/test_litestar.py @@ -13,8 +13,10 @@ from litestar.middleware.session.server_side import ServerSideSessionConfig from litestar.testing import TestClient +import sentry_sdk from sentry_sdk import capture_message from sentry_sdk.integrations.litestar import LitestarIntegration +from tests.conftest import ApproxDict from tests.integrations.conftest import parametrize_test_configurable_status_codes @@ -87,39 +89,66 @@ async def message_with_id() -> "dict[str, Any]": ), ], ) +@pytest.mark.parametrize("span_streaming", [True, False]) def test_catch_exceptions( sentry_init, capture_exceptions, capture_events, + capture_items, test_url, expected_error, expected_message, expected_tx_name, + span_streaming, ): - sentry_init(integrations=[LitestarIntegration()]) + sentry_init( + integrations=[LitestarIntegration()], + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, + ) litestar_app = litestar_app_factory() + client = TestClient(litestar_app) exceptions = capture_exceptions() - events = capture_events() + if span_streaming: + items = capture_items("event") - client = TestClient(litestar_app) - try: - client.get(test_url) - except Exception: - pass + try: + client.get(test_url) + except Exception: + pass + + (exc,) = exceptions + assert isinstance(exc, expected_error) + assert str(exc) == expected_message - (exc,) = exceptions - assert isinstance(exc, expected_error) - assert str(exc) == expected_message + (event,) = (item.payload for item in items) + else: + events = capture_events() - (event,) = events + try: + client.get(test_url) + except Exception: + pass + + (exc,) = exceptions + assert isinstance(exc, expected_error) + assert str(exc) == expected_message + + (event,) = events assert expected_tx_name in event["transaction"] assert event["exception"]["values"][0]["mechanism"]["type"] == "litestar" -def test_middleware_spans(sentry_init, capture_events): +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_middleware_spans( + sentry_init, + capture_events, + capture_items, + span_streaming, +): sentry_init( traces_sample_rate=1.0, integrations=[LitestarIntegration()], + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) logging_config = LoggingMiddlewareConfig() @@ -133,32 +162,61 @@ def test_middleware_spans(sentry_init, capture_events): rate_limit_config.middleware, ] ) - events = capture_events() - client = TestClient( litestar_app, raise_server_exceptions=False, base_url="http://testserver.local" ) - client.get("/message") + if span_streaming: + items = capture_items("span") - (_, transaction_event) = events + client.get("/message") - expected = {"SessionMiddleware", "LoggingMiddleware", "RateLimitMiddleware"} - found = set() + sentry_sdk.flush() + spans = [item.payload for item in items] - litestar_spans = ( - span - for span in transaction_event["spans"] - if span["op"] == "middleware.litestar" - ) + expected = {"SessionMiddleware", "LoggingMiddleware", "RateLimitMiddleware"} + found = set() + + litestar_spans = ( + span + for span in spans + if span["attributes"]["sentry.op"] == "middleware.litestar" + ) + + for span in litestar_spans: + assert span["name"] in expected + assert span["name"] not in found + found.add(span["name"]) + assert span["name"] == span["attributes"]["middleware.name"] + else: + events = capture_events() - for span in litestar_spans: - assert span["description"] in expected - assert span["description"] not in found - found.add(span["description"]) - assert span["description"] == span["tags"]["litestar.middleware_name"] + client.get("/message") + (_, transaction_event) = events -def test_middleware_callback_spans(sentry_init, capture_events): + expected = {"SessionMiddleware", "LoggingMiddleware", "RateLimitMiddleware"} + found = set() + + litestar_spans = ( + span + for span in transaction_event["spans"] + if span["op"] == "middleware.litestar" + ) + + for span in litestar_spans: + assert span["description"] in expected + assert span["description"] not in found + found.add(span["description"]) + assert span["description"] == span["tags"]["litestar.middleware_name"] + + +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_middleware_callback_spans( + sentry_init, + capture_events, + capture_items, + span_streaming, +): class SampleMiddleware(AbstractMiddleware): async def __call__(self, scope, receive, send) -> None: async def do_stuff(message): @@ -172,45 +230,99 @@ async def do_stuff(message): sentry_init( traces_sample_rate=1.0, integrations=[LitestarIntegration()], + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - litestar_app = litestar_app_factory(middleware=[SampleMiddleware]) - events = capture_events() + litestar_app = litestar_app_factory(middleware=[SampleMiddleware]) client = TestClient(litestar_app, raise_server_exceptions=False) - client.get("/message") + if span_streaming: + items = capture_items("span") + + client.get("/message") + + spans = [item.payload for item in items] + + expected_litestar_spans = [ + { + "name": "SampleMiddleware", + "attributes": ApproxDict( + { + "middleware.name": "SampleMiddleware", + "sentry.op": "middleware.litestar", + }, + ), + }, + { + "name": "SentryAsgiMiddleware._run_app.._sentry_wrapped_send", + "attributes": ApproxDict( + { + "middleware.name": "SampleMiddleware", + "sentry.op": "middleware.litestar.send", + } + ), + }, + { + "name": "SentryAsgiMiddleware._run_app.._sentry_wrapped_send", + "attributes": ApproxDict( + { + "middleware.name": "SampleMiddleware", + "sentry.op": "middleware.litestar.send", + } + ), + }, + ] - (_, transaction_events) = events - - expected_litestar_spans = [ - { - "op": "middleware.litestar", - "description": "SampleMiddleware", - "tags": {"litestar.middleware_name": "SampleMiddleware"}, - }, - { - "op": "middleware.litestar.send", - "description": "SentryAsgiMiddleware._run_app.._sentry_wrapped_send", - "tags": {"litestar.middleware_name": "SampleMiddleware"}, - }, - { - "op": "middleware.litestar.send", - "description": "SentryAsgiMiddleware._run_app.._sentry_wrapped_send", - "tags": {"litestar.middleware_name": "SampleMiddleware"}, - }, - ] - - def is_matching_span(expected_span, actual_span): - return ( - expected_span["op"] == actual_span["op"] - and expected_span["description"] == actual_span["description"] - and expected_span["tags"] == actual_span["tags"] + def is_matching_span(expected_span, actual_span): + return ( + expected_span["name"] == actual_span["name"] + and expected_span["attributes"] == actual_span["attributes"] + ) + + sentry_sdk.flush() + spans = [item.payload for item in items] + actual_litestar_spans = list( + span + for span in spans + if "middleware.litestar" in span["attributes"].get("sentry.op") + ) + else: + events = capture_events() + + client.get("/message") + + (_, transaction_events) = events + + expected_litestar_spans = [ + { + "op": "middleware.litestar", + "description": "SampleMiddleware", + "tags": {"litestar.middleware_name": "SampleMiddleware"}, + }, + { + "op": "middleware.litestar.send", + "description": "SentryAsgiMiddleware._run_app.._sentry_wrapped_send", + "tags": {"litestar.middleware_name": "SampleMiddleware"}, + }, + { + "op": "middleware.litestar.send", + "description": "SentryAsgiMiddleware._run_app.._sentry_wrapped_send", + "tags": {"litestar.middleware_name": "SampleMiddleware"}, + }, + ] + + def is_matching_span(expected_span, actual_span): + return ( + expected_span["op"] == actual_span["op"] + and expected_span["description"] == actual_span["description"] + and expected_span["tags"] == actual_span["tags"] + ) + + actual_litestar_spans = list( + span + for span in transaction_events["spans"] + if "middleware.litestar" in span["op"] ) - actual_litestar_spans = list( - span - for span in transaction_events["spans"] - if "middleware.litestar" in span["op"] - ) assert len(actual_litestar_spans) == 3 for expected_span in expected_litestar_spans: @@ -220,7 +332,8 @@ def is_matching_span(expected_span, actual_span): ) -def test_middleware_receive_send(sentry_init, capture_events): +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_middleware_receive_send(sentry_init, capture_items, span_streaming): class SampleReceiveSendMiddleware(AbstractMiddleware): async def __call__(self, scope, receive, send): message = await receive() @@ -235,6 +348,7 @@ async def __call__(self, scope, receive, send): sentry_init( traces_sample_rate=1.0, integrations=[LitestarIntegration()], + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) litestar_app = litestar_app_factory(middleware=[SampleReceiveSendMiddleware]) @@ -243,7 +357,13 @@ async def __call__(self, scope, receive, send): client.get("/message") -def test_middleware_partial_receive_send(sentry_init, capture_events): +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_middleware_partial_receive_send( + sentry_init, + capture_events, + capture_items, + span_streaming, +): class SamplePartialReceiveSendMiddleware(AbstractMiddleware): async def __call__(self, scope, receive, send): message = await receive() @@ -267,46 +387,106 @@ async def my_send(*args, **kwargs): sentry_init( traces_sample_rate=1.0, integrations=[LitestarIntegration()], + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) - litestar_app = litestar_app_factory(middleware=[SamplePartialReceiveSendMiddleware]) - events = capture_events() + litestar_app = litestar_app_factory(middleware=[SamplePartialReceiveSendMiddleware]) client = TestClient(litestar_app, raise_server_exceptions=False) - # See SamplePartialReceiveSendMiddleware.__call__ above for assertions of correct behavior - client.get("/message") - (_, transaction_events) = events - - expected_litestar_spans = [ - { - "op": "middleware.litestar", - "description": "SamplePartialReceiveSendMiddleware", - "tags": {"litestar.middleware_name": "SamplePartialReceiveSendMiddleware"}, - }, - { - "op": "middleware.litestar.receive", - "description": "TestClientTransport.create_receive..receive", - "tags": {"litestar.middleware_name": "SamplePartialReceiveSendMiddleware"}, - }, - { - "op": "middleware.litestar.send", - "description": "SentryAsgiMiddleware._run_app.._sentry_wrapped_send", - "tags": {"litestar.middleware_name": "SamplePartialReceiveSendMiddleware"}, - }, - ] - - def is_matching_span(expected_span, actual_span): - return ( - expected_span["op"] == actual_span["op"] - and actual_span["description"].startswith(expected_span["description"]) - and expected_span["tags"] == actual_span["tags"] + if span_streaming: + items = capture_items("span") + + # See SamplePartialReceiveSendMiddleware.__call__ above for assertions of correct behavior + client.get("/message") + + sentry_sdk.flush() + spans = [item.payload for item in items] + + expected_litestar_spans = [ + { + "name": "SamplePartialReceiveSendMiddleware", + "attributes": ApproxDict( + { + "middleware.name": "SamplePartialReceiveSendMiddleware", + "sentry.op": "middleware.litestar", + } + ), + }, + { + "name": "TestClientTransport.create_receive..receive", + "attributes": ApproxDict( + { + "middleware.name": "SamplePartialReceiveSendMiddleware", + "sentry.op": "middleware.litestar.receive", + } + ), + }, + { + "name": "SentryAsgiMiddleware._run_app.._sentry_wrapped_send", + "attributes": ApproxDict( + { + "middleware.name": "SamplePartialReceiveSendMiddleware", + "sentry.op": "middleware.litestar.send", + } + ), + }, + ] + + def is_matching_span(expected_span, actual_span): + return ( + actual_span["name"].startswith(expected_span["name"]) + and expected_span["attributes"] == actual_span["attributes"] + ) + + actual_litestar_spans = list( + span + for span in spans + if "middleware.litestar" in span["attributes"].get("sentry.op") ) + else: + events = capture_events() + + # See SamplePartialReceiveSendMiddleware.__call__ above for assertions of correct behavior + client.get("/message") + + (_, transaction_events) = events + + expected_litestar_spans = [ + { + "op": "middleware.litestar", + "description": "SamplePartialReceiveSendMiddleware", + "tags": { + "litestar.middleware_name": "SamplePartialReceiveSendMiddleware" + }, + }, + { + "op": "middleware.litestar.receive", + "description": "TestClientTransport.create_receive..receive", + "tags": { + "litestar.middleware_name": "SamplePartialReceiveSendMiddleware" + }, + }, + { + "op": "middleware.litestar.send", + "description": "SentryAsgiMiddleware._run_app.._sentry_wrapped_send", + "tags": { + "litestar.middleware_name": "SamplePartialReceiveSendMiddleware" + }, + }, + ] - actual_litestar_spans = list( - span - for span in transaction_events["spans"] - if "middleware.litestar" in span["op"] - ) + def is_matching_span(expected_span, actual_span): + return ( + expected_span["op"] == actual_span["op"] + and actual_span["description"].startswith(expected_span["description"]) + and expected_span["tags"] == actual_span["tags"] + ) + + actual_litestar_spans = list( + span + for span in transaction_events["spans"] + if "middleware.litestar" in span["op"] + ) assert len(actual_litestar_spans) == 3 for expected_span in expected_litestar_spans: @@ -316,10 +496,17 @@ def is_matching_span(expected_span, actual_span): ) -def test_span_origin(sentry_init, capture_events): +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_span_origin( + sentry_init, + capture_events, + capture_items, + span_streaming, +): sentry_init( integrations=[LitestarIntegration()], traces_sample_rate=1.0, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) logging_config = LoggingMiddlewareConfig() @@ -333,18 +520,31 @@ def test_span_origin(sentry_init, capture_events): rate_limit_config.middleware, ] ) - events = capture_events() - client = TestClient( litestar_app, raise_server_exceptions=False, base_url="http://testserver.local" ) - client.get("/message") + if span_streaming: + items = capture_items("span") + + client.get("/message") - (_, event) = events + sentry_sdk.flush() + spans = [item.payload for item in items] - assert event["contexts"]["trace"]["origin"] == "auto.http.litestar" - for span in event["spans"]: - assert span["origin"] == "auto.http.litestar" + for span in spans: + if span["attributes"]["sentry.origin"] == "auto.http.httpx": + continue + assert span["attributes"]["sentry.origin"] == "auto.http.litestar" + else: + events = capture_events() + + client.get("/message") + + (_, event) = events + + assert event["contexts"]["trace"]["origin"] == "auto.http.litestar" + for span in event["spans"]: + assert span["origin"] == "auto.http.litestar" @pytest.mark.parametrize( @@ -358,8 +558,14 @@ def test_span_origin(sentry_init, capture_events): "send_default_pii=False", ], ) +@pytest.mark.parametrize("span_streaming", [True, False]) def test_litestar_scope_user_on_exception_event( - sentry_init, capture_exceptions, capture_events, is_send_default_pii + sentry_init, + capture_exceptions, + capture_events, + capture_items, + is_send_default_pii, + span_streaming, ): class TestUserMiddleware(AbstractMiddleware): async def __call__(self, scope, receive, send): @@ -371,22 +577,37 @@ async def __call__(self, scope, receive, send): await self.app(scope, receive, send) sentry_init( - integrations=[LitestarIntegration()], send_default_pii=is_send_default_pii + integrations=[LitestarIntegration()], + send_default_pii=is_send_default_pii, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, ) + litestar_app = litestar_app_factory(middleware=[TestUserMiddleware]) + client = TestClient(litestar_app) exceptions = capture_exceptions() - events = capture_events() + if span_streaming: + items = capture_items("event") - # This request intentionally raises an exception - client = TestClient(litestar_app) - try: - client.get("/some_url") - except Exception: - pass + # This request intentionally raises an exception + try: + client.get("/some_url") + except Exception: + pass - assert len(exceptions) == 1 - assert len(events) == 1 - (event,) = events + assert len(exceptions) == 1 + (event,) = (item.payload for item in items) + else: + events = capture_events() + + # This request intentionally raises an exception + try: + client.get("/some_url") + except Exception: + pass + + assert len(exceptions) == 1 + assert len(events) == 1 + (event,) = events if is_send_default_pii: assert "user" in event @@ -400,21 +621,25 @@ async def __call__(self, scope, receive, send): @parametrize_test_configurable_status_codes +@pytest.mark.parametrize("span_streaming", [True, False]) def test_configurable_status_codes_handler( sentry_init, capture_events, + capture_items, failed_request_status_codes, status_code, expected_error, + span_streaming, ): integration_kwargs = ( {"failed_request_status_codes": failed_request_status_codes} if failed_request_status_codes is not None else {} ) - sentry_init(integrations=[LitestarIntegration(**integration_kwargs)]) - - events = capture_events() + sentry_init( + integrations=[LitestarIntegration(**integration_kwargs)], + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, + ) @get("/error") async def error() -> None: @@ -422,27 +647,42 @@ async def error() -> None: app = Litestar([error]) client = TestClient(app) - client.get("/error") + + if span_streaming: + items = capture_items("event") + + client.get("/error") + + events = [item.payload for item in items] + else: + events = capture_events() + + client.get("/error") assert len(events) == int(expected_error) @parametrize_test_configurable_status_codes +@pytest.mark.parametrize("span_streaming", [True, False]) def test_configurable_status_codes_middleware( sentry_init, capture_events, + capture_items, failed_request_status_codes, status_code, expected_error, + span_streaming, ): integration_kwargs = ( {"failed_request_status_codes": failed_request_status_codes} if failed_request_status_codes is not None else {} ) - sentry_init(integrations=[LitestarIntegration(**integration_kwargs)]) - events = capture_events() + sentry_init( + integrations=[LitestarIntegration(**integration_kwargs)], + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, + ) def create_raising_middleware(app): async def raising_middleware(scope, receive, send): @@ -455,18 +695,32 @@ async def error() -> None: ... app = Litestar([error], middleware=[create_raising_middleware]) client = TestClient(app) - client.get("/error") + + if span_streaming: + items = capture_items("event") + + client.get("/error") + + events = [item.payload for item in items] + else: + events = capture_events() + + client.get("/error") assert len(events) == int(expected_error) +@pytest.mark.parametrize("span_streaming", [True, False]) def test_catch_non_http_exceptions_in_middleware( sentry_init, capture_events, + capture_items, + span_streaming, ): - sentry_init(integrations=[LitestarIntegration()]) - - events = capture_events() + sentry_init( + integrations=[LitestarIntegration()], + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, + ) def create_raising_middleware(app): async def raising_middleware(scope, receive, send): @@ -480,10 +734,22 @@ async def error() -> None: ... app = Litestar([error], middleware=[create_raising_middleware]) client = TestClient(app) - try: - client.get("/error") - except RuntimeError: - pass + if span_streaming: + items = capture_items("event") + + try: + client.get("/error") + except RuntimeError: + pass + + events = [item.payload for item in items] + else: + events = capture_events() + + try: + client.get("/error") + except RuntimeError: + pass assert len(events) == 1 event_exception = events[0]["exception"]["values"][0]