Skip to content

Commit 4e4e02e

Browse files
authored
Report FastAPI and FastMCP errors without StarletteIntegration while tracing is off (#362)
* Report FastAPI and FastMCP errors without StarletteIntegration while 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. * Stop the integration reset fixture from re-patching Starlette in every 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. * Keep the exploding test handler from failing a later test's logging shutdown 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.
1 parent 02f0332 commit 4e4e02e

22 files changed

Lines changed: 1388 additions & 95 deletions

‎benchmarks/README.md‎

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ structlog 26.1.0. In-process, baseline 17.7 µs per request. The handler does no
210210
| OTel 0% + OTel HTTP metrics (`opentelemetry_metrics_endpoint`) | +78.9 | +12.3 over OTel 0% |
211211
| OTel 0% + Prometheus | +91.5 | +24.9 over OTel 0% |
212212
| OTel 0% + OTel HTTP metrics + Prometheus | +104.9 | +38.3 over OTel 0% |
213-
| Sentry, lite-bootstrap defaults (tracing off) | +59.0 to +59.7 | two runs |
213+
| Sentry, lite-bootstrap 1.x defaults (tracing off) | +59.0 to +59.7 | two runs |
214214
| Sentry, `sentry_auto_session_tracking=False` | +51.8 | |
215215
| Sentry, `auto_enabling_integrations=False` | +0.1 | the Starlette and FastAPI integrations are auto-enabling, so nothing captures request errors |
216216
| health check `/health/` | +22.6 | noisy, 16% spread |
@@ -219,6 +219,23 @@ structlog 26.1.0. In-process, baseline 17.7 µs per request. The handler does no
219219
| full: OTel + Prometheus + logging + Sentry + health, defaults | +259.6 | |
220220
<!-- --8<-- [end:fastapilinux] -->
221221

222+
Since 2.0, lite-bootstrap reports FastAPI request errors itself while Sentry tracing is off, without
223+
`StarletteIntegration` and `FastApiIntegration`. Rerun in-process on the M2 host (macOS, CPython
224+
3.14.7, fastapi 0.162.0, starlette 1.10.0,
225+
`bench.py fastapi bare,sentry,sentry_sampled_trace,sentry_nointegration,sentry_integration,sentry_integration_sampled_trace --repeat 2`):
226+
227+
| scenario | sentry-sdk 2.73.0 | 3.0.0b2 |
228+
|---|---:|---:|
229+
| `sentry`: lean capture, the 2.0 default | +6.1 | +5.1 |
230+
| `sentry_sampled_trace`: the same, request carries a sampled `sentry-trace` | +11.5 | +10.9 |
231+
| `sentry_nointegration`: both integrations disabled, no request capture at all | +0.1 | -0.2 |
232+
| `sentry_integration`: `sentry_lean_error_capture=False` | +57.1 | +47.0 |
233+
| `sentry_integration_sampled_trace` | +59.2 | +113.2 |
234+
235+
The last row on 3.0.0b2 varied by 40% between rounds. The table above was measured before 2.0. Its
236+
`sentry_nosess` row now has the same config as `sentry`, and `sentry_noauto` no longer stops request
237+
capture, because lean capture needs no auto-enabling integration.
238+
222239
FastAPI real-server runs under uvicorn in the container were unstable (bare repeat medians of 8235
223240
and 6341 RPS, over 100% spread on the full stack), so the Linux real-server numbers below are
224241
Litestar on Granian only.
@@ -398,7 +415,9 @@ Since 2.0, lite-bootstrap reports Litestar request errors itself while Sentry tr
398415
| `sentry_integration_sampled_trace` | +74.4 | +147.4 |
399416

400417
On 3.0 an incoming sampled `sentry-trace` makes `LitestarIntegration` record a real span even with
401-
tracing off. The table above was measured before 2.0. Its `sentry_nosess` scenario now has the same
418+
tracing off. `sentry_nointegration`, which disables `LitestarIntegration` and turns lean capture off,
419+
so nothing captures request errors, measured +0.3 and +0.0 in a later run that put `sentry` at
420+
+6.7 and +6.8. The table above was measured before 2.0. Its `sentry_nosess` scenario now has the same
402421
config as `sentry`, because sessions are off by default since 2.0.
403422

404423
lite-bootstrap's Litestar defaults with no endpoint configured (Prometheus, health, logging and
@@ -498,14 +517,30 @@ it FastMCP and the MCP SDK. Noise on this suite is about ±30 µs even with 3 re
498517
| OpenTelemetry, 100% | +272 | 3 ASGI spans plus FastMCP's own server span |
499518
| OpenTelemetry, 100%, FastMCP's own spans replaced by a no-op tracer | +226 | FastMCP's spans cost 46 |
500519
| OpenTelemetry, 10% | +120 | |
501-
| Sentry, defaults | +100 | the Starlette integration |
520+
| Sentry, 1.x defaults | +100 | the Starlette integration |
502521
| Sentry, MCPIntegration disabled | +99 | MCPIntegration costs nothing measurable with tracing off |
503522
| Sentry, `auto_enabling_integrations=False` | +3 | no Starlette integration, so no request capture |
504523
| full: OTel + Prometheus + logging + Sentry + health | +521 | |
505524
<!-- --8<-- [end:fastmcp] -->
506525

507526
The no-op tracer row patches FastMCP to attribute the cost; it is not a configuration.
508527

528+
Since 2.0, lite-bootstrap reports errors in every application `http_app()` builds itself while
529+
Sentry tracing is off, without `StarletteIntegration`. Rerun in-process on the M2 host (macOS,
530+
CPython 3.14.7, fastmcp 4.1.4, starlette 1.10.0,
531+
`bench.py fastmcp bare,sentry,sentry_sampled_trace,sentry_nointegration,sentry_integration,sentry_integration_sampled_trace --repeat 3 --ops 1500 --warmup 300`),
532+
over a baseline of about 620 µs per tool call:
533+
534+
| scenario | sentry-sdk 2.73.0 | 3.0.0b2 |
535+
|---|---:|---:|
536+
| `sentry`: lean capture, the 2.0 default | +20.6 | +15.9 |
537+
| `sentry_sampled_trace` | +16.1 | +18.9 |
538+
| `sentry_nointegration`: `StarletteIntegration` disabled, no request capture at all | -0.3 | +1.8 |
539+
| `sentry_integration`: `sentry_lean_error_capture=False` | +82.2 | +65.0 |
540+
| `sentry_integration_sampled_trace` | +82.3 | +216.4 |
541+
542+
Rounds differed by up to 15%, so the lean rows are not resolved from each other.
543+
509544
### 8f. Logging, per call
510545

511546
The per-call chain is the same on every framework: structlog's stdlib `BoundLogger`, lite-bootstrap's

‎benchmarks/frameworks/common.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,6 @@ def sentry(**extra_params: object) -> dict[str, typing.Any]:
167167
LOG: typing.Final = {"logging_enabled": True}
168168
CORS: typing.Final = {"cors_allowed_origins": ["https://example.com"], "cors_allowed_methods": ["GET"]}
169169
ORIGIN: typing.Final[Headers] = ((b"origin", b"https://example.com"),)
170+
SAMPLED_SENTRY_TRACE: typing.Final[Headers] = (
171+
(b"sentry-trace", b"771a43a4192642f0b136d5159a501700-1234567890abcdef-1"),
172+
)

‎benchmarks/frameworks/fastapi_suite.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
OTEL,
1111
OTEL_METRICS,
1212
PROM,
13+
SAMPLED_SENTRY_TRACE,
1314
ASGIApp,
1415
Cleanup,
1516
Op,
@@ -22,6 +23,8 @@
2223
start_lifespan,
2324
)
2425
from fastapi import FastAPI
26+
from sentry_sdk.integrations.fastapi import FastApiIntegration
27+
from sentry_sdk.integrations.starlette import StarletteIntegration
2528

