Skip to content

Default opentelemetry_exclude_spans to ["receive", "send"] and support it on FastMCP #309

Closed

Description

@lesnik512

opentelemetry_exclude_spans defaults to an empty list on FastAPI and Litestar (lite_bootstrap/bootstrappers/fastapi_bootstrapper.py:65, litestar_bootstrapper.py:127), so every request records three spans: the server span plus one each for the ASGI receive and send events. benchmarks/README.md section 4a measured ["receive", "send"] at -34.0 µs per request on FastAPI. The event spans are started with start_as_current_span even when the trace is unsampled, so the context attach and detach run either way.

Re-measured on 2026-10-24 at 705d8a3 on Linux with 2 CPUs (CPython 3.14.9, opentelemetry 1.44.0 / contrib 0.65b0, 100% sampling, OTLP over HTTP to a stub collector in another process, do-nothing handler):

default ["receive", "send"] saved
FastAPI, in-process +147.9 µs +101.6 µs 46 µs
Litestar, in-process (two runs) +145.4 / +131.8 µs +92.4 / +90.8 µs 41-53 µs
Litestar on Granian (1 worker, ab -k -c 16) +168.9 µs +102.7 µs 66 µs
FastMCP per tools/call, exclusion passed to the middleware as a prototype +272 µs +181 µs 91 µs

The FastMCP suite is noisy at about ±30 µs. At 10% sampling the exclusion still saves 20-25 µs on FastAPI and Litestar (79.0 to 54.0, 66.8 to 46.7). The tables and scripts are to be published with #335.

FastMcpConfig has no such field (fastmcp_bootstrapper.py:192-199), although the OpenTelemetryMiddleware it adds at :235-244 accepts exclude_spans at the 0.49b0 floor (opentelemetry/instrumentation/asgi/__init__.py:551 at contrib tag v0.49b0). FastMCP (3.x and 4.x) also emits its own SERVER span per MCP operation (fastmcp 3.4.6 server/telemetry.py:56-75, telemetry docs), so a tool call over HTTP currently records an HTTP server span, a FastMCP server span and the send spans.

Work

  • Test first: flip test_fastapi_opentelemetry_exclude_spans_defaults_to_recording_them (tests/test_fastapi_bootstrap.py:74-76) and test_litestar_otel_exclude_spans_defaults_to_recording_them (tests/test_litestar_bootstrap.py:640-642) to expect ["receive", "send"], and add a FastMCP test that the middleware receives exclude_spans=["receive", "send"] by default. All three fail today.
  • Change the default on FastAPIConfig and LitestarConfig, and update the comment at fastapi_bootstrapper.py:64, which says only the FastAPI and Litestar instrumentors take it.
  • Add opentelemetry_exclude_spans to FastMcpConfig and pass it to OpenTelemetryMiddleware in FastMcpOpenTelemetryInstrument._instrument_http_app. The field with an empty default is non-breaking and can ship in a 1.x minor ahead of the default flip, so the 2.0 PR is only the flip.
  • Optional: declare the field once on a small mixin shared by the three ASGI configs instead of three times. FastStream and Free must not get it.
  • Docs: docs/introduction/configuration.md:178-183 (the "For FastAPI and Litestar" paragraph, which also gains FastMCP), the table in docs/introduction/performance.md:49, and the tuned configuration in benchmarks/README.md section 6, which then no longer needs the setting.

Verify: the three tests pass, and a Litestar request under opentelemetry_log_traces=True prints one server span.

Breaking for anyone who reads the per-message receive/send spans. They set opentelemetry_exclude_spans=[] to get them back.

Activity

  1. added this to the 2.0.0 milestone on Oct 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestneeds-triageMaintainer needs to evaluate this issue

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions