Skip to content

Commit 455fbd2

Browse files
aaaarrrrrrttttiiiiixxxxEGOROV Vadimvrslev
authored
Fix opentelemetry span names (#135)
* fix_opentelemetry_span_names * fix creating middleware * test opentelemetry * fix tests * fix pyroscope * Apply suggestion from @vrslev --------- Co-authored-by: EGOROV Vadim <ruawiih@n-msk-macwiih.raiffeisen.ru> Co-authored-by: Lev Vereshchagin <levwint@gmail.com>
1 parent 609c420 commit 455fbd2

4 files changed

Lines changed: 45 additions & 39 deletions

File tree

microbootstrap/bootstrappers/litestar.py

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,16 @@
22
import typing
33

44
import litestar
5-
import litestar.exceptions
6-
import litestar.types
75
import typing_extensions
86
from litestar import openapi
97
from litestar.config.cors import CORSConfig as LitestarCorsConfig
108
from litestar.contrib.opentelemetry.config import (
119
OpenTelemetryConfig as LitestarOpentelemetryConfig,
1210
)
13-
from litestar.contrib.opentelemetry.middleware import (
14-
OpenTelemetryInstrumentationMiddleware,
15-
)
1611
from litestar.contrib.prometheus import PrometheusConfig, PrometheusController
12+
from litestar.middleware import ASGIMiddleware
1713
from litestar.openapi.plugins import SwaggerRenderPlugin
14+
from litestar.types.asgi_types import ASGIApp, Scope
1815
from litestar_offline_docs import generate_static_files_config
1916
from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware
2017
from opentelemetry.util.http import get_excluded_urls
@@ -43,6 +40,7 @@
4340
if typing.TYPE_CHECKING:
4441
from litestar.contrib.opentelemetry import OpenTelemetryConfig
4542
from litestar.types import ASGIApp, Scope
43+
from litestar.types.asgi_types import Receive, Send
4644

4745

4846
class LitestarBootstrapper(
@@ -155,34 +153,38 @@ def build_litestar_route_details_from_scope(
155153
return method, {}
156154

157155

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(
156+
class LitestarOpenTelemetryInstrumentationMiddleware(ASGIMiddleware):
157+
def __init__(self, config: OpenTelemetryConfig) -> None:
158+
self.config = config
159+
160+
def create_open_telemetry_middleware(self, app: ASGIApp) -> OpenTelemetryMiddleware:
161+
return OpenTelemetryMiddleware(
165162
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]
163+
client_request_hook=self.config.client_request_hook_handler, # type: ignore[arg-type]
164+
client_response_hook=self.config.client_response_hook_handler, # type: ignore[arg-type]
168165
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,
166+
excluded_urls=get_excluded_urls(self.config.exclude_urls_env_key),
167+
meter=self.config.meter,
168+
meter_provider=self.config.meter_provider,
169+
server_request_hook=self.config.server_request_hook_handler,
170+
tracer_provider=self.config.tracer_provider,
174171
)
175172

173+
async def handle(self, scope: Scope, receive: Receive, send: Send, next_app: ASGIApp) -> None:
174+
await self.create_open_telemetry_middleware(next_app)(scope, receive, send) # type: ignore[arg-type]
175+
176176

177177
@LitestarBootstrapper.use_instrument()
178178
class LitestarOpentelemetryInstrument(OpentelemetryInstrument):
179179
def bootstrap_before(self) -> dict[str, typing.Any]:
180180
return {
181181
"middleware": [
182-
LitestarOpentelemetryConfig(
183-
tracer_provider=self.tracer_provider,
184-
middleware_class=LitestarOpenTelemetryInstrumentationMiddleware,
185-
).middleware,
182+
LitestarOpenTelemetryInstrumentationMiddleware(
183+
LitestarOpentelemetryConfig(
184+
tracer_provider=self.tracer_provider,
185+
middleware_class=LitestarOpenTelemetryInstrumentationMiddleware, # type: ignore[arg-type]
186+
)
187+
)
186188
]
187189
}
188190

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ dependencies = [
4141
"rich>=13",
4242
"sentry-sdk>=2.7",
4343
"structlog>=24",
44-
"pyroscope-io; platform_system != 'Windows'",
44+
"pyroscope-io<=1.0.0; platform_system != 'Windows'",
4545
"opentelemetry-distro[otlp]>=0.54b1",
4646
"opentelemetry-instrumentation-aio-pika>=0.54b1",
4747
"opentelemetry-instrumentation-aiohttp-client>=0.54b1",

tests/bootstrappers/test_litestar_opentelemetry.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -115,21 +115,22 @@ def test_litestar_opentelemetry_instrument_uses_custom_middleware(
115115
assert "middleware" in bootstrap_result
116116
assert len(bootstrap_result["middleware"]) == 1
117117

118-
middleware_config: typing.Final = bootstrap_result["middleware"][0]
119-
assert middleware_config.middleware == LitestarOpenTelemetryInstrumentationMiddleware
118+
middleware_config: typing.Final = bootstrap_result["middleware"][0].config
119+
assert middleware_config.middleware.middleware == LitestarOpenTelemetryInstrumentationMiddleware
120120

121121

122122
@pytest.mark.parametrize(
123-
("path", "expected_span_name"),
123+
("path", "expected_span_name", "expected_path_template"),
124124
[
125-
("/users/123", "GET /users/{user_id}"),
126-
("/users/", "GET /users/"),
127-
("/", "GET /"),
125+
("/users/123", "GET /users/{user_id}", "/users/{user_id}"),
126+
("/users/", "GET /users/", "/users"),
127+
("/", "GET /", "/"),
128128
],
129129
)
130130
def test_litestar_opentelemetry_integration_with_path_templates(
131131
path: str,
132132
expected_span_name: str,
133+
expected_path_template: str,
133134
minimal_opentelemetry_config: OpentelemetryConfig,
134135
) -> None:
135136
@litestar.get("/users/{user_id:int}")
@@ -158,6 +159,7 @@ async def root() -> dict[str, str]:
158159
response: typing.Final = client.get(path)
159160
assert response.status_code == HTTP_200_OK
160161
assert mock_function.called
162+
assert mock_function.call_args_list[0].args[0].get("path_template") == expected_path_template
161163

162164

163165
def test_litestar_opentelemetry_middleware_initialization() -> None:
@@ -175,8 +177,8 @@ def test_litestar_opentelemetry_middleware_initialization() -> None:
175177
mock_config.server_request_hook_handler = None
176178
mock_config.tracer_provider = None
177179

178-
middleware: typing.Final = LitestarOpenTelemetryInstrumentationMiddleware(app=mock_app, config=mock_config)
180+
middleware: typing.Final = LitestarOpenTelemetryInstrumentationMiddleware(config=mock_config)
179181

180-
assert middleware.app == mock_app
181-
assert hasattr(middleware, "open_telemetry_middleware")
182-
assert middleware.open_telemetry_middleware is not None
182+
assert middleware.config == mock_config
183+
otel_middleware = middleware.create_open_telemetry_middleware(mock_app)
184+
assert otel_middleware is not None

tests/instruments/test_opentelemetry.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77
import litestar
88
import pytest
99
from fastapi.testclient import TestClient as FastAPITestClient
10-
from litestar.middleware.base import DefineMiddleware
1110
from litestar.testing import TestClient as LitestarTestClient
1211
from opentelemetry.instrumentation.dependencies import DependencyConflictError
1312

1413
from microbootstrap import OpentelemetryConfig
1514
from microbootstrap.bootstrappers.fastapi import FastApiOpentelemetryInstrument
16-
from microbootstrap.bootstrappers.litestar import LitestarOpentelemetryInstrument
15+
from microbootstrap.bootstrappers.litestar import (
16+
LitestarOpentelemetryInstrument,
17+
LitestarOpenTelemetryInstrumentationMiddleware,
18+
)
1719
from microbootstrap.instruments import opentelemetry_instrument
1820
from microbootstrap.instruments.opentelemetry_instrument import OpentelemetryInstrument
1921

@@ -61,7 +63,7 @@ def test_litestar_opentelemetry_bootstrap(
6163
assert "middleware" in opentelemetry_bootstrap_result
6264
assert isinstance(opentelemetry_bootstrap_result["middleware"], list)
6365
assert len(opentelemetry_bootstrap_result["middleware"]) == 1
64-
assert isinstance(opentelemetry_bootstrap_result["middleware"][0], DefineMiddleware)
66+
assert isinstance(opentelemetry_bootstrap_result["middleware"][0], LitestarOpenTelemetryInstrumentationMiddleware)
6567

6668

6769
def test_litestar_opentelemetry_teardown(
@@ -83,9 +85,9 @@ def test_litestar_opentelemetry_bootstrap_working(
8385
opentelemetry_bootstrap_result: typing.Final = test_opentelemetry_instrument.bootstrap_before()
8486

8587
opentelemetry_middleware = opentelemetry_bootstrap_result["middleware"][0]
86-
assert isinstance(opentelemetry_middleware, DefineMiddleware)
88+
assert isinstance(opentelemetry_middleware, LitestarOpenTelemetryInstrumentationMiddleware)
8789
async_mock.__name__ = "test-name"
88-
opentelemetry_middleware.middleware.__call__ = async_mock # type: ignore[operator]
90+
opentelemetry_middleware.handle = async_mock # type: ignore[method-assign]
8991

9092
@litestar.get("/test-handler")
9193
async def test_handler() -> None:

0 commit comments

Comments
 (0)