2629
from lite_bootstrap import FastAPIBootstrapper, FastAPIConfig
2730

@@ -44,6 +47,16 @@
4447
"sentry": Scenario(config=base_config(**sentry())),
4548
"sentry_nosess": Scenario(config=base_config(**sentry(), sentry_auto_session_tracking=False)),
4649
"sentry_noauto": Scenario(config=base_config(**sentry(auto_enabling_integrations=False))),
50+
"sentry_nointegration": Scenario(
51+
config=base_config(
52+
**sentry(disabled_integrations=[StarletteIntegration, FastApiIntegration]), sentry_lean_error_capture=False
53+
)
54+
),
55+
"sentry_integration": Scenario(config=base_config(**sentry(), sentry_lean_error_capture=False)),
56+
"sentry_sampled_trace": Scenario(config=base_config(**sentry()), headers=SAMPLED_SENTRY_TRACE),
57+
"sentry_integration_sampled_trace": Scenario(
58+
config=base_config(**sentry(), sentry_lean_error_capture=False), headers=SAMPLED_SENTRY_TRACE
59+
),
4760
"health": Scenario(config=base_config(**HEALTH), path="/health/"),
4861
"health_otel": Scenario(config=base_config(**HEALTH, **OTEL), path="/health/"),
4962
"health_otel_nospan": Scenario(

‎benchmarks/frameworks/fastmcp_suite.py‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
LOG,
1313
OTEL,
1414
PROM,
15+
SAMPLED_SENTRY_TRACE,
1516
Cleanup,
1617
Op,
1718
Scenario,
@@ -25,6 +26,7 @@
2526
from fastmcp import FastMCP
2627
from opentelemetry.trace import NoOpTracer, Tracer
2728
from sentry_sdk.integrations.mcp import MCPIntegration
29+
from sentry_sdk.integrations.starlette import StarletteIntegration
2830

2931
from lite_bootstrap import FastMcpBootstrapper, FastMcpConfig
3032

@@ -49,6 +51,14 @@ def no_fastmcp_spans() -> None:
4951
"sentry": Scenario(config=base_config(**sentry())),
5052
"sentry_nomcp": Scenario(config=base_config(**sentry(disabled_integrations=[MCPIntegration]))),
5153
"sentry_noauto": Scenario(config=base_config(**sentry(auto_enabling_integrations=False))),
54+
"sentry_nointegration": Scenario(
55+
config=base_config(**sentry(disabled_integrations=[StarletteIntegration]), sentry_lean_error_capture=False)
56+
),
57+
"sentry_integration": Scenario(config=base_config(**sentry(), sentry_lean_error_capture=False)),
58+
"sentry_sampled_trace": Scenario(config=base_config(**sentry()), headers=SAMPLED_SENTRY_TRACE),
59+
"sentry_integration_sampled_trace": Scenario(
60+
config=base_config(**sentry(), sentry_lean_error_capture=False), headers=SAMPLED_SENTRY_TRACE
61+
),
5262
"full": Scenario(config=base_config(**OTEL, **PROM, **LOG, **sentry(), health_checks_enabled=True)),
5363
}
5464

