Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changelog/4699.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`opentelemetry-instrumentation-fastapi`: fix ``AttributeError`` for ``_IncludedRouter`` in FastAPI v0.137.0+
Original file line number Diff line number Diff line change
Expand Up @@ -488,11 +488,38 @@ def _get_route_details(scope):
try:
route = starlette_route.path
except AttributeError:
# routes added via host routing won't have a path attribute
route = scope.get("path")
# FastAPI v0.137.0+ wraps included routers in
# _IncludedRouter objects that lack a path attribute.
# Recurse into the sub-routes to find the actual route.
sub_routes = getattr(starlette_route, "routes", None)
if sub_routes is None and hasattr(
starlette_route, "original_router"
):
sub_routes = getattr(
starlette_route.original_router, "routes", []
)
if sub_routes:
for sub in sub_routes:
sub_match, _ = (
Route.matches(sub, scope)
if isinstance(sub, Route)
else sub.matches(scope)
)
if sub_match == Match.FULL:
try:
route = sub.path
except AttributeError:
route = scope.get("path")
break
else:
# routes added via host routing won't have a path attribute
route = scope.get("path")
break
if match == Match.PARTIAL:
route = starlette_route.path
try:
route = starlette_route.path
except AttributeError:
route = scope.get("path")
return route


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,41 @@ def test_custom_api_router(self):
span.attributes[HTTP_URL],
)

def test_included_router_route_attribute(self):
"""Ensure that routes from included routers resolve the correct HTTP_ROUTE.

FastAPI v0.137.0+ wraps included routers in _IncludedRouter objects
that do not have a path attribute. The instrumentation must recurse
into the sub-routes to find the actual route path.
See: https://github.com/open-telemetry/opentelemetry-python-contrib/issues/4699
"""
from fastapi import APIRouter

app = fastapi.FastAPI()
router = APIRouter(prefix="/items")

@router.get("/{item_id}")
async def get_item(item_id: int):
return {"item_id": item_id}

app.include_router(router)
otel_fastapi.FastAPIInstrumentor.instrument_app(app)

client = TestClient(app)
resp = client.get("/items/42")
self.assertEqual(resp.status_code, 200)

spans = self.memory_exporter.get_finished_spans()
server_spans = [
span
for span in spans
if HTTP_ROUTE in span.attributes
]
self.assertTrue(len(server_spans) > 0)
self.assertEqual(
server_spans[-1].attributes[HTTP_ROUTE], "/items/{item_id}"
)

def test_host_fastapi_call(self):
client = TestClient(self._app, base_url="https://testserver2:443")
client.get("/")
Expand Down
Loading