Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lite_bootstrap/bootstrappers/litestar_bootstrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
from litestar.types.asgi_types import ASGIApp, Receive, Scope, Send
from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware
from opentelemetry.trace import TracerProvider
from opentelemetry.util.http import parse_excluded_urls

if import_checker.is_opentelemetry_installed:
from opentelemetry.trace import get_tracer_provider
Expand Down Expand Up @@ -99,7 +100,8 @@ def build_litestar_route_details_from_scope(
class LitestarOpenTelemetryInstrumentationMiddleware(ASGIMiddleware):
def __init__(self, tracer_provider: "TracerProvider", excluded_urls: set[str]) -> None:
self._tracer_provider = tracer_provider
self._excluded_urls = ",".join(excluded_urls)
# OpenTelemetryMiddleware only parses a raw string from 0.56b0; the floor is 0.49b0.
self._excluded_urls = parse_excluded_urls(",".join(excluded_urls))
# WeakKeyDictionary so wrapper apps are evicted when Litestar drops the
# next_app reference (hot reload, plugin add/remove, AppConfig rebuild).
# Apps that don't support weak references are simply not cached.
Expand Down
21 changes: 21 additions & 0 deletions tests/test_litestar_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -532,3 +532,24 @@ def test_second_litestar_bootstrapper_bootstrap_raises(litestar_config: Litestar
assert client.get(litestar_config.health_checks_path).status_code == status_codes.HTTP_200_OK
finally:
first.teardown()


def test_litestar_otel_middleware_hands_the_instrumentor_a_parsed_exclude_list() -> None:
"""INVARIANT: `excluded_urls` reaches OpenTelemetryMiddleware parsed, never as a raw string.

`OpenTelemetryMiddleware` only learned to parse a string itself in
opentelemetry-instrumentation 0.56b0. The declared floor is 0.49b0, where a string reaches
`self.excluded_urls.url_disabled(url)` and raises `AttributeError` on every request, so a
Litestar service with OpenTelemetry returns 500 for everything.
"""
middleware = LitestarOpenTelemetryInstrumentationMiddleware(
tracer_provider=TracerProvider(),
excluded_urls={"/custom-metrics"},
)

excluded_urls = middleware._excluded_urls # noqa: SLF001
assert not isinstance(excluded_urls, str)
# Matched against the URL the middleware builds from `scope["path"]`, which Litestar has
# already normalized; see #248 for the trailing-slash entries that therefore never match.
assert excluded_urls.url_disabled("http://test/custom-metrics")
assert not excluded_urls.url_disabled("http://test/items/1")
Loading