@@ -75,5 +85,5 @@ def add(a: int, b: int) -> int:
7585
application = FastMcpBootstrapper(FastMcpConfig(application=mcp, **scenario.config)).bootstrap()
7686
http_app = application.http_app(stateless_http=True, json_response=True)
7787
stop = await start_lifespan(http_app)
78-
scope = http_scope("/mcp", method="POST", extra_headers=HEADERS)
88+
scope = http_scope("/mcp", method="POST", extra_headers=(*HEADERS, *scenario.headers))
7989
return asgi_op(http_app, scope, body=CALL_BODY), stop

‎benchmarks/frameworks/litestar_suite.py‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
OTEL,
1717
OTEL_METRICS,
1818
PROM,
19+
SAMPLED_SENTRY_TRACE,
1920
ASGIApp,
2021
Cleanup,
2122
Op,
@@ -38,7 +39,6 @@
3839
HEALTH: typing.Final = {"health_checks_enabled": True}
3940
LITESTAR_DEFAULTS: typing.Final = {**PROM, **HEALTH, **LOG, "swagger_path": "/docs"}
4041
FULL: typing.Final = {**OTEL, **PROM, **LOG, **sentry(), **HEALTH}
41-
SAMPLED_SENTRY_TRACE: typing.Final = ((b"sentry-trace", b"771a43a4192642f0b136d5159a501700-1234567890abcdef-1"),)
4242

