Commit eab5ee3
authored
feat(debugger): instrument FastAPI route handlers via route-table patching (#780)
## Problem
Dynamic Instrumentation (DI) instruments a function by rebinding the
module-level name (`setattr(module, name, wrapper)`). That reaches
callers who look the function up by name at call time, but **not**
web-framework route handlers: when `@app.get(...)` runs at import time,
FastAPI captures a direct reference to the handler in its route table
(`APIRoute.endpoint`, and the dispatched `APIRoute.dependant.call`). The
module-level rebind never reaches those references, so requests routed
by FastAPI call the original unwrapped handler and **no snapshot is
produced**.
DI already solves this for Flask by patching `app.view_functions`, but
had no FastAPI equivalent.
## Fix
Add `_patch_fastapi_routes` / `_patch_single_fastapi_app`, dispatched
from the existing `_patch_framework_references` (so both install **and**
restore are symmetric — `restore_function` reuses the same path with
swapped args).
For each `APIRoute` whose handler matches the target (identity match
first, then a name+`__module__` fallback, mirroring the Flask
precedent), rebind **both** `route.endpoint` and `route.dependant.call`
(the attribute the dispatcher actually invokes). The already-built
`Dependant` stays valid because the DI wrapper preserves the handler
signature via `functools.wraps`, so no rebuild is needed. Per-route
errors are isolated so patching can never break the application
(SAFETY).
Works for both **sync** route handlers (run by FastAPI in a threadpool)
and **async** handlers (event loop) — DI's existing sync/async wrapper
selection composes with the route patch.
## Tests
- **Unit tests** (`test_function_wrapper_fastapi.py`) mirroring the
Flask coverage: identity match, name+module fallback, no-op when FastAPI
absent / no app / not-a-route, and integration via
`_replace_function_in_module` and `restore_function`.
- **New `di-fastapi` contract-test app + suite** mirroring the Flask DI
suite (function-level, probe, line-level, hit-limit, coexistence,
capture-limits) plus async-function tests and **sync + async
route-handler tests** that assert a snapshot is produced.
- Added `wait_for_method_snapshots(method_name, ...)` to the DI contract
base to wait for a specific method's snapshot (avoids a batch-flush race
where another function's snapshot arrives first).
## Verification
- Distro unit tests: 18/18 wrapper tests (9 FastAPI + 9 Flask) + 92
other wrapper tests pass; pylint 10.00/10; black clean.
- FastAPI DI contract suite: 29 passed.
- Flask DI contract suite (rebuilt on the patched wheel): 24 passed — no
regression on the shared dispatch path.1 parent a6dc44a commit eab5ee3
9 files changed
Lines changed: 1683 additions & 1 deletion
File tree
- aws-opentelemetry-distro
- src/amazon/opentelemetry/distro/debugger
- tests/amazon/opentelemetry/distro/debugger
- contract-tests
- images/applications/di-fastapi
- tests/test/amazon/di
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
Lines changed: 128 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
796 | 796 | | |
797 | 797 | | |
798 | 798 | | |
| 799 | + | |
799 | 800 | | |
800 | 801 | | |
801 | 802 | | |
| |||
805 | 806 | | |
806 | 807 | | |
807 | 808 | | |
| 809 | + | |
| 810 | + | |
808 | 811 | | |
809 | 812 | | |
810 | 813 | | |
| |||
900 | 903 | | |
901 | 904 | | |
902 | 905 | | |
| 906 | + | |
| 907 | + | |
| 908 | + | |
| 909 | + | |
| 910 | + | |
| 911 | + | |
| 912 | + | |
| 913 | + | |
| 914 | + | |
| 915 | + | |
| 916 | + | |
| 917 | + | |
| 918 | + | |
| 919 | + | |
| 920 | + | |
| 921 | + | |
| 922 | + | |
| 923 | + | |
| 924 | + | |
| 925 | + | |
| 926 | + | |
| 927 | + | |
| 928 | + | |
| 929 | + | |
| 930 | + | |
| 931 | + | |
| 932 | + | |
| 933 | + | |
| 934 | + | |
| 935 | + | |
| 936 | + | |
| 937 | + | |
| 938 | + | |
| 939 | + | |
| 940 | + | |
| 941 | + | |
| 942 | + | |
| 943 | + | |
| 944 | + | |
| 945 | + | |
| 946 | + | |
| 947 | + | |
| 948 | + | |
| 949 | + | |
| 950 | + | |
| 951 | + | |
| 952 | + | |
| 953 | + | |
| 954 | + | |
| 955 | + | |
| 956 | + | |
| 957 | + | |
| 958 | + | |
| 959 | + | |
| 960 | + | |
| 961 | + | |
| 962 | + | |
| 963 | + | |
| 964 | + | |
| 965 | + | |
| 966 | + | |
| 967 | + | |
| 968 | + | |
| 969 | + | |
| 970 | + | |
| 971 | + | |
| 972 | + | |
| 973 | + | |
| 974 | + | |
| 975 | + | |
| 976 | + | |
| 977 | + | |
| 978 | + | |
| 979 | + | |
| 980 | + | |
| 981 | + | |
| 982 | + | |
| 983 | + | |
| 984 | + | |
| 985 | + | |
| 986 | + | |
| 987 | + | |
| 988 | + | |
| 989 | + | |
| 990 | + | |
| 991 | + | |
| 992 | + | |
| 993 | + | |
| 994 | + | |
| 995 | + | |
| 996 | + | |
| 997 | + | |
| 998 | + | |
| 999 | + | |
| 1000 | + | |
| 1001 | + | |
| 1002 | + | |
| 1003 | + | |
| 1004 | + | |
| 1005 | + | |
| 1006 | + | |
| 1007 | + | |
| 1008 | + | |
| 1009 | + | |
| 1010 | + | |
| 1011 | + | |
| 1012 | + | |
| 1013 | + | |
| 1014 | + | |
| 1015 | + | |
| 1016 | + | |
| 1017 | + | |
| 1018 | + | |
| 1019 | + | |
| 1020 | + | |
| 1021 | + | |
| 1022 | + | |
| 1023 | + | |
| 1024 | + | |
| 1025 | + | |
| 1026 | + | |
| 1027 | + | |
| 1028 | + | |
| 1029 | + | |
| 1030 | + | |
903 | 1031 | | |
904 | 1032 | | |
905 | 1033 | | |
| |||
0 commit comments