From 255fcee874189e21bd95e75100bf8d51503c0fb2 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 16 Jun 2026 10:03:44 +0200 Subject: [PATCH 01/20] fix(asgi): Stop duplicating root_path in URLs --- sentry_sdk/integrations/_asgi_common.py | 7 +++++- sentry_sdk/integrations/asgi.py | 31 +++++++++++++++++++++---- sentry_sdk/integrations/starlette.py | 6 +++-- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/sentry_sdk/integrations/_asgi_common.py b/sentry_sdk/integrations/_asgi_common.py index bb44896a04..8a8f471501 100644 --- a/sentry_sdk/integrations/_asgi_common.py +++ b/sentry_sdk/integrations/_asgi_common.py @@ -32,6 +32,7 @@ def _get_url( asgi_scope: "Dict[str, Any]", default_scheme: "Literal['ws', 'http']", host: "Optional[Union[AnnotatedValue, str]]", + path_includes_root_path: "bool" = True, ) -> str: """ Extract URL from the ASGI scope, without also including the querystring. @@ -39,7 +40,11 @@ def _get_url( scheme = asgi_scope.get("scheme", default_scheme) server = asgi_scope.get("server", None) - path = asgi_scope.get("root_path", "") + asgi_scope.get("path", "") + path = ( + asgi_scope.get("path", "") + if path_includes_root_path + else asgi_scope.get("root_path", "") + asgi_scope.get("path", "") + ) if host: return "%s://%s%s" % (scheme, host, path) diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index f0470e33fc..baf9a1afbd 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -105,6 +105,7 @@ class SentryAsgiMiddleware: "mechanism_type", "span_origin", "http_methods_to_capture", + "path_includes_root_path", ) def __init__( @@ -116,6 +117,7 @@ def __init__( span_origin: str = "manual", http_methods_to_capture: "Tuple[str, ...]" = DEFAULT_HTTP_METHODS_TO_CAPTURE, asgi_version: "Optional[int]" = None, + path_includes_root_path: bool = True, ) -> None: """ Instrument an ASGI application with Sentry. Provides HTTP/websocket @@ -152,6 +154,7 @@ def __init__( self.span_origin = span_origin self.app = app self.http_methods_to_capture = http_methods_to_capture + self.path_includes_root_path = path_includes_root_path if asgi_version is None: if _looks_like_asgi3(app): @@ -447,7 +450,12 @@ def _get_transaction_name_and_source( if endpoint: name = transaction_from_function(endpoint) or "" else: - name = _get_url(asgi_scope, "http" if ty == "http" else "ws", host=None) + name = _get_url( + asgi_scope, + "http" if ty == "http" else "ws", + host=None, + path_includes_root_path=self.path_includes_root_path, + ) source = TransactionSource.URL elif transaction_style == "url": @@ -459,7 +467,12 @@ def _get_transaction_name_and_source( if path is not None: name = path else: - name = _get_url(asgi_scope, "http" if ty == "http" else "ws", host=None) + name = _get_url( + asgi_scope, + "http" if ty == "http" else "ws", + host=None, + path_includes_root_path=self.path_includes_root_path, + ) source = TransactionSource.URL if name is None: @@ -484,7 +497,12 @@ def _get_segment_name_and_source( if endpoint: name = qualname_from_function(endpoint) or "" else: - name = _get_url(asgi_scope, "http" if ty == "http" else "ws", host=None) + name = _get_url( + asgi_scope, + "http" if ty == "http" else "ws", + host=None, + path_includes_root_path=self.path_includes_root_path, + ) source = SegmentSource.URL.value elif segment_style == "url": @@ -496,7 +514,12 @@ def _get_segment_name_and_source( if path is not None: name = path else: - name = _get_url(asgi_scope, "http" if ty == "http" else "ws", host=None) + name = _get_url( + asgi_scope, + "http" if ty == "http" else "ws", + host=None, + path_includes_root_path=self.path_includes_root_path, + ) source = SegmentSource.URL.value if name is None: diff --git a/sentry_sdk/integrations/starlette.py b/sentry_sdk/integrations/starlette.py index 1482efc25b..526c0abd0f 100644 --- a/sentry_sdk/integrations/starlette.py +++ b/sentry_sdk/integrations/starlette.py @@ -143,7 +143,8 @@ def setup_once() -> None: ) patch_middlewares() - patch_asgi_app() + path_includes_root_path = version >= (0, 33) + patch_asgi_app(path_includes_root_path=path_includes_root_path) patch_request_response() if version >= (0, 24): @@ -427,7 +428,7 @@ def _sentry_middleware_init( Middleware.__init__ = _sentry_middleware_init -def patch_asgi_app() -> None: +def patch_asgi_app(path_includes_root_path: "bool") -> None: """ Instrument Starlette ASGI app using the SentryAsgiMiddleware. """ @@ -451,6 +452,7 @@ async def _sentry_patched_asgi_app( else DEFAULT_HTTP_METHODS_TO_CAPTURE ), asgi_version=3, + path_includes_root_path=path_includes_root_path, ) return await middleware(scope, receive, send) From 89023f4f72482adc79ac75d10774f6349c9bc721 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 19 Jun 2026 10:10:42 +0200 Subject: [PATCH 02/20] starlette and fastapi tests --- sentry_sdk/integrations/_asgi_common.py | 18 ++++++-- sentry_sdk/integrations/asgi.py | 9 +++- sentry_sdk/integrations/starlette.py | 1 + tests/integrations/fastapi/test_fastapi.py | 42 +++++++++++++++++++ .../integrations/starlette/test_starlette.py | 42 +++++++++++++++++++ 5 files changed, 106 insertions(+), 6 deletions(-) diff --git a/sentry_sdk/integrations/_asgi_common.py b/sentry_sdk/integrations/_asgi_common.py index 8a8f471501..9f0b64216a 100644 --- a/sentry_sdk/integrations/_asgi_common.py +++ b/sentry_sdk/integrations/_asgi_common.py @@ -86,7 +86,9 @@ def _get_ip(asgi_scope: "Any") -> str: return asgi_scope.get("client")[0] -def _get_request_data(asgi_scope: "Any") -> "Dict[str, Any]": +def _get_request_data( + asgi_scope: "Any", path_includes_root_path: "bool" = True +) -> "Dict[str, Any]": """ Returns data related to the HTTP request from the ASGI scope. """ @@ -101,7 +103,10 @@ def _get_request_data(asgi_scope: "Any") -> "Dict[str, Any]": request_data["query_string"] = _get_query(asgi_scope) request_data["url"] = _get_url( - asgi_scope, "http" if ty == "http" else "ws", headers.get("host") + asgi_scope, + "http" if ty == "http" else "ws", + headers.get("host"), + path_includes_root_path=path_includes_root_path, ) client = asgi_scope.get("client") @@ -111,7 +116,9 @@ def _get_request_data(asgi_scope: "Any") -> "Dict[str, Any]": return request_data -def _get_request_attributes(asgi_scope: "Any") -> "dict[str, Any]": +def _get_request_attributes( + asgi_scope: "Any", path_includes_root_path: "bool" = True +) -> "dict[str, Any]": """ Return attributes related to the HTTP request from the ASGI scope. """ @@ -132,7 +139,10 @@ def _get_request_attributes(asgi_scope: "Any") -> "dict[str, Any]": attributes["http.query"] = query url_without_query_string = _get_url( - asgi_scope, "http" if ty == "http" else "ws", headers.get("host") + asgi_scope, + "http" if ty == "http" else "ws", + headers.get("host"), + path_includes_root_path=path_includes_root_path, ) query_string = _get_query(asgi_scope) attributes["url.full"] = ( diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index baf9a1afbd..55e024f92a 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -322,7 +322,8 @@ async def _run_app( with span_ctx as span: if isinstance(span, StreamedSpan): for attribute, value in _get_request_attributes( - scope + scope, + path_includes_root_path=self.path_includes_root_path, ).items(): span.set_attribute(attribute, value) @@ -404,7 +405,11 @@ def event_processor( self, event: "Event", hint: "Hint", asgi_scope: "Any" ) -> "Optional[Event]": request_data = event.get("request", {}) - request_data.update(_get_request_data(asgi_scope)) + request_data.update( + _get_request_data( + asgi_scope, path_includes_root_path=self.path_includes_root_path + ) + ) event["request"] = deepcopy(request_data) # Only set transaction name if not already set by Starlette or FastAPI (or other frameworks) diff --git a/sentry_sdk/integrations/starlette.py b/sentry_sdk/integrations/starlette.py index 526c0abd0f..892de37df0 100644 --- a/sentry_sdk/integrations/starlette.py +++ b/sentry_sdk/integrations/starlette.py @@ -143,6 +143,7 @@ def setup_once() -> None: ) patch_middlewares() + # See https://github.com/Kludex/starlette/commit/e8f0dcd54e4ceec47e02c45f5275374e292339ad path_includes_root_path = version >= (0, 33) patch_asgi_app(path_includes_root_path=path_includes_root_path) patch_request_response() diff --git a/tests/integrations/fastapi/test_fastapi.py b/tests/integrations/fastapi/test_fastapi.py index dc6bde89c8..67c8bf8008 100644 --- a/tests/integrations/fastapi/test_fastapi.py +++ b/tests/integrations/fastapi/test_fastapi.py @@ -1043,6 +1043,48 @@ def test_transaction_http_method_custom(sentry_init, capture_events): assert event2["request"]["method"] == "HEAD" +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_request_url(sentry_init, capture_events, capture_items, span_streaming): + sentry_init( + traces_sample_rate=1.0, + send_default_pii=True, + integrations=[ + StarletteIntegration(), + ], + _experiments={ + "trace_lifecycle": "stream" if span_streaming else "static", + }, + ) + + starlette_app = fastapi_app_factory() + + client = TestClient(starlette_app, root_path="/root") + + if span_streaming: + items = capture_items("span") + + client.get("/root/nomessage") + sentry_sdk.flush() + spans = [item.payload for item in items] + + (server_span,) = ( + span + for span in spans + if span["attributes"].get("sentry.op") == "http.server" + ) + assert server_span["attributes"]["url.full"] == ( + "http://testserver/root/nomessage" + ) + else: + events = capture_events() + + client.get("/root/nomessage") + + assert len(events) == 1 + (event,) = events + assert event["request"]["url"] == "http://testserver/root/nomessage" + + @parametrize_test_configurable_status_codes def test_configurable_status_codes( sentry_init, diff --git a/tests/integrations/starlette/test_starlette.py b/tests/integrations/starlette/test_starlette.py index 22ae7c55a4..4bea39cb95 100644 --- a/tests/integrations/starlette/test_starlette.py +++ b/tests/integrations/starlette/test_starlette.py @@ -1477,6 +1477,48 @@ def test_transaction_http_method_default(sentry_init, capture_events): assert event["request"]["method"] == "GET" +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_request_url(sentry_init, capture_events, capture_items, span_streaming): + sentry_init( + traces_sample_rate=1.0, + send_default_pii=True, + integrations=[ + StarletteIntegration(), + ], + _experiments={ + "trace_lifecycle": "stream" if span_streaming else "static", + }, + ) + + starlette_app = starlette_app_factory() + + client = TestClient(starlette_app, root_path="/root") + + if span_streaming: + items = capture_items("span") + + client.get("/root/nomessage") + sentry_sdk.flush() + spans = [item.payload for item in items] + + (server_span,) = ( + span + for span in spans + if span["attributes"].get("sentry.op") == "http.server" + ) + assert server_span["attributes"]["url.full"] == ( + "http://testserver/root/nomessage" + ) + else: + events = capture_events() + + client.get("/root/nomessage") + + assert len(events) == 1 + (event,) = events + assert event["request"]["url"] == "http://testserver/root/nomessage" + + @pytest.mark.skipif( STARLETTE_VERSION < (0, 21), reason="Requires Starlette >= 0.21, because earlier versions do not support HTTP 'HEAD' requests", From a58e42dee8d2d6ec7f464024f8d1bdde8a329a19 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 19 Jun 2026 11:02:50 +0200 Subject: [PATCH 03/20] remanining tests --- tests/integrations/django/asgi/test_asgi.py | 51 +++++++++++++++++++ tests/integrations/fastapi/test_fastapi.py | 7 ++- tests/integrations/litestar/test_litestar.py | 43 ++++++++++++++++ tests/integrations/quart/test_quart.py | 45 ++++++++++++++++ .../integrations/starlette/test_starlette.py | 10 +++- tests/integrations/starlite/test_starlite.py | 43 ++++++++++++++++ 6 files changed, 195 insertions(+), 4 deletions(-) diff --git a/tests/integrations/django/asgi/test_asgi.py b/tests/integrations/django/asgi/test_asgi.py index 4e9eb95556..2c5a18bd30 100644 --- a/tests/integrations/django/asgi/test_asgi.py +++ b/tests/integrations/django/asgi/test_asgi.py @@ -1049,3 +1049,54 @@ async def test_transaction_http_method_custom( (event1, event2) = events assert event1["request"]["method"] == "OPTIONS" assert event2["request"]["method"] == "HEAD" + + +@pytest.mark.parametrize("application", APPS) +@pytest.mark.asyncio +@pytest.mark.skipif( + django.VERSION < (3, 0), reason="Django ASGI support shipped in 3.0" +) +@pytest.mark.parametrize("span_streaming", [True, False]) +async def test_request_url( + sentry_init, + capture_events, + capture_items, + application, + span_streaming, +): + sentry_init( + integrations=[DjangoIntegration()], + traces_sample_rate=1.0, + send_default_pii=True, + _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, + ) + comm = HttpCommunicator( + application, + "GET", + "/root/nomessage", + ) + + if span_streaming: + items = capture_items("span") + await comm.get_response() + await comm.wait() + + sentry_sdk.flush() + spans = [item.payload for item in items] + + (server_span,) = ( + span + for span in spans + if span["attributes"].get("sentry.op") == "http.server" + ) + assert server_span["attributes"]["url.full"] == ( + "http://testserver/root/nomessage" + ) + else: + events = capture_events() + + await comm.get_response() + await comm.wait() + + (event,) = events + assert event["request"]["url"] == "http://testserver/root/nomessage" diff --git a/tests/integrations/fastapi/test_fastapi.py b/tests/integrations/fastapi/test_fastapi.py index 67c8bf8008..583d95fd7f 100644 --- a/tests/integrations/fastapi/test_fastapi.py +++ b/tests/integrations/fastapi/test_fastapi.py @@ -62,6 +62,7 @@ def fastapi_app_factory(): app = FastAPI() + mounted_app = FastAPI() @app.get("/error") async def _error(): @@ -74,6 +75,7 @@ async def _message(): capture_message("Hi") return {"message": "Hi"} + @mounted_app.get("/nomessage") @app.delete("/nomessage") @app.get("/nomessage") @app.head("/nomessage") @@ -118,6 +120,8 @@ async def body_form( capture_message("hi") return {"status": "ok"} + app.mount("/root", mounted_app) + return app @@ -1058,7 +1062,7 @@ def test_request_url(sentry_init, capture_events, capture_items, span_streaming) starlette_app = fastapi_app_factory() - client = TestClient(starlette_app, root_path="/root") + client = TestClient(starlette_app) if span_streaming: items = capture_items("span") @@ -1080,7 +1084,6 @@ def test_request_url(sentry_init, capture_events, capture_items, span_streaming) client.get("/root/nomessage") - assert len(events) == 1 (event,) = events assert event["request"]["url"] == "http://testserver/root/nomessage" diff --git a/tests/integrations/litestar/test_litestar.py b/tests/integrations/litestar/test_litestar.py index 4abb037e36..89ecaa596c 100644 --- a/tests/integrations/litestar/test_litestar.py +++ b/tests/integrations/litestar/test_litestar.py @@ -47,6 +47,10 @@ async def message_with_id() -> "dict[str, Any]": capture_message("hi") return {"status": "ok"} + @get("/nomessage") + async def nomessage() -> "dict[str, Any]": + return {"status": "ok"} + logging_config = LoggingConfig() app = Litestar( @@ -55,6 +59,7 @@ async def message_with_id() -> "dict[str, Any]": custom_error, message, message_with_id, + nomessage, MyController, ], debug=debug, @@ -818,3 +823,41 @@ async def error() -> None: ... event_exception = events[0]["exception"]["values"][0] assert event_exception["type"] == "RuntimeError" assert event_exception["value"] == "Too Hot" + + +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_request_url(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", + }, + ) + + litestar_app = litestar_app_factory() + client = TestClient(litestar_app, root_path="/root") + + if span_streaming: + items = capture_items("span") + + client.get("/root/nomessage") + + sentry_sdk.flush() + spans = [item.payload for item in items] + + (server_span,) = ( + span + for span in spans + if span["attributes"].get("sentry.op") == "http.server" + ) + assert server_span["attributes"]["url.full"] == ( + "http://testserver/root/nomessage" + ) + else: + events = capture_events() + + client.get("/root/nomessage") + + (event,) = events + assert event["request"]["url"] == "http://testserver/root/nomessage" diff --git a/tests/integrations/quart/test_quart.py b/tests/integrations/quart/test_quart.py index 7c7579501b..afc65004a7 100644 --- a/tests/integrations/quart/test_quart.py +++ b/tests/integrations/quart/test_quart.py @@ -44,6 +44,10 @@ async def hi(): capture_message("hi") return "ok" + @app.route("/nomessage") + async def nomessage(): + return "ok" + @app.route("/message/") async def hi_with_id(message_id): capture_message("hi with id") @@ -682,6 +686,22 @@ async def test_span_origin(sentry_init, capture_events): assert event["contexts"]["trace"]["origin"] == "auto.http.quart" +@pytest.mark.asyncio +async def test_request_url(sentry_init, capture_events): + sentry_init( + traces_sample_rate=1.0, + integrations=[quart_sentry.QuartIntegration()], + ) + app = quart_app_factory() + client = app.test_client() + + events = capture_events() + await client.get("/root/nomessage", root_path="/root") + + (event,) = events + assert event["request"]["url"] == "http://localhost/root/nomessage" + + @pytest.mark.asyncio async def test_span_streaming_basic(sentry_init, capture_items): sentry_init( @@ -966,3 +986,28 @@ async def test_span_streaming_sensitive_header_passthrough_with_pii( segment["attributes"]["http.request.header.authorization"] == "Bearer secret-token" ) + + +@pytest.mark.asyncio +async def test_span_streaming_request_url(sentry_init, capture_items): + sentry_init( + traces_sample_rate=1.0, + send_default_pii=True, + integrations=[quart_sentry.QuartIntegration()], + _experiments={ + "trace_lifecycle": "stream", + }, + ) + app = quart_app_factory() + client = app.test_client() + + items = capture_items("span") + await client.get("/root/nomessage", root_path="/root") + + sentry_sdk.flush() + spans = [item.payload for item in items] + + (server_span,) = ( + span for span in spans if span["attributes"].get("sentry.op") == "http.server" + ) + assert server_span["attributes"]["url.full"] == "http://localhost/root/nomessage" diff --git a/tests/integrations/starlette/test_starlette.py b/tests/integrations/starlette/test_starlette.py index 4bea39cb95..5287911be6 100644 --- a/tests/integrations/starlette/test_starlette.py +++ b/tests/integrations/starlette/test_starlette.py @@ -146,6 +146,12 @@ async def _body_raw(request): "TRACE", ] + mounted_app = starlette.applications.Starlette( + routes=[ + starlette.routing.Route("/nomessage", _nomessage, methods=all_methods), + ], + ) + app = starlette.applications.Starlette( debug=debug, routes=[ @@ -160,6 +166,7 @@ async def _body_raw(request): starlette.routing.Route("/body/json", _body_json, methods=["POST"]), starlette.routing.Route("/body/form", _body_form, methods=["POST"]), starlette.routing.Route("/body/raw", _body_raw, methods=["POST"]), + starlette.routing.Mount("/root", app=mounted_app), ], middleware=middleware, ) @@ -1492,7 +1499,7 @@ def test_request_url(sentry_init, capture_events, capture_items, span_streaming) starlette_app = starlette_app_factory() - client = TestClient(starlette_app, root_path="/root") + client = TestClient(starlette_app) if span_streaming: items = capture_items("span") @@ -1514,7 +1521,6 @@ def test_request_url(sentry_init, capture_events, capture_items, span_streaming) client.get("/root/nomessage") - assert len(events) == 1 (event,) = events assert event["request"]["url"] == "http://testserver/root/nomessage" diff --git a/tests/integrations/starlite/test_starlite.py b/tests/integrations/starlite/test_starlite.py index 3b6dc44131..eb7478b89b 100644 --- a/tests/integrations/starlite/test_starlite.py +++ b/tests/integrations/starlite/test_starlite.py @@ -41,6 +41,10 @@ async def message_with_id() -> "Dict[str, Any]": capture_message("hi") return {"status": "ok"} + @get("/nomessage") + async def nomessage() -> "Dict[str, Any]": + return {"status": "ok"} + logging_config = LoggingConfig() app = Starlite( @@ -49,6 +53,7 @@ async def message_with_id() -> "Dict[str, Any]": custom_error, message, message_with_id, + nomessage, MyController, ], debug=debug, @@ -574,3 +579,41 @@ async def __call__(self, scope, receive, send): } else: assert "user" not in event + + +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_request_url(sentry_init, capture_events, capture_items, span_streaming): + sentry_init( + traces_sample_rate=1.0, + integrations=[StarliteIntegration()], + _experiments={ + "trace_lifecycle": "stream" if span_streaming else "static", + }, + ) + + starlite_app = starlite_app_factory() + client = TestClient(starlite_app, root_path="/root") + + if span_streaming: + items = capture_items("span") + + client.get("/nomessage") + + sentry_sdk.flush() + spans = [item.payload for item in items] + + (server_span,) = ( + span + for span in spans + if span["attributes"].get("sentry.op") == "http.server" + ) + assert server_span["attributes"]["url.full"] == ( + "http://testserver/root/nomessage" + ) + else: + events = capture_events() + + client.get("/nomessage") + + (event,) = events + assert event["request"]["url"] == "http://testserver/root/nomessage" From 42aca2143cdb4d8a12d1b7ef50f790cb69e475d8 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 19 Jun 2026 11:39:04 +0200 Subject: [PATCH 04/20] update --- sentry_sdk/integrations/litestar.py | 2 ++ sentry_sdk/integrations/starlite.py | 2 ++ tests/integrations/litestar/test_litestar.py | 9 ++++++--- tests/integrations/starlite/test_starlite.py | 9 ++++++--- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/sentry_sdk/integrations/litestar.py b/sentry_sdk/integrations/litestar.py index f0c90a7921..ad0b1fa70d 100644 --- a/sentry_sdk/integrations/litestar.py +++ b/sentry_sdk/integrations/litestar.py @@ -92,6 +92,8 @@ def __init__( mechanism_type="asgi", span_origin=span_origin, asgi_version=3, + # https://github.com/litestar-org/litestar/issues/2077 + path_includes_root_path=False, ) def _capture_request_exception(self, exc: Exception) -> None: diff --git a/sentry_sdk/integrations/starlite.py b/sentry_sdk/integrations/starlite.py index 1c9328a09d..71d2c19ece 100644 --- a/sentry_sdk/integrations/starlite.py +++ b/sentry_sdk/integrations/starlite.py @@ -77,6 +77,8 @@ def __init__( mechanism_type="asgi", span_origin=span_origin, asgi_version=3, + # https://github.com/litestar-org/litestar/issues/2077 + path_includes_root_path=False, ) diff --git a/tests/integrations/litestar/test_litestar.py b/tests/integrations/litestar/test_litestar.py index 89ecaa596c..73a62f2e28 100644 --- a/tests/integrations/litestar/test_litestar.py +++ b/tests/integrations/litestar/test_litestar.py @@ -830,13 +830,16 @@ def test_request_url(sentry_init, capture_events, capture_items, span_streaming) sentry_init( traces_sample_rate=1.0, integrations=[LitestarIntegration()], + send_default_pii=True, _experiments={ "trace_lifecycle": "stream" if span_streaming else "static", }, ) litestar_app = litestar_app_factory() - client = TestClient(litestar_app, root_path="/root") + client = TestClient( + litestar_app, base_url="http://testserver.local", root_path="/root" + ) if span_streaming: items = capture_items("span") @@ -852,7 +855,7 @@ def test_request_url(sentry_init, capture_events, capture_items, span_streaming) if span["attributes"].get("sentry.op") == "http.server" ) assert server_span["attributes"]["url.full"] == ( - "http://testserver/root/nomessage" + "http://testserver.local/root/nomessage" ) else: events = capture_events() @@ -860,4 +863,4 @@ def test_request_url(sentry_init, capture_events, capture_items, span_streaming) client.get("/root/nomessage") (event,) = events - assert event["request"]["url"] == "http://testserver/root/nomessage" + assert event["request"]["url"] == "http://testserver.local/root/nomessage" diff --git a/tests/integrations/starlite/test_starlite.py b/tests/integrations/starlite/test_starlite.py index eb7478b89b..3ea483f39f 100644 --- a/tests/integrations/starlite/test_starlite.py +++ b/tests/integrations/starlite/test_starlite.py @@ -586,13 +586,16 @@ def test_request_url(sentry_init, capture_events, capture_items, span_streaming) sentry_init( traces_sample_rate=1.0, integrations=[StarliteIntegration()], + send_default_pii=True, _experiments={ "trace_lifecycle": "stream" if span_streaming else "static", }, ) starlite_app = starlite_app_factory() - client = TestClient(starlite_app, root_path="/root") + client = TestClient( + starlite_app, base_url="http://testserver.local", root_path="/root" + ) if span_streaming: items = capture_items("span") @@ -608,7 +611,7 @@ def test_request_url(sentry_init, capture_events, capture_items, span_streaming) if span["attributes"].get("sentry.op") == "http.server" ) assert server_span["attributes"]["url.full"] == ( - "http://testserver/root/nomessage" + "http://testserver.local/root/nomessage" ) else: events = capture_events() @@ -616,4 +619,4 @@ def test_request_url(sentry_init, capture_events, capture_items, span_streaming) client.get("/nomessage") (event,) = events - assert event["request"]["url"] == "http://testserver/root/nomessage" + assert event["request"]["url"] == "http://testserver.local/root/nomessage" From ad1d00f262cf66257fa0f0af6d39f5e1ad3d4511 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 19 Jun 2026 11:49:39 +0200 Subject: [PATCH 05/20] update --- tests/integrations/litestar/test_litestar.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/integrations/litestar/test_litestar.py b/tests/integrations/litestar/test_litestar.py index 73a62f2e28..3ea00c7be0 100644 --- a/tests/integrations/litestar/test_litestar.py +++ b/tests/integrations/litestar/test_litestar.py @@ -16,9 +16,12 @@ import sentry_sdk from sentry_sdk import capture_message from sentry_sdk.integrations.litestar import LitestarIntegration +from sentry_sdk.utils import package_version from tests.conftest import ApproxDict from tests.integrations.conftest import parametrize_test_configurable_status_codes +LITESTAR_VERSION = package_version("litestar") + def litestar_app_factory(middleware=None, debug=True, exception_handlers=None): class MyController(Controller): @@ -844,7 +847,9 @@ def test_request_url(sentry_init, capture_events, capture_items, span_streaming) if span_streaming: items = capture_items("span") - client.get("/root/nomessage") + # https://github.com/litestar-org/litestar/commit/72dda171768bd470adc065c47c1ecf1d80b5e749 + url = "/root/nomessage" if LITESTAR_VERSION > (2, 5, 3) else "/nomessage" + client.get(url) sentry_sdk.flush() spans = [item.payload for item in items] @@ -860,7 +865,9 @@ def test_request_url(sentry_init, capture_events, capture_items, span_streaming) else: events = capture_events() - client.get("/root/nomessage") + # https://github.com/litestar-org/litestar/commit/72dda171768bd470adc065c47c1ecf1d80b5e749 + url = "/root/nomessage" if LITESTAR_VERSION > (2, 5, 3) else "/nomessage" + client.get(url) (event,) = events assert event["request"]["url"] == "http://testserver.local/root/nomessage" From 539b1cdf15c5c376d9c545528d168223b6b246e8 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 19 Jun 2026 11:49:56 +0200 Subject: [PATCH 06/20] logic error --- tests/integrations/litestar/test_litestar.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integrations/litestar/test_litestar.py b/tests/integrations/litestar/test_litestar.py index 3ea00c7be0..97770932ea 100644 --- a/tests/integrations/litestar/test_litestar.py +++ b/tests/integrations/litestar/test_litestar.py @@ -848,7 +848,7 @@ def test_request_url(sentry_init, capture_events, capture_items, span_streaming) items = capture_items("span") # https://github.com/litestar-org/litestar/commit/72dda171768bd470adc065c47c1ecf1d80b5e749 - url = "/root/nomessage" if LITESTAR_VERSION > (2, 5, 3) else "/nomessage" + url = "/root/nomessage" if LITESTAR_VERSION >= (2, 5, 3) else "/nomessage" client.get(url) sentry_sdk.flush() @@ -866,7 +866,7 @@ def test_request_url(sentry_init, capture_events, capture_items, span_streaming) events = capture_events() # https://github.com/litestar-org/litestar/commit/72dda171768bd470adc065c47c1ecf1d80b5e749 - url = "/root/nomessage" if LITESTAR_VERSION > (2, 5, 3) else "/nomessage" + url = "/root/nomessage" if LITESTAR_VERSION >= (2, 5, 3) else "/nomessage" client.get(url) (event,) = events From a7112ba2b4c602c526fa2d8ff25d8e6c677d21ab Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 19 Jun 2026 12:02:45 +0200 Subject: [PATCH 07/20] fix quart tests --- tests/integrations/quart/test_quart.py | 30 +++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/tests/integrations/quart/test_quart.py b/tests/integrations/quart/test_quart.py index afc65004a7..c2ef37964b 100644 --- a/tests/integrations/quart/test_quart.py +++ b/tests/integrations/quart/test_quart.py @@ -14,7 +14,9 @@ set_tag, ) from sentry_sdk.integrations.logging import LoggingIntegration -from sentry_sdk.utils import SENSITIVE_DATA_SUBSTITUTE +from sentry_sdk.utils import SENSITIVE_DATA_SUBSTITUTE, package_version + +QUART_VERSION = package_version("quart") def quart_app_factory(): @@ -696,7 +698,18 @@ async def test_request_url(sentry_init, capture_events): client = app.test_client() events = capture_events() - await client.get("/root/nomessage", root_path="/root") + + # https://github.com/pallets/quart/commit/7be545c + url = ( + "/root/nomessage" + if QUART_VERSION + >= ( + 0, + 19, + ) + else "/nomessage" + ) + await client.get(url, root_path="/root") (event,) = events assert event["request"]["url"] == "http://localhost/root/nomessage" @@ -1002,7 +1015,18 @@ async def test_span_streaming_request_url(sentry_init, capture_items): client = app.test_client() items = capture_items("span") - await client.get("/root/nomessage", root_path="/root") + + # https://github.com/pallets/quart/commit/7be545c + url = ( + "/root/nomessage" + if QUART_VERSION + >= ( + 0, + 19, + ) + else "/nomessage" + ) + await client.get(url, root_path="/root") sentry_sdk.flush() spans = [item.payload for item in items] From 574b5e57227dc7dbf9cbd4f06b8fbfb348919cef Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 19 Jun 2026 13:08:16 +0200 Subject: [PATCH 08/20] fix django tests --- tests/integrations/django/asgi/test_asgi.py | 6 ++---- tests/integrations/quart/test_quart.py | 20 ++------------------ 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/tests/integrations/django/asgi/test_asgi.py b/tests/integrations/django/asgi/test_asgi.py index 2c5a18bd30..7a33289ffb 100644 --- a/tests/integrations/django/asgi/test_asgi.py +++ b/tests/integrations/django/asgi/test_asgi.py @@ -1089,9 +1089,7 @@ async def test_request_url( for span in spans if span["attributes"].get("sentry.op") == "http.server" ) - assert server_span["attributes"]["url.full"] == ( - "http://testserver/root/nomessage" - ) + assert server_span["attributes"]["url.full"] == ("/root/nomessage") else: events = capture_events() @@ -1099,4 +1097,4 @@ async def test_request_url( await comm.wait() (event,) = events - assert event["request"]["url"] == "http://testserver/root/nomessage" + assert event["request"]["url"] == "/root/nomessage" diff --git a/tests/integrations/quart/test_quart.py b/tests/integrations/quart/test_quart.py index c2ef37964b..6d3529d157 100644 --- a/tests/integrations/quart/test_quart.py +++ b/tests/integrations/quart/test_quart.py @@ -700,15 +700,7 @@ async def test_request_url(sentry_init, capture_events): events = capture_events() # https://github.com/pallets/quart/commit/7be545c - url = ( - "/root/nomessage" - if QUART_VERSION - >= ( - 0, - 19, - ) - else "/nomessage" - ) + url = "/root/nomessage" if QUART_VERSION >= (0, 19) else "/nomessage" await client.get(url, root_path="/root") (event,) = events @@ -1017,15 +1009,7 @@ async def test_span_streaming_request_url(sentry_init, capture_items): items = capture_items("span") # https://github.com/pallets/quart/commit/7be545c - url = ( - "/root/nomessage" - if QUART_VERSION - >= ( - 0, - 19, - ) - else "/nomessage" - ) + url = "/root/nomessage" if QUART_VERSION >= (0, 19) else "/nomessage" await client.get(url, root_path="/root") sentry_sdk.flush() From e7c7fab43350be7c1bbef7944dbc3e2023b2a1dc Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 19 Jun 2026 13:21:25 +0200 Subject: [PATCH 09/20] fix django tests --- tests/integrations/django/asgi/test_asgi.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integrations/django/asgi/test_asgi.py b/tests/integrations/django/asgi/test_asgi.py index 7a33289ffb..37afb0b30f 100644 --- a/tests/integrations/django/asgi/test_asgi.py +++ b/tests/integrations/django/asgi/test_asgi.py @@ -1075,6 +1075,7 @@ async def test_request_url( "GET", "/root/nomessage", ) + comm.scope["root_path"] = "/root" if span_streaming: items = capture_items("span") From 518acbef5b91c3ddca86f54df54ebdb97bec22fb Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 19 Jun 2026 13:43:15 +0200 Subject: [PATCH 10/20] fix quart url --- sentry_sdk/integrations/quart.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/quart.py b/sentry_sdk/integrations/quart.py index 6a5603d825..d111425a9f 100644 --- a/sentry_sdk/integrations/quart.py +++ b/sentry_sdk/integrations/quart.py @@ -17,6 +17,7 @@ capture_internal_exceptions, ensure_integration_enabled, event_from_exception, + package_version, ) if TYPE_CHECKING: @@ -86,16 +87,23 @@ def setup_once() -> None: def patch_asgi_app() -> None: old_app = Quart.__call__ + version = package_version("quart") + async def sentry_patched_asgi_app( self: "Any", scope: "Any", receive: "Any", send: "Any" ) -> "Any": - if sentry_sdk.get_client().get_integration(QuartIntegration) is None: + if ( + sentry_sdk.get_client().get_integration(QuartIntegration) is None + or version is None + ): return await old_app(self, scope, receive, send) middleware = SentryAsgiMiddleware( lambda *a, **kw: old_app(self, *a, **kw), span_origin=QuartIntegration.origin, asgi_version=3, + # https://github.com/pallets/quart/commit/7be545c + path_includes_root_path=version >= (0, 19), ) return await middleware(scope, receive, send) From b4205873f1a5f61c8b4f188c4213cfcef5456208 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 19 Jun 2026 14:47:48 +0200 Subject: [PATCH 11/20] update docstrings --- sentry_sdk/integrations/litestar.py | 5 ++++- sentry_sdk/integrations/quart.py | 2 ++ sentry_sdk/integrations/starlette.py | 3 ++- sentry_sdk/integrations/starlite.py | 1 - 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/sentry_sdk/integrations/litestar.py b/sentry_sdk/integrations/litestar.py index ad0b1fa70d..70a0fec430 100644 --- a/sentry_sdk/integrations/litestar.py +++ b/sentry_sdk/integrations/litestar.py @@ -92,7 +92,10 @@ def __init__( mechanism_type="asgi", span_origin=span_origin, asgi_version=3, - # https://github.com/litestar-org/litestar/issues/2077 + # Unlike Starlette, LiteStar does not extend scope["root_path"] with the mount path. + # Since LiteStar handles servers that include and do not include scope["root_path"] in scope["path"] + # with the commit below, keep the existing behavior for compatibility. + # https://github.com/litestar-org/litestar/commit/72dda171768bd470adc065c47c1ecf1d80b5e749 path_includes_root_path=False, ) diff --git a/sentry_sdk/integrations/quart.py b/sentry_sdk/integrations/quart.py index d111425a9f..4578706fe3 100644 --- a/sentry_sdk/integrations/quart.py +++ b/sentry_sdk/integrations/quart.py @@ -102,6 +102,8 @@ async def sentry_patched_asgi_app( lambda *a, **kw: old_app(self, *a, **kw), span_origin=QuartIntegration.origin, asgi_version=3, + # Starting with the commit below, Quart treats any scope["path"] + # that does not include scope["root_path"] as invalid. # https://github.com/pallets/quart/commit/7be545c path_includes_root_path=version >= (0, 19), ) diff --git a/sentry_sdk/integrations/starlette.py b/sentry_sdk/integrations/starlette.py index 892de37df0..dc2b7d00e1 100644 --- a/sentry_sdk/integrations/starlette.py +++ b/sentry_sdk/integrations/starlette.py @@ -143,7 +143,8 @@ def setup_once() -> None: ) patch_middlewares() - # See https://github.com/Kludex/starlette/commit/e8f0dcd54e4ceec47e02c45f5275374e292339ad + # Starlette's Mount includes scope["root_path"] in scope["path"] starting with: + # https://github.com/Kludex/starlette/commit/e8f0dcd54e4ceec47e02c45f5275374e292339ad. path_includes_root_path = version >= (0, 33) patch_asgi_app(path_includes_root_path=path_includes_root_path) patch_request_response() diff --git a/sentry_sdk/integrations/starlite.py b/sentry_sdk/integrations/starlite.py index 71d2c19ece..e23efaa612 100644 --- a/sentry_sdk/integrations/starlite.py +++ b/sentry_sdk/integrations/starlite.py @@ -77,7 +77,6 @@ def __init__( mechanism_type="asgi", span_origin=span_origin, asgi_version=3, - # https://github.com/litestar-org/litestar/issues/2077 path_includes_root_path=False, ) From eda0dec33e69728493ecffce67ddfdfc4cd6b9a1 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 23 Jun 2026 10:12:16 +0200 Subject: [PATCH 12/20] revert litestar and starlite tests as behavior unchanged --- tests/integrations/litestar/test_litestar.py | 50 -------------------- tests/integrations/starlite/test_starlite.py | 46 ------------------ 2 files changed, 96 deletions(-) diff --git a/tests/integrations/litestar/test_litestar.py b/tests/integrations/litestar/test_litestar.py index 97770932ea..bff2d91f9b 100644 --- a/tests/integrations/litestar/test_litestar.py +++ b/tests/integrations/litestar/test_litestar.py @@ -50,10 +50,6 @@ async def message_with_id() -> "dict[str, Any]": capture_message("hi") return {"status": "ok"} - @get("/nomessage") - async def nomessage() -> "dict[str, Any]": - return {"status": "ok"} - logging_config = LoggingConfig() app = Litestar( @@ -62,7 +58,6 @@ async def nomessage() -> "dict[str, Any]": custom_error, message, message_with_id, - nomessage, MyController, ], debug=debug, @@ -826,48 +821,3 @@ async def error() -> None: ... event_exception = events[0]["exception"]["values"][0] assert event_exception["type"] == "RuntimeError" assert event_exception["value"] == "Too Hot" - - -@pytest.mark.parametrize("span_streaming", [True, False]) -def test_request_url(sentry_init, capture_events, capture_items, span_streaming): - sentry_init( - traces_sample_rate=1.0, - integrations=[LitestarIntegration()], - send_default_pii=True, - _experiments={ - "trace_lifecycle": "stream" if span_streaming else "static", - }, - ) - - litestar_app = litestar_app_factory() - client = TestClient( - litestar_app, base_url="http://testserver.local", root_path="/root" - ) - - if span_streaming: - items = capture_items("span") - - # https://github.com/litestar-org/litestar/commit/72dda171768bd470adc065c47c1ecf1d80b5e749 - url = "/root/nomessage" if LITESTAR_VERSION >= (2, 5, 3) else "/nomessage" - client.get(url) - - sentry_sdk.flush() - spans = [item.payload for item in items] - - (server_span,) = ( - span - for span in spans - if span["attributes"].get("sentry.op") == "http.server" - ) - assert server_span["attributes"]["url.full"] == ( - "http://testserver.local/root/nomessage" - ) - else: - events = capture_events() - - # https://github.com/litestar-org/litestar/commit/72dda171768bd470adc065c47c1ecf1d80b5e749 - url = "/root/nomessage" if LITESTAR_VERSION >= (2, 5, 3) else "/nomessage" - client.get(url) - - (event,) = events - assert event["request"]["url"] == "http://testserver.local/root/nomessage" diff --git a/tests/integrations/starlite/test_starlite.py b/tests/integrations/starlite/test_starlite.py index 3ea483f39f..3b6dc44131 100644 --- a/tests/integrations/starlite/test_starlite.py +++ b/tests/integrations/starlite/test_starlite.py @@ -41,10 +41,6 @@ async def message_with_id() -> "Dict[str, Any]": capture_message("hi") return {"status": "ok"} - @get("/nomessage") - async def nomessage() -> "Dict[str, Any]": - return {"status": "ok"} - logging_config = LoggingConfig() app = Starlite( @@ -53,7 +49,6 @@ async def nomessage() -> "Dict[str, Any]": custom_error, message, message_with_id, - nomessage, MyController, ], debug=debug, @@ -579,44 +574,3 @@ async def __call__(self, scope, receive, send): } else: assert "user" not in event - - -@pytest.mark.parametrize("span_streaming", [True, False]) -def test_request_url(sentry_init, capture_events, capture_items, span_streaming): - sentry_init( - traces_sample_rate=1.0, - integrations=[StarliteIntegration()], - send_default_pii=True, - _experiments={ - "trace_lifecycle": "stream" if span_streaming else "static", - }, - ) - - starlite_app = starlite_app_factory() - client = TestClient( - starlite_app, base_url="http://testserver.local", root_path="/root" - ) - - if span_streaming: - items = capture_items("span") - - client.get("/nomessage") - - sentry_sdk.flush() - spans = [item.payload for item in items] - - (server_span,) = ( - span - for span in spans - if span["attributes"].get("sentry.op") == "http.server" - ) - assert server_span["attributes"]["url.full"] == ( - "http://testserver.local/root/nomessage" - ) - else: - events = capture_events() - - client.get("/nomessage") - - (event,) = events - assert event["request"]["url"] == "http://testserver.local/root/nomessage" From dc3d3526ccdce05186f5f9b6c75cc4b4d2bdcbe5 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 23 Jun 2026 10:18:20 +0200 Subject: [PATCH 13/20] update django condition --- sentry_sdk/integrations/django/asgi.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/django/asgi.py b/sentry_sdk/integrations/django/asgi.py index 43faffb5be..02c2608304 100644 --- a/sentry_sdk/integrations/django/asgi.py +++ b/sentry_sdk/integrations/django/asgi.py @@ -11,6 +11,7 @@ import inspect from typing import TYPE_CHECKING +from django import VERSION as DJANGO_VERSION from django.core.handlers.wsgi import WSGIRequest import sentry_sdk @@ -96,8 +97,11 @@ async def sentry_patched_asgi_handler( unsafe_context_data=True, span_origin=DjangoIntegration.origin, http_methods_to_capture=integration.http_methods_to_capture, + # From Django 5.1 onwards, ASGI request.path is taken directly from scope["path"] without prepending scope["root_path"]. + # Assume that scope["path"] includes scope["root_path"] for these versions, as otherwise request.path is also incorrect. + # https://github.com/django/django/commit/bcd255cd5ca0a1e686d276cca71f45ec400d84a2 + path_includes_root_path=DJANGO_VERSION >= (5, 1), )._run_asgi3 - return await middleware(scope, receive, send) cls.__call__ = sentry_patched_asgi_handler From a96d284a67386415e83e277879ccf4c2398fc875 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 23 Jun 2026 10:18:50 +0200 Subject: [PATCH 14/20] restore whitespace --- sentry_sdk/integrations/django/asgi.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sentry_sdk/integrations/django/asgi.py b/sentry_sdk/integrations/django/asgi.py index 02c2608304..4b5864e788 100644 --- a/sentry_sdk/integrations/django/asgi.py +++ b/sentry_sdk/integrations/django/asgi.py @@ -102,6 +102,7 @@ async def sentry_patched_asgi_handler( # https://github.com/django/django/commit/bcd255cd5ca0a1e686d276cca71f45ec400d84a2 path_includes_root_path=DJANGO_VERSION >= (5, 1), )._run_asgi3 + return await middleware(scope, receive, send) cls.__call__ = sentry_patched_asgi_handler From 0b1570bc40a8f75b5e9db1edb80ea54d52747172 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 26 Jun 2026 09:47:43 +0200 Subject: [PATCH 15/20] only change starlette and fastapi --- sentry_sdk/integrations/django/asgi.py | 6 +-- sentry_sdk/integrations/litestar.py | 4 -- sentry_sdk/integrations/quart.py | 13 +---- sentry_sdk/integrations/starlite.py | 1 - tests/integrations/django/asgi/test_asgi.py | 50 ------------------ tests/integrations/litestar/test_litestar.py | 3 -- tests/integrations/quart/test_quart.py | 55 +------------------- 7 files changed, 4 insertions(+), 128 deletions(-) diff --git a/sentry_sdk/integrations/django/asgi.py b/sentry_sdk/integrations/django/asgi.py index 4b5864e788..11045d47ec 100644 --- a/sentry_sdk/integrations/django/asgi.py +++ b/sentry_sdk/integrations/django/asgi.py @@ -11,7 +11,6 @@ import inspect from typing import TYPE_CHECKING -from django import VERSION as DJANGO_VERSION from django.core.handlers.wsgi import WSGIRequest import sentry_sdk @@ -97,10 +96,7 @@ async def sentry_patched_asgi_handler( unsafe_context_data=True, span_origin=DjangoIntegration.origin, http_methods_to_capture=integration.http_methods_to_capture, - # From Django 5.1 onwards, ASGI request.path is taken directly from scope["path"] without prepending scope["root_path"]. - # Assume that scope["path"] includes scope["root_path"] for these versions, as otherwise request.path is also incorrect. - # https://github.com/django/django/commit/bcd255cd5ca0a1e686d276cca71f45ec400d84a2 - path_includes_root_path=DJANGO_VERSION >= (5, 1), + path_includes_root_path=False, )._run_asgi3 return await middleware(scope, receive, send) diff --git a/sentry_sdk/integrations/litestar.py b/sentry_sdk/integrations/litestar.py index 70a0fec430..9b14d1c976 100644 --- a/sentry_sdk/integrations/litestar.py +++ b/sentry_sdk/integrations/litestar.py @@ -92,10 +92,6 @@ def __init__( mechanism_type="asgi", span_origin=span_origin, asgi_version=3, - # Unlike Starlette, LiteStar does not extend scope["root_path"] with the mount path. - # Since LiteStar handles servers that include and do not include scope["root_path"] in scope["path"] - # with the commit below, keep the existing behavior for compatibility. - # https://github.com/litestar-org/litestar/commit/72dda171768bd470adc065c47c1ecf1d80b5e749 path_includes_root_path=False, ) diff --git a/sentry_sdk/integrations/quart.py b/sentry_sdk/integrations/quart.py index 4578706fe3..2b549d1201 100644 --- a/sentry_sdk/integrations/quart.py +++ b/sentry_sdk/integrations/quart.py @@ -17,7 +17,6 @@ capture_internal_exceptions, ensure_integration_enabled, event_from_exception, - package_version, ) if TYPE_CHECKING: @@ -87,25 +86,17 @@ def setup_once() -> None: def patch_asgi_app() -> None: old_app = Quart.__call__ - version = package_version("quart") - async def sentry_patched_asgi_app( self: "Any", scope: "Any", receive: "Any", send: "Any" ) -> "Any": - if ( - sentry_sdk.get_client().get_integration(QuartIntegration) is None - or version is None - ): + if sentry_sdk.get_client().get_integration(QuartIntegration) is None: return await old_app(self, scope, receive, send) middleware = SentryAsgiMiddleware( lambda *a, **kw: old_app(self, *a, **kw), span_origin=QuartIntegration.origin, asgi_version=3, - # Starting with the commit below, Quart treats any scope["path"] - # that does not include scope["root_path"] as invalid. - # https://github.com/pallets/quart/commit/7be545c - path_includes_root_path=version >= (0, 19), + path_includes_root_path=False, ) return await middleware(scope, receive, send) diff --git a/sentry_sdk/integrations/starlite.py b/sentry_sdk/integrations/starlite.py index e23efaa612..1c9328a09d 100644 --- a/sentry_sdk/integrations/starlite.py +++ b/sentry_sdk/integrations/starlite.py @@ -77,7 +77,6 @@ def __init__( mechanism_type="asgi", span_origin=span_origin, asgi_version=3, - path_includes_root_path=False, ) diff --git a/tests/integrations/django/asgi/test_asgi.py b/tests/integrations/django/asgi/test_asgi.py index 37afb0b30f..4e9eb95556 100644 --- a/tests/integrations/django/asgi/test_asgi.py +++ b/tests/integrations/django/asgi/test_asgi.py @@ -1049,53 +1049,3 @@ async def test_transaction_http_method_custom( (event1, event2) = events assert event1["request"]["method"] == "OPTIONS" assert event2["request"]["method"] == "HEAD" - - -@pytest.mark.parametrize("application", APPS) -@pytest.mark.asyncio -@pytest.mark.skipif( - django.VERSION < (3, 0), reason="Django ASGI support shipped in 3.0" -) -@pytest.mark.parametrize("span_streaming", [True, False]) -async def test_request_url( - sentry_init, - capture_events, - capture_items, - application, - span_streaming, -): - sentry_init( - integrations=[DjangoIntegration()], - traces_sample_rate=1.0, - send_default_pii=True, - _experiments={"trace_lifecycle": "stream" if span_streaming else "static"}, - ) - comm = HttpCommunicator( - application, - "GET", - "/root/nomessage", - ) - comm.scope["root_path"] = "/root" - - if span_streaming: - items = capture_items("span") - await comm.get_response() - await comm.wait() - - sentry_sdk.flush() - spans = [item.payload for item in items] - - (server_span,) = ( - span - for span in spans - if span["attributes"].get("sentry.op") == "http.server" - ) - assert server_span["attributes"]["url.full"] == ("/root/nomessage") - else: - events = capture_events() - - await comm.get_response() - await comm.wait() - - (event,) = events - assert event["request"]["url"] == "/root/nomessage" diff --git a/tests/integrations/litestar/test_litestar.py b/tests/integrations/litestar/test_litestar.py index bff2d91f9b..4abb037e36 100644 --- a/tests/integrations/litestar/test_litestar.py +++ b/tests/integrations/litestar/test_litestar.py @@ -16,12 +16,9 @@ import sentry_sdk from sentry_sdk import capture_message from sentry_sdk.integrations.litestar import LitestarIntegration -from sentry_sdk.utils import package_version from tests.conftest import ApproxDict from tests.integrations.conftest import parametrize_test_configurable_status_codes -LITESTAR_VERSION = package_version("litestar") - def litestar_app_factory(middleware=None, debug=True, exception_handlers=None): class MyController(Controller): diff --git a/tests/integrations/quart/test_quart.py b/tests/integrations/quart/test_quart.py index 6d3529d157..7c7579501b 100644 --- a/tests/integrations/quart/test_quart.py +++ b/tests/integrations/quart/test_quart.py @@ -14,9 +14,7 @@ set_tag, ) from sentry_sdk.integrations.logging import LoggingIntegration -from sentry_sdk.utils import SENSITIVE_DATA_SUBSTITUTE, package_version - -QUART_VERSION = package_version("quart") +from sentry_sdk.utils import SENSITIVE_DATA_SUBSTITUTE def quart_app_factory(): @@ -46,10 +44,6 @@ async def hi(): capture_message("hi") return "ok" - @app.route("/nomessage") - async def nomessage(): - return "ok" - @app.route("/message/") async def hi_with_id(message_id): capture_message("hi with id") @@ -688,25 +682,6 @@ async def test_span_origin(sentry_init, capture_events): assert event["contexts"]["trace"]["origin"] == "auto.http.quart" -@pytest.mark.asyncio -async def test_request_url(sentry_init, capture_events): - sentry_init( - traces_sample_rate=1.0, - integrations=[quart_sentry.QuartIntegration()], - ) - app = quart_app_factory() - client = app.test_client() - - events = capture_events() - - # https://github.com/pallets/quart/commit/7be545c - url = "/root/nomessage" if QUART_VERSION >= (0, 19) else "/nomessage" - await client.get(url, root_path="/root") - - (event,) = events - assert event["request"]["url"] == "http://localhost/root/nomessage" - - @pytest.mark.asyncio async def test_span_streaming_basic(sentry_init, capture_items): sentry_init( @@ -991,31 +966,3 @@ async def test_span_streaming_sensitive_header_passthrough_with_pii( segment["attributes"]["http.request.header.authorization"] == "Bearer secret-token" ) - - -@pytest.mark.asyncio -async def test_span_streaming_request_url(sentry_init, capture_items): - sentry_init( - traces_sample_rate=1.0, - send_default_pii=True, - integrations=[quart_sentry.QuartIntegration()], - _experiments={ - "trace_lifecycle": "stream", - }, - ) - app = quart_app_factory() - client = app.test_client() - - items = capture_items("span") - - # https://github.com/pallets/quart/commit/7be545c - url = "/root/nomessage" if QUART_VERSION >= (0, 19) else "/nomessage" - await client.get(url, root_path="/root") - - sentry_sdk.flush() - spans = [item.payload for item in items] - - (server_span,) = ( - span for span in spans if span["attributes"].get("sentry.op") == "http.server" - ) - assert server_span["attributes"]["url.full"] == "http://localhost/root/nomessage" From ae1b1d0a94408c2184bb1fded766f44df4d09427 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 26 Jun 2026 10:01:00 +0200 Subject: [PATCH 16/20] update url.path --- sentry_sdk/consts.py | 6 ++++++ sentry_sdk/integrations/_asgi_common.py | 6 ++++-- tests/integrations/fastapi/test_fastapi.py | 3 ++- tests/integrations/starlette/test_starlette.py | 3 ++- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index b85b179223..8306d01626 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -1020,6 +1020,12 @@ class SPANDATA: Example: "details" """ + URL_PATH = "url.path" + """ + The URI path component. + Example: "/foo" + """ + URL_QUERY = "url.query" """ The query string present in the URL. Note that this does not contain the leading ? character, while the `http.query` attribute does. diff --git a/sentry_sdk/integrations/_asgi_common.py b/sentry_sdk/integrations/_asgi_common.py index c4507009af..ee5df77698 100644 --- a/sentry_sdk/integrations/_asgi_common.py +++ b/sentry_sdk/integrations/_asgi_common.py @@ -150,8 +150,10 @@ def _get_request_attributes( if query_string is not None else url_without_query_string ) - attributes["url.path"] = asgi_scope.get("root_path", "") + asgi_scope.get( - "path", "" + attributes["url.path"] = ( + asgi_scope.get("path", "") + if path_includes_root_path + else asgi_scope.get("root_path", "") + asgi_scope.get("path", "") ) client = asgi_scope.get("client") diff --git a/tests/integrations/fastapi/test_fastapi.py b/tests/integrations/fastapi/test_fastapi.py index 583d95fd7f..d91927a644 100644 --- a/tests/integrations/fastapi/test_fastapi.py +++ b/tests/integrations/fastapi/test_fastapi.py @@ -1076,9 +1076,10 @@ def test_request_url(sentry_init, capture_events, capture_items, span_streaming) for span in spans if span["attributes"].get("sentry.op") == "http.server" ) - assert server_span["attributes"]["url.full"] == ( + assert server_span["attributes"][SPANDATA.URL_FULL] == ( "http://testserver/root/nomessage" ) + assert server_span["attributes"][SPANDATA.URL_PATH] == "/root/nomessage" else: events = capture_events() diff --git a/tests/integrations/starlette/test_starlette.py b/tests/integrations/starlette/test_starlette.py index 5287911be6..fb458a84d1 100644 --- a/tests/integrations/starlette/test_starlette.py +++ b/tests/integrations/starlette/test_starlette.py @@ -1513,9 +1513,10 @@ def test_request_url(sentry_init, capture_events, capture_items, span_streaming) for span in spans if span["attributes"].get("sentry.op") == "http.server" ) - assert server_span["attributes"]["url.full"] == ( + assert server_span["attributes"][SPANDATA.URL_FULL] == ( "http://testserver/root/nomessage" ) + assert server_span["attributes"][SPANDATA.URL_PATH] == "/root/nomessage" else: events = capture_events() From cb28622ea866921714d6d559961ed6d44746d03d Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 26 Jun 2026 10:02:06 +0200 Subject: [PATCH 17/20] add path_includes_root_path in channels middleware --- sentry_sdk/integrations/django/asgi.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sentry_sdk/integrations/django/asgi.py b/sentry_sdk/integrations/django/asgi.py index 11045d47ec..902064d10e 100644 --- a/sentry_sdk/integrations/django/asgi.py +++ b/sentry_sdk/integrations/django/asgi.py @@ -152,6 +152,7 @@ async def sentry_patched_asgi_handler( unsafe_context_data=True, span_origin=DjangoIntegration.origin, http_methods_to_capture=integration.http_methods_to_capture, + path_includes_root_path=False, ) return await middleware(self.scope)(receive, send) # type: ignore From 7d263e45bbe7d4ab821cbd4fd959cf310c9a3515 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 26 Jun 2026 10:04:43 +0200 Subject: [PATCH 18/20] remove test --- tests/integrations/asgi/test_asgi.py | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/tests/integrations/asgi/test_asgi.py b/tests/integrations/asgi/test_asgi.py index 60ccd88bd1..3bce0d1e10 100644 --- a/tests/integrations/asgi/test_asgi.py +++ b/tests/integrations/asgi/test_asgi.py @@ -243,30 +243,6 @@ async def test_capture_transaction( } -@pytest.mark.asyncio -async def test_capture_transaction_with_root_path( - sentry_init, - asgi3_app, - capture_items, -): - sentry_init( - send_default_pii=True, - traces_sample_rate=1.0, - _experiments={"trace_lifecycle": "stream"}, - ) - app = SentryAsgiMiddleware(asgi3_app) - - async with TestClient(app, scope={"root_path": "/api"}) as client: - items = capture_items("span") - await client.get("/some_url") - - sentry_sdk.flush() - - assert len(items) == 1 - span = items[0].payload - assert span["attributes"]["url.path"] == "/api/some_url" - - @pytest.mark.asyncio @pytest.mark.parametrize( "span_streaming", From c3050ce244420d7694d5f2683601101fd4562f3e Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 26 Jun 2026 11:02:35 +0200 Subject: [PATCH 19/20] use enum and tolerate both --- sentry_sdk/integrations/_asgi_common.py | 46 +++++++++++++++++-------- sentry_sdk/integrations/asgi.py | 30 ++++++++++------ sentry_sdk/integrations/django/asgi.py | 2 -- sentry_sdk/integrations/litestar.py | 1 - sentry_sdk/integrations/quart.py | 1 - sentry_sdk/integrations/starlette.py | 13 ++++--- 6 files changed, 58 insertions(+), 35 deletions(-) diff --git a/sentry_sdk/integrations/_asgi_common.py b/sentry_sdk/integrations/_asgi_common.py index ee5df77698..0e97132e24 100644 --- a/sentry_sdk/integrations/_asgi_common.py +++ b/sentry_sdk/integrations/_asgi_common.py @@ -1,4 +1,5 @@ import urllib +from enum import Enum from typing import TYPE_CHECKING from sentry_sdk.integrations._wsgi_common import _filter_headers @@ -12,6 +13,11 @@ from sentry_sdk.utils import AnnotatedValue +class _RootPathInPath(Enum): + EXCLUDED = "excluded" + EITHER = "either" + + def _get_headers(asgi_scope: "Any") -> "Dict[str, str]": """ Extract headers from the ASGI scope, in the format that the Sentry protocol expects. @@ -28,11 +34,27 @@ def _get_headers(asgi_scope: "Any") -> "Dict[str, str]": return headers +def _get_path( + asgi_scope: "Dict[str, Any]", root_path_in_path: "_RootPathInPath" +) -> "str": + if root_path_in_path is _RootPathInPath.EXCLUDED: + return asgi_scope.get("root_path", "") + asgi_scope.get("path", "") + + # Inverse of https://github.com/Kludex/starlette/blob/de970d7b3facb853eb7ad077decbf3d94f2aab6c/starlette/_utils.py#L96 + path = asgi_scope["path"] + root_path = asgi_scope.get("root_path", "") + + if not root_path or path == root_path or path.startswith(root_path + "/"): + return path + + return root_path + path + + def _get_url( asgi_scope: "Dict[str, Any]", default_scheme: "Literal['ws', 'http']", host: "Optional[Union[AnnotatedValue, str]]", - path_includes_root_path: "bool" = True, + path: str, ) -> str: """ Extract URL from the ASGI scope, without also including the querystring. @@ -40,11 +62,6 @@ def _get_url( scheme = asgi_scope.get("scheme", default_scheme) server = asgi_scope.get("server", None) - path = ( - asgi_scope.get("path", "") - if path_includes_root_path - else asgi_scope.get("root_path", "") + asgi_scope.get("path", "") - ) if host: return "%s://%s%s" % (scheme, host, path) @@ -87,7 +104,7 @@ def _get_ip(asgi_scope: "Any") -> str: def _get_request_data( - asgi_scope: "Any", path_includes_root_path: "bool" = True + asgi_scope: "Any", root_path_in_path: "_RootPathInPath" = _RootPathInPath.EXCLUDED ) -> "Dict[str, Any]": """ Returns data related to the HTTP request from the ASGI scope. @@ -106,7 +123,7 @@ def _get_request_data( asgi_scope, "http" if ty == "http" else "ws", headers.get("host"), - path_includes_root_path=path_includes_root_path, + path=_get_path(asgi_scope=asgi_scope, root_path_in_path=root_path_in_path), ) client = asgi_scope.get("client") @@ -117,7 +134,8 @@ def _get_request_data( def _get_request_attributes( - asgi_scope: "Any", path_includes_root_path: "bool" = True + asgi_scope: "Any", + root_path_in_path: "_RootPathInPath" = _RootPathInPath.EXCLUDED, ) -> "dict[str, Any]": """ Return attributes related to the HTTP request from the ASGI scope. @@ -138,11 +156,14 @@ def _get_request_attributes( if query: attributes["http.query"] = query + path = _get_path(asgi_scope=asgi_scope, root_path_in_path=root_path_in_path) + attributes["url.path"] = path + url_without_query_string = _get_url( asgi_scope, "http" if ty == "http" else "ws", headers.get("host"), - path_includes_root_path=path_includes_root_path, + path=path, ) query_string = _get_query(asgi_scope) attributes["url.full"] = ( @@ -150,11 +171,6 @@ def _get_request_attributes( if query_string is not None else url_without_query_string ) - attributes["url.path"] = ( - asgi_scope.get("path", "") - if path_includes_root_path - else asgi_scope.get("root_path", "") + asgi_scope.get("path", "") - ) client = asgi_scope.get("client") if client and should_send_default_pii(): diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index 55e024f92a..8b1ff5e2a3 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -16,9 +16,11 @@ from sentry_sdk.integrations._asgi_common import ( _get_headers, _get_ip, + _get_path, _get_request_attributes, _get_request_data, _get_url, + _RootPathInPath, ) from sentry_sdk.integrations._wsgi_common import ( DEFAULT_HTTP_METHODS_TO_CAPTURE, @@ -105,7 +107,7 @@ class SentryAsgiMiddleware: "mechanism_type", "span_origin", "http_methods_to_capture", - "path_includes_root_path", + "root_path_in_path", ) def __init__( @@ -117,7 +119,7 @@ def __init__( span_origin: str = "manual", http_methods_to_capture: "Tuple[str, ...]" = DEFAULT_HTTP_METHODS_TO_CAPTURE, asgi_version: "Optional[int]" = None, - path_includes_root_path: bool = True, + root_path_in_path: "_RootPathInPath" = _RootPathInPath.EXCLUDED, ) -> None: """ Instrument an ASGI application with Sentry. Provides HTTP/websocket @@ -154,7 +156,7 @@ def __init__( self.span_origin = span_origin self.app = app self.http_methods_to_capture = http_methods_to_capture - self.path_includes_root_path = path_includes_root_path + self.root_path_in_path = root_path_in_path if asgi_version is None: if _looks_like_asgi3(app): @@ -323,7 +325,7 @@ async def _run_app( if isinstance(span, StreamedSpan): for attribute, value in _get_request_attributes( scope, - path_includes_root_path=self.path_includes_root_path, + root_path_in_path=self.root_path_in_path, ).items(): span.set_attribute(attribute, value) @@ -406,9 +408,7 @@ def event_processor( ) -> "Optional[Event]": request_data = event.get("request", {}) request_data.update( - _get_request_data( - asgi_scope, path_includes_root_path=self.path_includes_root_path - ) + _get_request_data(asgi_scope, root_path_in_path=self.root_path_in_path) ) event["request"] = deepcopy(request_data) @@ -459,7 +459,9 @@ def _get_transaction_name_and_source( asgi_scope, "http" if ty == "http" else "ws", host=None, - path_includes_root_path=self.path_includes_root_path, + path=_get_path( + asgi_scope=asgi_scope, root_path_in_path=self.root_path_in_path + ), ) source = TransactionSource.URL @@ -476,7 +478,9 @@ def _get_transaction_name_and_source( asgi_scope, "http" if ty == "http" else "ws", host=None, - path_includes_root_path=self.path_includes_root_path, + path=_get_path( + asgi_scope=asgi_scope, root_path_in_path=self.root_path_in_path + ), ) source = TransactionSource.URL @@ -506,7 +510,9 @@ def _get_segment_name_and_source( asgi_scope, "http" if ty == "http" else "ws", host=None, - path_includes_root_path=self.path_includes_root_path, + path=_get_path( + asgi_scope=asgi_scope, root_path_in_path=self.root_path_in_path + ), ) source = SegmentSource.URL.value @@ -523,7 +529,9 @@ def _get_segment_name_and_source( asgi_scope, "http" if ty == "http" else "ws", host=None, - path_includes_root_path=self.path_includes_root_path, + path=_get_path( + asgi_scope=asgi_scope, root_path_in_path=self.root_path_in_path + ), ) source = SegmentSource.URL.value diff --git a/sentry_sdk/integrations/django/asgi.py b/sentry_sdk/integrations/django/asgi.py index 902064d10e..43faffb5be 100644 --- a/sentry_sdk/integrations/django/asgi.py +++ b/sentry_sdk/integrations/django/asgi.py @@ -96,7 +96,6 @@ async def sentry_patched_asgi_handler( unsafe_context_data=True, span_origin=DjangoIntegration.origin, http_methods_to_capture=integration.http_methods_to_capture, - path_includes_root_path=False, )._run_asgi3 return await middleware(scope, receive, send) @@ -152,7 +151,6 @@ async def sentry_patched_asgi_handler( unsafe_context_data=True, span_origin=DjangoIntegration.origin, http_methods_to_capture=integration.http_methods_to_capture, - path_includes_root_path=False, ) return await middleware(self.scope)(receive, send) # type: ignore diff --git a/sentry_sdk/integrations/litestar.py b/sentry_sdk/integrations/litestar.py index 9b14d1c976..f0c90a7921 100644 --- a/sentry_sdk/integrations/litestar.py +++ b/sentry_sdk/integrations/litestar.py @@ -92,7 +92,6 @@ def __init__( mechanism_type="asgi", span_origin=span_origin, asgi_version=3, - path_includes_root_path=False, ) def _capture_request_exception(self, exc: Exception) -> None: diff --git a/sentry_sdk/integrations/quart.py b/sentry_sdk/integrations/quart.py index 2b549d1201..6a5603d825 100644 --- a/sentry_sdk/integrations/quart.py +++ b/sentry_sdk/integrations/quart.py @@ -96,7 +96,6 @@ async def sentry_patched_asgi_app( lambda *a, **kw: old_app(self, *a, **kw), span_origin=QuartIntegration.origin, asgi_version=3, - path_includes_root_path=False, ) return await middleware(scope, receive, send) diff --git a/sentry_sdk/integrations/starlette.py b/sentry_sdk/integrations/starlette.py index dc2b7d00e1..eccb4f780f 100644 --- a/sentry_sdk/integrations/starlette.py +++ b/sentry_sdk/integrations/starlette.py @@ -15,6 +15,7 @@ DidNotEnable, Integration, ) +from sentry_sdk.integrations._asgi_common import _RootPathInPath from sentry_sdk.integrations._wsgi_common import ( DEFAULT_HTTP_METHODS_TO_CAPTURE, HttpCodeRangeContainer, @@ -143,10 +144,12 @@ def setup_once() -> None: ) patch_middlewares() - # Starlette's Mount includes scope["root_path"] in scope["path"] starting with: + # Starlette tolerates both starting with: # https://github.com/Kludex/starlette/commit/e8f0dcd54e4ceec47e02c45f5275374e292339ad. - path_includes_root_path = version >= (0, 33) - patch_asgi_app(path_includes_root_path=path_includes_root_path) + root_path_in_path = ( + _RootPathInPath.EITHER if version >= (0, 33) else _RootPathInPath.EXCLUDED + ) + patch_asgi_app(root_path_in_path=root_path_in_path) patch_request_response() if version >= (0, 24): @@ -430,7 +433,7 @@ def _sentry_middleware_init( Middleware.__init__ = _sentry_middleware_init -def patch_asgi_app(path_includes_root_path: "bool") -> None: +def patch_asgi_app(root_path_in_path: "_RootPathInPath") -> None: """ Instrument Starlette ASGI app using the SentryAsgiMiddleware. """ @@ -454,7 +457,7 @@ async def _sentry_patched_asgi_app( else DEFAULT_HTTP_METHODS_TO_CAPTURE ), asgi_version=3, - path_includes_root_path=path_includes_root_path, + root_path_in_path=root_path_in_path, ) return await middleware(scope, receive, send) From 557c418989afd8e1a0a090bfbd28de1714a4452a Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Fri, 26 Jun 2026 11:03:51 +0200 Subject: [PATCH 20/20] remove default arguments --- sentry_sdk/integrations/_asgi_common.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/_asgi_common.py b/sentry_sdk/integrations/_asgi_common.py index 0e97132e24..85f8f11d6d 100644 --- a/sentry_sdk/integrations/_asgi_common.py +++ b/sentry_sdk/integrations/_asgi_common.py @@ -104,7 +104,8 @@ def _get_ip(asgi_scope: "Any") -> str: def _get_request_data( - asgi_scope: "Any", root_path_in_path: "_RootPathInPath" = _RootPathInPath.EXCLUDED + asgi_scope: "Any", + root_path_in_path: "_RootPathInPath", ) -> "Dict[str, Any]": """ Returns data related to the HTTP request from the ASGI scope. @@ -135,7 +136,7 @@ def _get_request_data( def _get_request_attributes( asgi_scope: "Any", - root_path_in_path: "_RootPathInPath" = _RootPathInPath.EXCLUDED, + root_path_in_path: "_RootPathInPath", ) -> "dict[str, Any]": """ Return attributes related to the HTTP request from the ASGI scope.