Skip to content

INSTA-118934 fix(asgi): resolve path template for routers added with include_router - #907

Merged
GSVarsha merged 1 commit into
instana:mainfrom
adityaanikam:906-asgi-included-router-path-tpl
Oct 12, 2026
Merged

GSVarsha merged 1 commit into
instana:mainfrom
adityaanikam:906-asgi-included-router-path-tpl

Conversation

@adityaanikam

@adityaanikam adityaanikam commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #906

With FastAPI 0.137 and later, routers added with include_router() are kept in a lazy _IncludedRouter wrapper that has no path attribute. InstanaASGIMiddleware._collect_kvs read route.path for the matched route, so it raised AttributeError for every request to those endpoints. The exception is swallowed and logged at debug level, and http.path_tpl was never set, so those endpoints lost their path template.

This flattens the app's routes before matching, with a new _iter_routes helper. It uses FastAPI's public iter_route_contexts() (0.137.2 and later), falls back to effective_route_contexts() on the wrapper for 0.137.0 and 0.137.1, and leaves older FastAPI versions and plain Starlette apps unchanged, since their routes are already flat. Each flattened route carries its full templated path, including every router prefix, so routers nested several levels deep are covered. The private _match() is no longer used. Match and iter_route_contexts are imported at the top of the module behind ImportError guards, so Starlette and FastAPI stay optional and instana.middleware still imports without them.

Testing: added test_path_templates_with_included_routers with a single level and a nested include_router() route, and test_iter_routes_without_iter_route_contexts for the fallback branches. The two router cases fail on the unmodified code (no path_tpl on the span) and pass with the change. Ran tests/frameworks/test_fastapi.py against FastAPI 0.136.1, 0.137.0, 0.137.1 and 0.141.1 (14 passed each), and test_fastapi_middleware.py, test_starlette.py and test_starlette_middleware.py with 0.141.1 (12 passed). ruff check is clean on the changed files, and ruff format --check on asgi.py and test_fastapi.py.

@adityaanikam
adityaanikam requested a review from a team as a code owner September 28, 2026 10:42
Copilot AI lite review requested due to automatic review settings September 28, 2026 10:42

This comment was marked as resolved.

@CagriYonca CagriYonca left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few comments, then it looks good

Comment thread src/instana/instrumentation/asgi.py Outdated
Comment thread src/instana/instrumentation/asgi.py Outdated

@GSVarsha GSVarsha left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @adityaanikam,

Thanks for working on this bug!

I would suggest using the public helper iter_route_contexts in fastapi.routing introduced in FastAPI ≥ 0.137.2 that flattens the _IncludedRouter tree into matchable route context objects, each carrying the full templated path. For FastAPI 0.137.0–0.137.1 (which lack iter_route_contexts but still have _IncludedRouter), effective_route_contexts() on the router node serves as a fallback. For FastAPI < 0.137, routes are already flat and need no special handling.

This would avoid relying on _match(), which is a private Starlette method with no stability guarantee — a future Starlette or FastAPI release could rename it, change its return type, or remove it entirely, breaking path_tpl collection silently for all requests without any warning at startup or runtime.

For more details, refer the official documentation

@adityaanikam
adityaanikam force-pushed the 906-asgi-included-router-path-tpl branch from cfdf4c8 to a4aa388 Compare October 1, 2026 11:36
@adityaanikam

Copy link
Copy Markdown
Contributor Author

Thanks both, I reworked it along both reviews. _get_path_template and the private _match() are gone. _collect_kvs now iterates _iter_routes(app.routes), which uses fastapi.routing.iter_route_contexts (0.137.2 and later), falls back to effective_route_contexts() on the wrapper for 0.137.0 and 0.137.1, and leaves older FastAPI versions and plain Starlette routes as they are.

Match is now imported at the top of the module. I kept it behind an ImportError guard because instana.middleware exports this middleware for non-Starlette ASGI apps too, and the in-function import is removed.

I ran the FastAPI tests against 0.136.1, 0.137.0, 0.137.1 and 0.141.1, and the included-router cases fail on the unmodified code.

@CagriYonca

Copy link
Copy Markdown
Contributor

Hello again @adityaanikam , could you rebase your code to the main? CI tests will pass, then we can merge it.

Thanks a lot for your efforts!

@adityaanikam
adityaanikam force-pushed the 906-asgi-included-router-path-tpl branch from a4aa388 to bc03d4b Compare October 2, 2026 18:05

