77import typing_extensions
88from litestar import openapi
99from litestar .config .cors import CORSConfig as LitestarCorsConfig
10- from litestar .contrib .opentelemetry .config import OpenTelemetryConfig as LitestarOpentelemetryConfig
10+ from litestar .contrib .opentelemetry .config import (
11+ OpenTelemetryConfig as LitestarOpentelemetryConfig ,
12+ )
13+ from litestar .contrib .opentelemetry .middleware import (
14+ OpenTelemetryInstrumentationMiddleware ,
15+ )
1116from litestar .contrib .prometheus import PrometheusConfig , PrometheusController
1217from litestar .openapi .plugins import SwaggerRenderPlugin
1318from litestar_offline_docs import generate_static_files_config
19+ from opentelemetry .instrumentation .asgi import OpenTelemetryMiddleware
20+ from opentelemetry .util .http import get_excluded_urls
1421from sentry_sdk .integrations .litestar import LitestarIntegration
1522
1623from microbootstrap .bootstrappers .base import ApplicationBootstrapper
1724from microbootstrap .config .litestar import LitestarConfig
1825from microbootstrap .instruments .cors_instrument import CorsInstrument
19- from microbootstrap .instruments .health_checks_instrument import HealthChecksInstrument , HealthCheckTypedDict
26+ from microbootstrap .instruments .health_checks_instrument import (
27+ HealthChecksInstrument ,
28+ HealthCheckTypedDict ,
29+ )
2030from microbootstrap .instruments .logging_instrument import LoggingInstrument
2131from microbootstrap .instruments .opentelemetry_instrument import OpentelemetryInstrument
22- from microbootstrap .instruments .prometheus_instrument import LitestarPrometheusConfig , PrometheusInstrument
32+ from microbootstrap .instruments .prometheus_instrument import (
33+ LitestarPrometheusConfig ,
34+ PrometheusInstrument ,
35+ )
2336from microbootstrap .instruments .pyroscope_instrument import PyroscopeInstrument
2437from microbootstrap .instruments .sentry_instrument import SentryInstrument
2538from microbootstrap .instruments .swagger_instrument import SwaggerInstrument
2639from microbootstrap .middlewares .litestar import build_litestar_logging_middleware
2740from microbootstrap .settings import LitestarSettings
2841
2942
43+ if typing .TYPE_CHECKING :
44+ from litestar .contrib .opentelemetry import OpenTelemetryConfig
45+ from litestar .types import ASGIApp , Scope
46+
47+
3048class LitestarBootstrapper (
3149 ApplicationBootstrapper [LitestarSettings , litestar .Litestar , LitestarConfig ],
3250):
@@ -106,16 +124,66 @@ def bootstrap_before(self) -> dict[str, typing.Any]:
106124LitestarBootstrapper .use_instrument ()(PyroscopeInstrument )
107125
108126
127+ def build_span_name (method : str , route : str ) -> str :
128+ if not route :
129+ return method
130+ return f"{ method } { route } "
131+
132+
133+ def build_litestar_route_details_from_scope (
134+ scope : Scope ,
135+ ) -> tuple [str , dict [str , str ]]:
136+ """Retrieve the span name and attributes from the ASGI scope for Litestar routes.
137+
138+ Args:
139+ scope: The ASGI scope instance.
140+
141+ Returns:
142+ A tuple of the span name and a dict of attrs.
143+
144+ """
145+ path_template : typing .Final = scope .get ("path_template" )
146+ method : typing .Final = str (scope .get ("method" , "HTTP" )).strip ()
147+ if path_template is not None :
148+ path_template_stripped : typing .Final = path_template .strip ()
149+ return build_span_name (method , path_template_stripped ), {"http.route" : path_template_stripped }
150+
151+ path : typing .Final = scope .get ("path" )
152+ if path is not None :
153+ path_stripped : typing .Final = path .strip ()
154+ return build_span_name (method , path_stripped ), {"http.route" : path_stripped }
155+ return method , {}
156+
157+
158+ class LitestarOpenTelemetryInstrumentationMiddleware (OpenTelemetryInstrumentationMiddleware ):
159+ def __init__ (self , app : ASGIApp , config : OpenTelemetryConfig ) -> None :
160+ super ().__init__ (
161+ app = app ,
162+ config = config ,
163+ )
164+ self .open_telemetry_middleware = OpenTelemetryMiddleware (
165+ app = app ,
166+ client_request_hook = config .client_request_hook_handler , # type: ignore[arg-type]
167+ client_response_hook = config .client_response_hook_handler , # type: ignore[arg-type]
168+ default_span_details = build_litestar_route_details_from_scope ,
169+ excluded_urls = get_excluded_urls (config .exclude_urls_env_key ),
170+ meter = config .meter ,
171+ meter_provider = config .meter_provider ,
172+ server_request_hook = config .server_request_hook_handler ,
173+ tracer_provider = config .tracer_provider ,
174+ )
175+
176+
109177@LitestarBootstrapper .use_instrument ()
110178class LitestarOpentelemetryInstrument (OpentelemetryInstrument ):
111179 def bootstrap_before (self ) -> dict [str , typing .Any ]:
112180 return {
113181 "middleware" : [
114182 LitestarOpentelemetryConfig (
115183 tracer_provider = self .tracer_provider ,
116- exclude = self . define_exclude_urls () ,
184+ middleware_class = LitestarOpenTelemetryInstrumentationMiddleware ,
117185 ).middleware ,
118- ],
186+ ]
119187 }
120188
121189
@@ -141,7 +209,10 @@ class LitestarPrometheusController(PrometheusController):
141209 ** self .instrument_config .prometheus_additional_params ,
142210 )
143211
144- return {"route_handlers" : [LitestarPrometheusController ], "middleware" : [litestar_prometheus_config .middleware ]}
212+ return {
213+ "route_handlers" : [LitestarPrometheusController ],
214+ "middleware" : [litestar_prometheus_config .middleware ],
215+ }
145216
146217 @classmethod
147218 def get_config_type (cls ) -> type [LitestarPrometheusConfig ]:
0 commit comments