4343
SCENARIOS: typing.Final = {
4444
"bare": Scenario(config=base_config()),
@@ -63,7 +63,9 @@
6363
"sentry_nosess": Scenario(config=base_config(**sentry(), sentry_auto_session_tracking=False)),
6464
"sentry_noauto": Scenario(config=base_config(**sentry(auto_enabling_integrations=False))),
6565
"sentry_nodefault": Scenario(config=base_config(**sentry(), sentry_default_integrations=False)),
66-
"sentry_nointegration": Scenario(config=base_config(**sentry(disabled_integrations=[LitestarIntegration]))),
66+
"sentry_nointegration": Scenario(
67+
config=base_config(**sentry(disabled_integrations=[LitestarIntegration]), sentry_lean_error_capture=False)
68+
),
6769
"sentry_integration": Scenario(config=base_config(**sentry(), sentry_lean_error_capture=False)),
6870
"sentry_sampled_trace": Scenario(config=base_config(**sentry()), headers=SAMPLED_SENTRY_TRACE),
6971
"sentry_integration_sampled_trace": Scenario(

‎docs/integrations/fastapi.md‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,64 @@ application = bootstrapper.bootstrap()
4747

4848
Read more about available configuration options [here](../introduction/configuration.md).
4949

50+
## Sentry
51+
52+
While Sentry tracing is off, lite-bootstrap reports request errors itself instead of through
53+
sentry-sdk's `StarletteIntegration` and `FastApiIntegration`. Tracing counts as off when
54+
`sentry_traces_sample_rate` is unset and `sentry_additional_params` sets none of
55+
`traces_sample_rate`, `traces_sampler` and `enable_tracing`. With tracing off, the integrations
56+
still build a transaction for every request and then discard it
57+
([getsentry/sentry-python#7400](https://github.com/getsentry/sentry-python/issues/7400)).
58+
59+
`sentry_lean_error_capture` picks the path:
60+
61+
- `None`, the default: lean capture while tracing is off and `sentry_integrations` holds neither
62+
integration, the integrations otherwise.
63+
- `True`: lean capture always. lite-bootstrap warns when tracing is on, since no request starts a
64+
transaction, and when `sentry_integrations` holds either integration, which gets dropped.
65+
- `False`: the integrations always.
66+
67+
Lean capture passes both integrations in `disabled_integrations` and adds one pure-ASGI middleware.
68+
It gives each HTTP and WebSocket request its own Sentry scope, and its own release-health session
69+
when `sentry_auto_session_tracking` is on. It continues an incoming `sentry-trace` and `baggage`,
70+
and attaches the request to every event sent while the request runs.
71+
72+
The request data follows the same [`data_collection`](../introduction/configuration.md#sentry-data-collection)
73+
settings the integrations read. Method, URL, query string, headers and cookies are filtered by
74+
their denylists. The event's transaction is the route template, such as `/items/{item_id}`. The
75+
client address and the user that Starlette's `AuthenticationMiddleware` sets are attached only
76+
when `user_info` is on. A body is attached only when `http_bodies` includes `"incoming_request"`
77+
and the handler has read all of it: lean capture copies what the application reads and never
78+
reads the stream itself. JSON and URL-encoded form bodies are attached parsed, a multipart body is
79+
replaced by a placeholder, and any other body is attached as text. On sentry-sdk 2.x the
80+
integrations read the body before the handler runs.
81+
82+
What is reported matches the integrations. An exception that escapes the application is reported.
83+
An exception that an exception handler answers is reported only when it carries a `status_code`
84+
from 500 to 599, as `HTTPException(503)` does, whatever the handler responds. So a 404 from
85+
`HTTPException` is not reported, and neither is a custom exception your handler turns into a 503
86+
unless the exception itself has a 5xx `status_code`. To find those handlers, lean capture looks
87+
through the middleware below it for Starlette's `ExceptionMiddleware`. A middleware added before
88+
bootstrap that keeps its inner application anywhere but `.app` hides it, and then only escaping
89+
exceptions are reported.
90+
91+
uvicorn and Granian log an exception that escapes the application. sentry-sdk's dedupe integration
92+
drops that log event, because it carries the same exception, so each failing request sends one
93+
event either way.
94+
95+
Measured in-process on a do-nothing handler, over the same app without Sentry:
96+
97+
| Sentry setup | sentry-sdk 2.73.0 | 3.0.0b2 |
98+
|---|---:|---:|
99+
| lean capture, the default | +6.1 µs | +5.1 µs |
100+
| the same, request carries a sampled `sentry-trace` | +11.5 µs | +10.9 µs |
101+
| `sentry_lean_error_capture=False` | +57.1 µs | +47.0 µs |
102+
| the same, request carries a sampled `sentry-trace` | +59.2 µs | +113.2 µs |
103+
104+
sentry-sdk records installed integrations for the whole process, and `disabled_integrations`
105+
cannot remove one that an earlier `sentry_sdk.init` installed. If that happened, lite-bootstrap
106+
warns that errors may be reported twice.
107+
50108
## Logging
51109

52110
Structlog is configured process-wide, so `structlog.get_logger()` works in any route handler.

‎docs/integrations/fastmcp.md‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,28 @@ nanoseconds. A message that raises is logged at exception level and the exceptio
6464

6565
Set `health_checks_enabled=False` to omit the health route.
6666

67+
## Sentry
68+
69+
While Sentry tracing is off, lite-bootstrap reports errors in every application
70+
`application.http_app()` builds itself, instead of through sentry-sdk's `StarletteIntegration`.
71+
FastMCP's HTTP application is a Starlette application, so it works as it does
72+
[on FastAPI](fastapi.md#sentry): the same `sentry_lean_error_capture` rule, the same middleware and
73+
the same request data. `MCPIntegration` is left alone.
74+
75+
A tool that raises does not fail the request: FastMCP answers with an error result and logs the
76+
exception, and the logging integration turns that record into one event with the `/mcp` request
77+
attached, with lean capture on or off.
78+
79+
Measured in-process per `tools/call` over stateless JSON HTTP, over the same app without Sentry:
80+
81+
| Sentry setup | sentry-sdk 2.73.0 | 3.0.0b2 |
82+
|---|---:|---:|
83+
| lean capture, the default | +20.6 µs | +15.9 µs |
84+
| `sentry_lean_error_capture=False` | +82.2 µs | +65.0 µs |
85+
| the same, request carries a sampled `sentry-trace` | +82.3 µs | +216.4 µs |
86+
87+
A tool call costs about 620 µs before Sentry, and rows differ by up to 15% between runs.
88+
6789
## Metrics
6890

6991
With the `fastmcp-metrics` extra installed, the registry is served at `prometheus_metrics_path`,

‎docs/introduction/configuration.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@ Additional parameters can also be supplied through the settings object:
2222
- `sentry_additional_params` - additional params, which will be passed to `sentry_sdk.init`, overriding any of the above that map to the same keyword
2323
- `sentry_default_integrations` - whether to use sentry's default integrations (default: `True`)
2424
- `sentry_before_send` - optional callback chained after the built-in structlog enricher, passed to `sentry_sdk.init(before_send=...)`
25-
- `sentry_lean_error_capture` - Litestar only: report request errors without Sentry's `LitestarIntegration` (default: `None`, meaning on while Sentry tracing is off). See [Litestar](../integrations/litestar.md#sentry).
25+
- `sentry_lean_error_capture` - Litestar, FastAPI and FastMCP: report request errors without sentry-sdk's framework integration (default: `None`, meaning on while Sentry tracing is off). See [Litestar](../integrations/litestar.md#sentry), [FastAPI](../integrations/fastapi.md#sentry) and [FastMCP](../integrations/fastmcp.md#sentry).
2626

2727
Read more about sentry_sdk params [here](https://docs.sentry.io/platforms/python/configuration/options/).
2828

29-
Sentry is the second most expensive instrument in the stack. Its cost comes almost entirely from the ASGI
30-
integration; see [Performance](performance.md).
29+
Before 2.0, Sentry was the second most expensive instrument in the stack, and its cost came almost
30+
entirely from sentry-sdk's framework integration. Since 2.0, lite-bootstrap replaces that integration on
31+
Litestar, FastAPI and FastMCP while Sentry tracing is off; see [Performance](performance.md).
3132

3233
### Sentry logging integration
3334

‎docs/introduction/performance.md‎

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ Four things here are worth knowing before you tune anything:
3939

4040
`attach_stacktrace`, `max_breadcrumbs` and dropping the default integrations all measure inside
4141
noise. Essentially the entire cost is the ASGI integration, and over half of that is a `Transaction`
42-
built and discarded because `sentry_traces_sample_rate` is unset.
42+
built and discarded because `sentry_traces_sample_rate` is unset. That table configures
43+
sentry-sdk directly. Since 2.0, lite-bootstrap leaves the framework integration out while tracing
44+
is off and reports request errors itself, which costs about 6 µs per request on FastAPI
45+
([FastAPI: Sentry](../integrations/fastapi.md#sentry)).
4346

4447
## If you are RPS-constrained, start here
4548

@@ -65,13 +68,15 @@ request, every request then pays the per-record cost measured here:
6568

6669
--8<-- "benchmarks/README.md:logging"
6770

68-
lite-bootstrap already applies three savings for you. It turns Sentry Logs off in the logging
71+
lite-bootstrap already applies four savings for you. It turns Sentry Logs off in the logging
6972
integration, because it never enables them and on 2.x the handler formats every record before
7073
checking whether they are enabled; that one costs nothing. Since 2.0 it also leaves release-health
7174
sessions off and starts breadcrumbs at `WARNING`, so an `INFO` record skips the breadcrumb handler.
7275
Those two cost you release health and `INFO` breadcrumbs on error events; set
7376
`sentry_auto_session_tracking=True` or `sentry_logging_breadcrumb_level=logging.INFO` to get them
74-
back. The numbers on this page were measured before 2.0, against the old defaults.
77+
back. And while Sentry tracing is off it reports request errors without sentry-sdk's framework
78+
integration, which costs you request transactions and bodies the handler never read; set `sentry_lean_error_capture=False` to keep the integration. The numbers on this page were
79+
measured before 2.0, against the old defaults.
7580

7681
## What each saving costs you
7782

@@ -92,7 +97,8 @@ The same instruments one at a time, in-process, in the Linux container:
9297
--8<-- "benchmarks/README.md:fastapilinux"
9398

9499
The ordering matches macOS: OpenTelemetry first, then Sentry, then Prometheus, with logging free
95-
until something logs.
100+
until something logs. Since 2.0, Sentry with tracing off drops from about +59 to about +6 µs per
101+
request, below Prometheus ([FastAPI: Sentry](../integrations/fastapi.md#sentry)).
96102

97103
## Litestar
98104

@@ -131,7 +137,8 @@ Per tool call, through the stateless JSON HTTP app:
131137
--8<-- "benchmarks/README.md:fastmcp"
132138

133139
The baseline is large, so the instruments are a smaller share here than on the HTTP frameworks.
134-
OpenTelemetry is still the largest item.
140+
OpenTelemetry is still the largest item. Since 2.0, Sentry with tracing off drops from about +82 to
141+
about +21 µs per tool call ([FastMCP: Sentry](../integrations/fastmcp.md#sentry)).
135142

136143
## Unsampled OpenTelemetry is not free
137144

0 commit comments

Comments
 (0)