Skip to content

fix: parse excluded_urls before handing them to the ASGI instrumentor - #249

Merged
lesnik512 merged 2 commits into
mainfrom
fix/247-otel-excluded-urls
Sep 20, 2026
Merged

lesnik512 merged 2 commits into
mainfrom
fix/247-otel-excluded-urls

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Closes #247.

Any Litestar service with OpenTelemetry enabled returns 500 on every request when
opentelemetry-instrumentation sits between the declared floor 0.49b0 and 0.55b1:

File "lite_bootstrap/bootstrappers/litestar_bootstrapper.py", line 127, in handle
    await otel_app(scope, receive, send)
File "opentelemetry/instrumentation/asgi/__init__.py", line 684, in __call__
    if self.excluded_urls and self.excluded_urls.url_disabled(url):
AttributeError: 'str' object has no attribute 'url_disabled'

The middleware was handed a comma-joined string. OpenTelemetryMiddleware only learned to parse one
itself in 0.56b0. opentelemetry.util.http.parse_excluded_urls exists at the floor and newer releases
accept the ExcludeList it returns, so parsing once covers the whole declared range and the floor
stays at >=0.49b0.

litestar opentelemetry-instrumentation before after
2.19.0 0.49b0 (floor) 500 200
2.24.0 0.49b0 500 200
2.24.0 0.65b0 (newest) 200 200

Isolated to the OpenTelemetry floor rather than litestar's, and bisected: broken at 0.49b0, 0.50b0,
0.52b1 and 0.55b1; fixed from 0.56b0.

The test pins the type, deliberately

At current resolution both a string and an ExcludeList work, so a behavioural assertion would pass
on main and prove nothing. What is true at the floor and false before this change is that the
instrumentor receives something already parsed, so that is what the test asserts. It fails on main
at current resolution, so CI guards the regression without needing the floors job.

A correction made during review

The first version of that test asserted url_disabled("http://test/custom-health/") and passed. It
was certifying a fiction: Litestar normalises the trailing slash out of scope["path"], so the
middleware builds http://host/custom-health and never sees the URL the test used. Worse, the
behaviour it implied does not hold — a request to the excluded health path emits three spans.

That is a real, separate, pre-existing defect, now filed as #248 with the reproduction, the reason
FastAPI is unaffected, and the trap in the obvious fix (stripping the slash would start excluding
/custom-healthy, because ExcludeList searches unanchored). The test here now asserts only what is
actually true, against a URL form the middleware really produces, and points at #248.

Not done here

#247 also suggests having scripts/floor_smoke.py drive one request per HTTP-serving target, which is
what would have caught this. That belongs with #236 (floors never type-checked or unit-tested) and
#245 (the floors job never runs on pull requests) as one piece of work on what the floor job actually
exercises, rather than a quarter of it inside a three-line fix.

325 tests, ruff, ty, mkdocs build --strict clean.

@lesnik512
lesnik512 merged commit 9cfb98b into main Sep 20, 2026
13 checks passed
@lesnik512
lesnik512 deleted the fix/247-otel-excluded-urls branch September 20, 2026 15:14
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.

Litestar + OpenTelemetry returns 500 on every request at the declared otel floor

1 participant