Skip to content

Use Litestar's OpenTelemetryPlugin in the Litestar bootstrapper - #154

Open
personage-hub wants to merge 2 commits into
community-of-python:mainfrom
personage-hub:litestar-opentelemetry-plugin
Open

personage-hub wants to merge 2 commits into
community-of-python:mainfrom
personage-hub:litestar-opentelemetry-plugin

Conversation

@personage-hub

@personage-hub personage-hub commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Litestar ships its own OpenTelemetryPlugin now, so the Litestar bootstrapper registers it instead of hand-building an ASGI middleware around OpenTelemetryMiddleware (the old LitestarOpenTelemetryInstrumentationMiddleware, which also re-created the OTel middleware on every request in handle).

  • opentelemetry_exclude_urls (plus the health check path when opentelemetry_generate_health_check_spans is off) map to the plugin's exclude; OTEL_PYTHON_LITESTAR_EXCLUDED_URLS keeps working through the plugin's exclude_urls_env_key, so CombinedExcludeList is no longer needed for Litestar.

  • User-supplied plugins are preserved, the lists are merged like any other container config.

  • Requires litestar>=2.22.0, where litestar.plugins.opentelemetry appeared (2.21 only has the deprecated litestar.contrib.opentelemetry path).

  • Fixes Anchor derived trace exclusions for Litestar #159: the derived exclusions (prometheus_metrics_path, and the health path when health spans are off) are anchored as ^<path>(?:/|$) by define_excluded_path_patterns(), the path-based counterpart of define_excluded_url_patterns() from Anchor derived trace exclusions for FastAPI and FastMCP #157, so /api/metrics-report and /api/health/details keep their spans while /health and /health/ are both excluded.

  • Requests that match no route produce a span named by the method only, without http.route, so a 404 scan does not explode span cardinality.

Why there is still a middleware

Litestar applies the plugin's middleware outside the router (Litestar.__init__ wraps asgi_handler with it), i.e. before path_template is known. That is also why Litestar's default scope_span_details_extractor reports GET /users/123. To keep the span names and http.route as they are today (GET /users/{user_id}), a tiny LitestarOpentelemetryRouteMiddleware inside the route handler stack renames the current server span by the route template. It does nothing for excluded urls (the span is non-recording there). The HTTP server metrics of opentelemetry-instrumentation-asgi take their attributes at span start, so they are not renamed; no MeterProvider is configured here, and litestar-org/litestar#5120 is the proper fix, after which both the extractor and the middleware can be removed.

Tests

  • test_litestar_opentelemetry_server_spans_use_path_template asserts real exported spans: GET /users/{user_id} with http.route, and GET without http.route for a 404.
  • test_litestar_opentelemetry_exclusions_do_not_silence_lookalike_routes mirrors the FastAPI test from Anchor derived trace exclusions for FastAPI and FastMCP #157, plus /health without the slash; ..._custom_metrics_path_is_excluded checks a custom prometheus_metrics_path.
  • Exclusion via settings and via OTEL_PYTHON_LITESTAR_EXCLUDED_URLS are now integration tests over exported spans.
  • just test: 348 passed.

Note: uv run mypy . reports two pre-existing errors in microbootstrap/helpers.py:29 with the locked mypy on Python 3.14; untouched here.

🤖 Generated with Claude Code

@lesnik512

Copy link
Copy Markdown
Member

Two things from running this branch (80a9b11):

  1. The plugin fixes the trailing-slash miss on main (the health check is now excluded, because the plugin sees the raw path), but the patterns are still searched unanchored: with the default ["/metrics"] and health spans off, /api/metrics-report and /api/health/details produce no span. Details and a suggested anchoring in Trace exclusions match anywhere in the URL, and miss the Litestar health check #156.
  2. Because the plugin opens the span before routing, an unmatched path keeps its raw path as the span name, as test_litestar_opentelemetry_server_spans_use_path_template pins with GET /missing. Scanners then create one span name per random URL. Before this PR, 404s produced no span, since the middleware ran inside the route stack. Returning the method alone when path_template is missing keeps cardinality bounded: LitestarOpentelemetryRouteMiddleware still renames matched routes, so only unmatched requests are affected. That is what feat!: trace Litestar through its own OpenTelemetryPlugin modern-python/lite-bootstrap#279 does.

@lesnik512

Copy link
Copy Markdown
Member

#157 landed on main and changes the shared exclusion helper, so this branch needs a rebase:

  • README.md conflicts on the opentelemetry_exclude_urls line.
  • opentelemetry_exclude_urls now defaults to [], and define_exclude_urls() always adds prometheus_metrics_path. So test_litestar_opentelemetry_instrument_does_not_exclude_anything_by_default fails: with defaults, plugin.config.exclude is now ["/metrics"], not None.

While rebasing, could this PR also cover #159? The plugin's exclude is searched unanchored in scope["path"], so the derived paths would need anchoring as ^{re.escape(path.rstrip("/"))}(?:/|$), the path-based counterpart of define_excluded_url_patterns(). The method-only span name for unmatched paths from my earlier comment fits here too. If you'd rather not, I'm happy to push these to this branch or follow up after it merges.

Alexander Niyazov and others added 2 commits October 13, 2026 19:18
Litestar ships its own OpenTelemetry plugin since the custom middleware was written, so the
bootstrapper now registers OpenTelemetryPlugin instead of building an ASGI middleware around
OpenTelemetryMiddleware by hand (which also re-created the OTel middleware on every request).
opentelemetry_exclude_urls map to the plugin's exclude, OTEL_PYTHON_LITESTAR_EXCLUDED_URLS keeps
working through the plugin's exclude_urls_env_key.

Litestar applies the plugin middleware outside the router, before path_template is known, so a
small LitestarOpentelemetryRouteMiddleware inside the handler stack renames the server span by the
route template and sets http.route, keeping the span names unchanged.

Requires litestar>=2.22.0, where litestar.plugins.opentelemetry appeared.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Litestar searches the plugin's exclude patterns unanchored in scope["path"], so the derived
paths (prometheus_metrics_path, and health_checks_path when health spans are off) are now anchored
as ^<path>(?:/|$) by define_excluded_path_patterns(), the path-based counterpart of
define_excluded_url_patterns(); user-supplied regexes stay verbatim. /health and /health/ are both
excluded, /api/metrics-report and /api/health/details keep their spans.

The plugin opens the span before routing, so the extractor no longer falls back to the raw path:
an unmatched request gets a span named by the method alone and no http.route, keeping cardinality
bounded under a 404 scan. Matched routes are still renamed by LitestarOpentelemetryRouteMiddleware.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@personage-hub
personage-hub force-pushed the litestar-opentelemetry-plugin branch from 80a9b11 to d851cc7 Compare October 13, 2026 14:29
@personage-hub

Copy link
Copy Markdown
Contributor Author

Rebased on #157, both points addressed.

  • Exclusions: define_excluded_path_patterns() is the path-based counterpart of define_excluded_url_patterns(). Derived paths are anchored as ^{re.escape(path.rstrip("/"))}(?:/|$), user regexes stay verbatim. That covers Anchor derived trace exclusions for Litestar #159; the lookalike-routes test from Anchor derived trace exclusions for FastAPI and FastMCP #157 is mirrored for Litestar, with /health and /health/ both excluded, and a custom prometheus_metrics_path test.
  • Unmatched paths: the extractor returns the method alone without http.route when path_template is missing, so a 404 scan yields one span name. LitestarOpentelemetryRouteMiddleware still renames matched routes. The GET /missing assertion now expects GET without http.route.

One caveat: the HTTP server metrics of opentelemetry-instrumentation-asgi take their attributes at span start, so the renaming middleware doesn't help them; we don't configure a MeterProvider, so they are no-op today. The real fix is litestar-org/litestar#5120, where the extractor resolves the template through the router before the span starts. Once it ships, the custom extractor and the middleware can both go.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Anchor derived trace exclusions for Litestar

2 participants