starlette/fastapi: fix error on host-based routing (#3507)

* starlette/fastapi: fix error on host-based routing

Fix #3506

* Update CHANGELOG.md

* Update instrumentation/opentelemetry-instrumentation-starlette/tests/test_starlette_instrumentation.py

* Update CHANGELOG.md

---------

Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com>
Co-authored-by: Emídio Neto <9735060+emdneto@users.noreply.github.com>
This commit is contained in:
François Voron 2025-08-29 17:15:57 +02:00 committed by GitHub
parent e211bffc0a
commit 86d26ce1b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 53 additions and 3 deletions

View File

@ -23,6 +23,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#3679](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3679))
- `opentelemetry-instrumentation`: Avoid calls to `context.detach` with `None` token.
([#3673](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3673))
- `opentelemetry-instrumentation-starlette`/`opentelemetry-instrumentation-fastapi`: Fixes a crash when host-based routing is used
([#3507](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3507))
### Added

View File

@ -516,7 +516,11 @@ def _get_route_details(scope):
for starlette_route in app.routes:
match, _ = starlette_route.matches(scope)
if match == Match.FULL:
try:
route = starlette_route.path
except AttributeError:
# routes added via host routing won't have a path attribute
route = scope.get("path")
break
if match == Match.PARTIAL:
route = starlette_route.path

View File

@ -234,6 +234,7 @@ class TestBaseFastAPI(TestBase):
raise UnhandledException("This is an unhandled exception")
app.mount("/sub", app=sub_app)
app.host("testserver2", sub_app)
return app
@ -310,6 +311,26 @@ class TestBaseManualFastAPI(TestBaseFastAPI):
span.attributes[HTTP_URL],
)
def test_host_fastapi_call(self):
client = TestClient(self._app, base_url="https://testserver2")
client.get("/")
spans = self.memory_exporter.get_finished_spans()
spans_with_http_attributes = [
span
for span in spans
if (HTTP_URL in span.attributes or HTTP_TARGET in span.attributes)
]
self.assertEqual(1, len(spans_with_http_attributes))
for span in spans_with_http_attributes:
self.assertEqual("/", span.attributes[HTTP_TARGET])
self.assertEqual(
"https://testserver2:443/",
span.attributes[HTTP_URL],
)
class TestBaseAutoFastAPI(TestBaseFastAPI):
@classmethod

View File

@ -354,7 +354,11 @@ def _get_route_details(scope: dict[str, Any]) -> str | None:
for starlette_route in app.routes:
match, _ = starlette_route.matches(scope)
if match == Match.FULL:
try:
route = starlette_route.path
except AttributeError:
# routes added via host routing won't have a path attribute
route = scope.get("path")
break
if match == Match.PARTIAL:
route = starlette_route.path

View File

@ -18,7 +18,7 @@ from unittest.mock import patch
from starlette import applications
from starlette.responses import PlainTextResponse
from starlette.routing import Mount, Route
from starlette.routing import Host, Mount, Route
from starlette.testclient import TestClient
from starlette.websockets import WebSocket
@ -140,6 +140,24 @@ class TestStarletteManualInstrumentation(TestBase):
span.attributes[HTTP_URL],
)
def test_host_starlette_call(self):
client = TestClient(self._app, base_url="http://testserver2")
client.get("/home")
spans = self.memory_exporter.get_finished_spans()
spans_with_http_attributes = [
span
for span in spans
if (HTTP_URL in span.attributes or HTTP_TARGET in span.attributes)
]
for span in spans_with_http_attributes:
self.assertEqual("/home", span.attributes[HTTP_TARGET])
self.assertEqual(
"http://testserver2/home",
span.attributes[HTTP_URL],
)
def test_starlette_route_attribute_added(self):
"""Ensure that starlette routes are used as the span name."""
self._client.get("/user/123")
@ -294,6 +312,7 @@ class TestStarletteManualInstrumentation(TestBase):
Route("/user/{username}", home),
Route("/healthzz", health),
Mount("/sub", app=sub_app),
Host("testserver2", sub_app),
],
)