|
33 | 33 | from litestar.static_files import create_static_files_router |
34 | 34 |
|
35 | 35 | if import_checker.is_litestar_opentelemetry_installed: |
36 | | - from litestar.contrib.opentelemetry import OpenTelemetryConfig |
| 36 | + from litestar.middleware import ASGIMiddleware |
| 37 | + from litestar.types.asgi_types import ASGIApp, Receive, Scope, Send |
| 38 | + from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware |
| 39 | + from opentelemetry.trace import TracerProvider |
37 | 40 |
|
38 | 41 | if import_checker.is_opentelemetry_installed: |
39 | 42 | from opentelemetry.trace import get_tracer_provider |
40 | 43 |
|
41 | 44 |
|
| 45 | +def build_span_name(method: str, route: str) -> str: |
| 46 | + if not route: |
| 47 | + return method |
| 48 | + return f"{method} {route}" |
| 49 | + |
| 50 | + |
| 51 | +def build_litestar_route_details_from_scope( |
| 52 | + scope: typing.MutableMapping[str, typing.Any], |
| 53 | +) -> tuple[str, dict[str, str]]: |
| 54 | + path_template: typing.Final = scope.get("path_template") |
| 55 | + method: typing.Final = str(scope.get("method", "HTTP")).strip() |
| 56 | + if path_template is not None: |
| 57 | + path_template_stripped: typing.Final = path_template.strip() |
| 58 | + return build_span_name(method, path_template_stripped), {"http.route": path_template_stripped} |
| 59 | + |
| 60 | + path: typing.Final = scope.get("path") |
| 61 | + if path is not None: |
| 62 | + path_stripped: typing.Final = path.strip() |
| 63 | + return build_span_name(method, path_stripped), {"http.route": path_stripped} |
| 64 | + return method, {} |
| 65 | + |
| 66 | + |
| 67 | +if import_checker.is_litestar_opentelemetry_installed: |
| 68 | + |
| 69 | + class LitestarOpenTelemetryInstrumentationMiddleware(ASGIMiddleware): |
| 70 | + def __init__(self, tracer_provider: "TracerProvider", excluded_urls: set[str]) -> None: |
| 71 | + self._tracer_provider = tracer_provider |
| 72 | + self._excluded_urls = ",".join(excluded_urls) |
| 73 | + |
| 74 | + async def handle( |
| 75 | + self, |
| 76 | + scope: "Scope", |
| 77 | + receive: "Receive", |
| 78 | + send: "Send", |
| 79 | + next_app: "ASGIApp", |
| 80 | + ) -> None: |
| 81 | + await OpenTelemetryMiddleware( |
| 82 | + app=next_app, |
| 83 | + default_span_details=build_litestar_route_details_from_scope, |
| 84 | + excluded_urls=self._excluded_urls, |
| 85 | + tracer_provider=self._tracer_provider, |
| 86 | + )(scope, receive, send) # ty: ignore |
| 87 | + |
| 88 | + |
42 | 89 | @dataclasses.dataclass(kw_only=True, slots=True, frozen=True) |
43 | 90 | class LitestarConfig( |
44 | 91 | CorsConfig, |
@@ -111,10 +158,10 @@ def _build_excluded_urls(self) -> set[str]: |
111 | 158 | def bootstrap(self) -> None: |
112 | 159 | super().bootstrap() |
113 | 160 | self.bootstrap_config.application_config.middleware.append( |
114 | | - OpenTelemetryConfig( |
| 161 | + LitestarOpenTelemetryInstrumentationMiddleware( |
115 | 162 | tracer_provider=get_tracer_provider(), |
116 | | - exclude=list(self._build_excluded_urls()), |
117 | | - ).middleware, |
| 163 | + excluded_urls=self._build_excluded_urls(), |
| 164 | + ) |
118 | 165 | ) |
119 | 166 |
|
120 | 167 |
|
|
0 commit comments