Repository navigation
Report FastAPI and FastMCP errors without StarletteIntegration while tracing is off - #362
Merged
Merged
Conversation
…tracing is off While Sentry tracing is off, FastAPIBootstrapper and FastMcpBootstrapper disable sentry-sdk's StarletteIntegration (and FastApiIntegration on FastAPI) and report request errors through a pure-ASGI middleware instead, with the same sentry_lean_error_capture rule Litestar got in #361. The rule now lives in SentryLeanCaptureConfig, shared by all three configs. Closes #341.
…y test Clearing sentry-sdk's processed-integrations registry before each test made StarletteIntegration.setup_once run again for every bootstrap, and each run wraps Starlette.__call__ once more. By the end of the suite 126 wrappers were stacked, enough to overflow the stack on Python 3.11 in CI. Once an integration has been set up, the fixture now replaces its setup_once with a no-op, so the registries still read as a fresh process without patching twice.
…hutdown test_teardown_removes_filters_even_when_a_handler_close_fails added a handler whose close always raised and left it on the root logger. Its exception kept it alive until the cyclic garbage collector ran, so whether Litestar's dictConfig in a later test closed it again, and failed, depended on GC timing. The handler now raises on its first close only, and the test detaches it.
This was referenced Oct 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #341.
While Sentry tracing is off, FastAPI and FastMCP now report request errors through lite-bootstrap's own middleware instead of sentry-sdk's
StarletteIntegration(andFastApiIntegrationon FastAPI). With tracing off, those integrations still build and discard a transaction on every request. This is the Starlette half of #307, which #361 did for Litestar. #313 comes next.Changes
SentryLeanCaptureConfiginsentry_instrument.pynow holdssentry_lean_error_capture, its resolution rule and the two config warnings that feat: capture Litestar errors without LitestarIntegration while Sentry tracing is off #361 put onLitestarConfig.LitestarConfig,FastAPIConfigandFastMcpConfiginherit it. Each one names the integrations it replaces in a_sentry_lean_replaced_integrationsClassVar, which is what the "supplied integration keeps the integration" rule and the warning text read.Shared helpers in
sentry_instrument.py, used by all three frameworks:capture_request_exception, which passesclient_optionson 2.x onlyread_sentry_trace_headersdisable_sentry_integrationswarn_about_sentry_integrations_installed_earlierSENTRY_FAILED_REQUEST_STATUS_CODESThe Litestar code now uses them, with no change in behaviour.
lite_bootstrap/helpers/starlette_sentry.pyholdsStarletteSentryCaptureMiddleware, a pure-ASGI middleware for HTTP and WebSocket requests. For each request it opens an isolation scope and a request session, clears breadcrumbs, and continuessentry-traceandbaggage. It also adds an event processor and reports any exception that escapes.import_checker.is_starlette_installedguards the module.FastAPISentryInstrumentdisables both integrations and adds the middleware.FastMcpSentryInstrumentdisablesStarletteIntegrationand wraps everyhttp_app()through_postprocess_http_apps, as the OTel and Prometheus instruments do, and its teardown restoreshttp_app.MCPIntegrationis not touched.What gets reported
The rules follow
StarletteIntegration:handled: False.handled: True, only when it carries an intstatus_codefrom 500 to 599. SoHTTPException(503)is reported,HTTPException(404)is not, and a custom exception that a handler turns into a 503 is not reported unless the exception itself has thatstatus_code.Starlette offers no hook for the second rule. The integration patches
ExceptionMiddleware.__init__for every app in the process. The lean middleware instead finds theExceptionMiddlewarebelow it when Starlette builds the stack, by following.app, and wraps that one instance's_exception_handlers. The stack is built on the first request, so handlers added after bootstrap are included. Each wrapper keeps Starlette's sync/async split, so sync handlers still run in the threadpool. A middleware added before bootstrap that keeps its inner app anywhere but.apphidesExceptionMiddleware, and then only escaping exceptions are reported. A test pins that, and the docs say so.Request data
Request data is gated by
data_collection, as it is for Litestar:_get_request_data(with_RootPathInPath.EITHER, asStarletteIntegrationpasses on Starlette 0.33 and later) supplies the method, URL, query string and headers, plus the client address whenuser_infois on._apply_key_value_collection_filtering.scope["user"](username,id,email) whenuser_infois on.scope["route"].pathwith sourceroute. A transaction name already on the event wins.Departures from the issue
Requestinstance, which a middleware never sees. So whenhttp_bodiesincludes"incoming_request", the middleware wrapsreceiveand keeps the chunks ofhttp.requestmessages. A body is attached only once the application has read all of it. With the defaulthttp_bodies=[],receiveis not wrapped. JSON and URL-encoded bodies are attached parsed, multipart becomesAnnotatedValue.removed_because_raw_data(), and anything else, including JSON that fails to parse, is attached as text, as the Litestar path does. 2.x's integration annotates raw bodies instead of attaching them. Neither path checksmax_request_body_size.conftest.py. It removes the framework integrations from sentry-sdk's process-wide registries and now coverslitestar,starletteandfastapifor every test. Without it,test_fastapi_bootstrap.pysaw the new "installed by an earlier init" warning. Clearing the registries makes sentry-sdk runsetup_onceagain, which wraps the framework once more each time; across the suite that stacked 126 wrappers onStarlette.__call__and overflowed the stack on Python 3.11 in CI. So once an integration has been set up, the fixture replaces itssetup_oncewith a no-op for later tests, and the patches from the first run stay in place. A second autouse fixture gives each test fresh current and isolation scopes: an in-memory FastMCP client test left atools/call ...transaction name on the main context's scope, and every later event picked it up.benchmarks/sentry_scenarios.py. It callssentry_sdk.initdirectly, so lean capture cannot change its numbers. The FastAPI and FastMCP suites measure lite-bootstrap itself, and they were rerun instead (below).Correction to #361
The #361 description said
sentry_nointegration"disables the integration and adds nothing else". That was wrong: the scenario leftsentry_lean_error_captureat its default, so lean capture was still on. It now also setssentry_lean_error_capture=False, and the same scenario added for FastAPI and FastMCP means no request capture at all. Remeasured on Litestar:sentry+6.7 / +6.8 µs andsentry_nointegration+0.3 / +0.0 µs (2.73.0 / 3.0.0b2). So Litestar's lean middleware costs about 6.7 µs, not "almost nothing".benchmarks/README.md§8b records the rerun.Tests
tests/test_starlette_sentry_capture.pywas written first. Against main's code, 15 of its 36 tests failed, among them:test_lean_capture_is_the_default_on_fastapi_while_tracing_is_offand its FastMCP twin:starletteandfastapiwere active.""for a raw body.sentry_lean_error_capture.The rest passed on main, which shows lean capture matches the integration on those points:
HTTPExceptionreportedNew tests cover:
functools.partialhandler.apphttp_apptest_fastapi_bootstrapper_runs_without_sentry_sdkandtest_fastmcp_bootstrapper_runs_without_sentry_sdkreload each module with sentry-sdk missing, as #361's Litestar test does.The full suite passes, 537 tests at 100% coverage, on sentry-sdk 2.73.0 and 3.0.0b2.
typasses against both, and so dojust lint,just adr-check,just docs-buildand lychee.The
floorssteps pass locally forfastapiandfastmcpon 3.11 and 3.14 (sentry-sdk 2.70.0, fastapi 0.133.0, fastmcp 3.0.0, starlette 1.10.0).floor_smoke.pynow sends one failing request through lean capture for each, without a test client, because the floors don't install httpx. The FastMCP request runs in its own Sentry-only bootstrap. The full one exports metrics to an absent collector and already takes about 65 s at this floor on main.CI's floors only cover
fastapi-all, which raises fastapi to 0.133, so I checked thefastapi-sentryfloor by hand: fastapi 0.112.2, starlette 0.38.6, sentry-sdk 2.70.0. There the transaction is/items/{item_id}andHTTPException(503)is reported as handled.Measurement
In-process on an M2 Mac (CPython 3.14.7, fastapi 0.162.0, fastmcp 4.1.4, starlette 1.10.0), in µs over the same app without Sentry:
sentry(lean, the new default)sentry_sampled_tracesentry_nointegration(no capture)sentry_integration(sentry_lean_error_capture=False)sentry_integration_sampled_traceFastMCP is per
tools/callover a base of about 620 µs, and its rounds differed by up to 15%. The FastAPI row with the integration and a sampled trace on 3.0.0b2 varied by 40% between rounds.Docs
docs/integrations/fastapi.mdandfastmcp.mdget a Sentry section: the rule, what is reported, the request data, the ExceptionMiddleware lookup and its limit, uvicorn and Granian, and the numbers.configuration.md: thesentry_lean_error_capturebullet names all three frameworks. The "second most expensive instrument" paragraph is now in the past tense.performance.md: the "savings applied for you" paragraph lists lean capture, and the FastAPI and FastMCP sections give the 2.0 numbers.benchmarks/README.md: rows measured with the old defaults are relabelled "1.x defaults", and §4d, §8b and §8e get the rerun. It also notes thatsentry_noautono longer stops request capture, which Default sentry_auto_enabling_integrations to False together with lean capture #313 relies on.Breaking changes
These apply to FastAPI and FastMCP services with a
sentry_dsnand no tracing option.http_bodiesincludes"incoming_request"and the handler read all of it. On 2.x the integration read the body before the handler ran.Set
sentry_lean_error_capture=Falseto keep the integrations.