Repository navigation
Record HTTP metrics once when both OpenTelemetry metrics and Prometheus are configured - #365
Merged
Merged
Conversation
…us are configured
This was referenced Oct 29, 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 #312.
On FastAPI, Litestar and FastMCP, HTTP request metrics are now recorded once. When
opentelemetry_metrics_endpointinstalls aMeterProvider, the Prometheus instrument no longer adds its HTTP middleware, so OpenTelemetry's HTTP metrics are the only set. The metrics route is still served, for the user's own collectors and FastMCP's tool-call metrics.Decision
The issue asked to settle which side wins before writing a PR. The maintainer picked OpenTelemetry.
opentelemetry_metrics_endpointis an explicit opt-in, while Prometheus is on whenever its extra is installed, so the setting the user turned on by hand wins. FastStream stays as it is: both of its broker middlewares are opt-in (opentelemetry_middleware_cls,prometheus_middleware_cls), so a service with both asked for both.The measurement below does not back the issue's other argument, that OpenTelemetry's is the cheaper set. On the M2 host, OTel's HTTP metrics cost 34 to 36 µs per request and Prometheus's 18 to 25 µs. The issue's Linux run had OTel at about 12 µs. I haven't found the cause. The candidates are the OS, or opentelemetry-sdk 1.45.1 against 1.44.0. On this host, keeping Prometheus would have saved more. Switching the rule is a small change if you'd rather go that way.
Changes
PrometheusHttpConfiginprometheus_instrument.pysubclassesPrometheusConfigand addsprometheus_http_metrics_enabled: bool | None = None.FastAPIConfig,LitestarConfigandFastMcpConfiginherit it, and FastStream keeps plainPrometheusConfig.is_prometheus_http_metrics_on(opentelemetry_records_http_metrics=...)resolves the field:Noneis on unless OpenTelemetry records HTTP metrics.Truekeeps both sets, which is the field the issue asks for.Falseturns them off even without OpenTelemetry. This one wasn't requested. It comes free with thebool | Noneshape, the same shapesentry_auto_enabling_integrationsuses.OpenTelemetryInstrument.is_recording_metrics(config)is true when the endpoint is set, the framework's OTel instrument has its dependencies, and the OTLP metric exporter foropentelemetry_exporter_protocolis installed. With the exporter missing,bootstrap()installs noMeterProviderand only warns, so Prometheus keeps its HTTP metrics and the service doesn't lose both. Each framework's Prometheus instrument asks its own OTel instrument class, because Litestar's and FastMCP's dependency checks differ.Instrumentator(...).exposewithout.instrument. Litestar adds the controller withoutPrometheusMiddleware. FastMCP skips thehttp_app()wrapping and keeps the tool-call middleware and the route.conftest.unregister_prometheus_request_metricsso the FastAPI tests can use it. conftest also hasin_memory_opentelemetry_metric_readerand two small readers for each side.Tests
Written first. One request is sent and the test checks which set moved: an in-memory metric reader for OpenTelemetry, and the request's unique path in
prometheus_client.generate_latest()for Prometheus. The same parametrized test runs for FastAPI, Litestar and FastMCP. These cases failed on main:prometheus_http_metrics_enabled=True: both moveprometheus_http_metrics_enabled=False: neither movesThese passed on main and pin the "each signal alone" check: Prometheus alone, OpenTelemetry alone, the metrics route still served (and FastMCP's tool-call middleware still added), and a missing OTLP metric exporter leaving Prometheus's HTTP metrics in place.
The full suite passes, 574 tests at 100% coverage (
just test-ci).just lint(includingty),just adr-checkandjust docs-buildpass too. No floor changes, so I left the floors to CI.Measurement
In-process on the M2 host (macOS, CPython 3.14.7, opentelemetry-sdk 1.45.1, instrumentation-asgi and -fastapi 0.66b1, prometheus-client 0.26.0, prometheus-fastapi-instrumentator 8.1.0, fastapi 0.162.0, litestar 2.25.0). Figures are µs over each suite's bare app, and every row's rounds were within 2%:
otel_0: OTel at 0%otel_0_metrics: + OTel HTTP metricsotel_0_prom: + Prometheusotel_0_metrics_prom_both: 1.x behaviourotel_0_metrics_prom: the 2.0 defaultConfiguring both now costs 21.9 µs less on FastAPI and 16.8 µs less on Litestar. The issue asked for a
stack_scenarios.pyrun before and after. Withrun.py stack async, at 100% sampling overotelat 109.0 µs, OTel HTTP metrics cost +44.4, both sets +71.8, and the 2.0 default +48.1.otel_metrics,otel_metrics_promandotel_metrics_prom_bothare new stack scenarios, andotel_0_metrics_prom_bothis new in the FastAPI and Litestar suites. I didn't measure FastMCP: its noise, about ±15% over a 620 µs baseline, is larger than the saving.Docs
configuration.md:performance.md: the "savings applied for you" paragraph now has seven savings.benchmarks/README.md§4d gets the rerun, including the reversed cost on this host, and §9 gets the stack command.Breaking changes
FastAPI, Litestar and FastMCP services that set
opentelemetry_metrics_endpointand also serve Prometheus metrics lose the instrumentator's or Litestar's HTTP series on/metrics, for examplehttp_requests_totalandlitestar_requests_total. Setprometheus_http_metrics_enabled=Trueto keep them.