@CagriYonca CagriYonca left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the changes, it looks good to me now. Let's wait until @GSVarsha also reviews, then we can merge.

Comment thread src/instana/instrumentation/asgi.py
Comment thread src/instana/instrumentation/asgi.py

@GSVarsha GSVarsha left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few more suggestions

@GSVarsha GSVarsha self-assigned this Oct 5, 2026
@GSVarsha GSVarsha changed the title fix(asgi): resolve path template for routers added with include_router INSTA-118934 fix(asgi): resolve path template for routers added with include_router Oct 5, 2026
@GSVarsha GSVarsha modified the milestone: H1-2026 Oct 7, 2026
@adityaanikam
adityaanikam force-pushed the 906-asgi-included-router-path-tpl branch from bc03d4b to 309703e Compare October 7, 2026 10:07
@adityaanikam

Copy link
Copy Markdown
Contributor Author

Thanks for the review.

I added the break after the first full match, together with a test for it (test_path_template_of_first_full_match_wins). With two routes that both match the same path, the template of the first one is kept.

I kept the guarded import of Match on purpose. instana.middleware re-exports InstanaASGIMiddleware for any ASGI app, and Starlette is only listed in the test requirements, not in the package dependencies. On main the import sits inside _collect_kvs, under the try. A module-level from starlette.routing import Match without a guard would raise ImportError on import instana.middleware for apps that do not use Starlette. If you would rather not have the guard, I can move the import back into _collect_kvs as it is on main.

Comment thread src/instana/instrumentation/asgi.py Outdated
@adityaanikam
adityaanikam force-pushed the 906-asgi-included-router-path-tpl branch from 309703e to d483e11 Compare October 9, 2026 11:21
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown

❌ @adityaanikam the signed-off-by was not found in the following 1 commits:

  • d483e11: fix(asgi): guard the Match lookup when Starlette is not installed

📝 What should I do to fix it?

All proposed commits should include a sign-off in their messages, ideally at the end.

❔ Why it is required

The Developer Certificate of Origin (DCO) is a lightweight way for contributors to certify that they wrote or otherwise have the right to submit the code they are contributing to the project. Here is the full text of the DCO, reformatted for readability:

By making a contribution to this project, I certify that:

a. The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or

b. The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or

c. The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it.

d. I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved.

Contributors sign-off that they adhere to these requirements by adding a Signed-off-by line to commit messages.

This is my commit message

Signed-off-by: Random Developer <randomdeveloper@example.com>

Git even has a -s command line option to append this automatically to your commit message:

$ git commit -s -m 'This is my commit message'

@adityaanikam
adityaanikam force-pushed the 906-asgi-included-router-path-tpl branch from d483e11 to 4e4cdb7 Compare October 9, 2026 11:25
@adityaanikam

Copy link
Copy Markdown
Contributor Author

Added the explicit Match is not None guard and rebased on main. The FastAPI test failures came from main now expecting the extra OpenTelemetry spans, so the new included-router test no longer asserts a span count.

@CagriYonca

Copy link
Copy Markdown
Contributor

Hello @adityaanikam ,

Could you rebase onto the main branch? FastAPI issues will be solved, then you can re-add span count assertions.

FastAPI 0.137 stores routers added with include_router() in a lazy wrapper that has no path attribute. _collect_kvs read route.path unconditionally, so it raised AttributeError, which was swallowed and logged at debug level, and http.path_tpl was never set for those endpoints.

Flatten the routes with FastAPI's public iter_route_contexts() (0.137.2 and later), or with effective_route_contexts() on the wrapper for 0.137.0 and 0.137.1, so every route carries its fully prefixed path. Older FastAPI versions and Starlette keep their routes flat and are handled as before.

Fixes instana#906

Signed-off-by: adityaanikam <adityanikam9502@gmail.com>
@adityaanikam
adityaanikam force-pushed the 906-asgi-included-router-path-tpl branch from 4e4cdb7 to d9271a7 Compare October 9, 2026 19:41
@adityaanikam

Copy link
Copy Markdown
Contributor Author

Rebased on main and re-added the span count assertion to the included router test: 5 spans, the asgi span plus the four FastAPI OTel spans. It now also checks them with assert_fastapi_otel_spans.

@GSVarsha GSVarsha left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @adityaanikam for making all the necessary changes. The PR looks good to me :)

@GSVarsha
GSVarsha merged commit ab2a67d into instana:main Oct 12, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: ASGI instrumentation crashes with AttributeError on FastAPI ≥ 0.137.0 (_IncludedRouter has no attribute 'path')

5 participants