Skip to content

FastAPI 0.137 regression: Elastic APM Starlette middleware crashes on _IncludedRouter (path missing) #2681

Description

@NCatalani

Describe the bug:

Starting with FastAPI 0.137, the ElasticAPM Starlette/FastAPI middleware crashes with an AttributeError while resolving the transaction (route) name on every HTTP request.

FastAPI 0.137 (fastapi/fastapi#15785) changed how included routers are represented in app.routes. Instead of flattening sub-routers, router.routes now contains _IncludedRouter wrapper objects (the new iter_route_contexts() API is the supported way to walk them). These _IncludedRouter objects do not expose a .path attribute

The agent's _get_route_name() iterates over app.routes and unconditionally reads route.path:

def _get_route_name(self, scope, routes, route_name=None):
    for route in routes:
        match, child_scope = route.matches(scope)
        if match == Match.FULL:
            route_name = route.path          # <-- _IncludedRouter has no `path`
            ...
        elif match == Match.PARTIAL and route_name is None:
            route_name = route.path          # <-- same here

Because route.path is treated as non-nullable / always-present, the loop raises AttributeError: '_IncludedRouter' object has no attribute 'path', and every request returns 500 Internal Server Error.

This is the same root cause that broke prometheus-fastapi-instrumentator and OpenTelemetry, e.g. vllm-project/vllm#45597 and open-telemetry/opentelemetry-python-contrib#4700.

Traceback

INFO:     10.224.234.6:40760 - "POST /RPC2 HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/app/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/h11_impl.py", line 415, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        self.scope, self.receive, self.send
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/app/.venv/lib/python3.14/site-packages/uvicorn/middleware/proxy_headers.py", line 56, in __call__
    return await self.app(scope, receive, send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/fastapi/applications.py", line 1162, in __call__
    await super().__call__(scope, receive, send)
  File "/app/.venv/lib/python3.14/site-packages/starlette/applications.py", line 90, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/app/.venv/lib/python3.14/site-packages/elasticapm/instrumentation/packages/asyncio/starlette.py", line 48, in call
    return await wrapped(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/starlette/middleware/errors.py", line 186, in __call__
    raise exc
  File "/app/.venv/lib/python3.14/site-packages/starlette/middleware/errors.py", line 164, in __call__
    await self.app(scope, receive, _send)
  File "/app/.venv/lib/python3.14/site-packages/elasticapm/contrib/starlette/__init__.py", line 194, in __call__
    await self._request_started(request)
  File "/app/.venv/lib/python3.14/site-packages/elasticapm/contrib/starlette/__init__.py", line 244, in _request_started
    transaction_name = self.get_route_name(request) or request.url.path
                       ~~~~~~~~~~~~~~~~~~~^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/elasticapm/contrib/starlette/__init__.py", line 251, in get_route_name
    route_name = self._get_route_name(scope, routes)
  File "/app/.venv/lib/python3.14/site-packages/elasticapm/contrib/starlette/__init__.py", line 274, in _get_route_name
    route_name = route.path
                 ^^^^^^^^^^
AttributeError: '_IncludedRouter' object has no attribute 'path'

To Reproduce

  1. Install fastapi>=0.137 and elastic-apm.
  2. Create a FastAPI app that uses an included router (app.include_router(...)), so app.routes contains an _IncludedRouter entry.
  3. Add the middleware: app.add_middleware(ElasticAPM, client=apm).
  4. Send any request -> the request fails with 500 and the traceback above is logged from elasticapm/contrib/starlette/__init__.py in _get_route_name / get_route_name.

Environment

  • OS: Linux (Docker, python:3.14)
  • Python version: 3.14
  • Framework and version: FastAPI 0.137 (Starlette); served via Uvicorn (h11)
  • APM Server version: N/A (fails before sending data)
  • Agent version: elastic-apm (current)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions