From 211d2d09a17e016d86122d162f74e5edf1c7728a Mon Sep 17 00:00:00 2001 From: EGOROV Vadim Date: Wed, 25 Feb 2026 15:28:14 +0300 Subject: [PATCH 1/6] fix_opentelemetry_span_names --- microbootstrap/bootstrappers/litestar.py | 57 +++++++++++++----------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/microbootstrap/bootstrappers/litestar.py b/microbootstrap/bootstrappers/litestar.py index a864c73..4aa7a66 100644 --- a/microbootstrap/bootstrappers/litestar.py +++ b/microbootstrap/bootstrappers/litestar.py @@ -2,19 +2,16 @@ import typing import litestar -import litestar.exceptions -import litestar.types import typing_extensions from litestar import openapi from litestar.config.cors import CORSConfig as LitestarCorsConfig from litestar.contrib.opentelemetry.config import ( OpenTelemetryConfig as LitestarOpentelemetryConfig, ) -from litestar.contrib.opentelemetry.middleware import ( - OpenTelemetryInstrumentationMiddleware, -) from litestar.contrib.prometheus import PrometheusConfig, PrometheusController +from litestar.middleware import ASGIMiddleware from litestar.openapi.plugins import SwaggerRenderPlugin +from litestar.types.asgi_types import ASGIApp, Scope from litestar_offline_docs import generate_static_files_config from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware from opentelemetry.util.http import get_excluded_urls @@ -43,6 +40,7 @@ if typing.TYPE_CHECKING: from litestar.contrib.opentelemetry import OpenTelemetryConfig from litestar.types import ASGIApp, Scope + from litestar.types.asgi_types import Receive, Send class LitestarBootstrapper( @@ -155,23 +153,28 @@ def build_litestar_route_details_from_scope( return method, {} -class LitestarOpenTelemetryInstrumentationMiddleware(OpenTelemetryInstrumentationMiddleware): - def __init__(self, app: ASGIApp, config: OpenTelemetryConfig) -> None: - super().__init__( - app=app, - config=config, - ) - self.open_telemetry_middleware = OpenTelemetryMiddleware( - app=app, - client_request_hook=config.client_request_hook_handler, # type: ignore[arg-type] - client_response_hook=config.client_response_hook_handler, # type: ignore[arg-type] - default_span_details=build_litestar_route_details_from_scope, - excluded_urls=get_excluded_urls(config.exclude_urls_env_key), - meter=config.meter, - meter_provider=config.meter_provider, - server_request_hook=config.server_request_hook_handler, - tracer_provider=config.tracer_provider, - ) +class LitestarOpenTelemetryInstrumentationMiddleware(ASGIMiddleware): + def __init__(self, config: OpenTelemetryConfig) -> None: + self.config = config + self.open_telemetry_middleware = None + + def create_open_telemetry_middleware(self, app: ASGIApp) -> OpenTelemetryMiddleware: + if not self.open_telemetry_middleware: + self.open_telemetry_middleware = OpenTelemetryMiddleware( + app=app, + client_request_hook=self.config.client_request_hook_handler, # type: ignore[arg-type] + client_response_hook=self.config.client_response_hook_handler, # type: ignore[arg-type] + default_span_details=build_litestar_route_details_from_scope, + excluded_urls=get_excluded_urls(self.config.exclude_urls_env_key), + meter=self.config.meter, + meter_provider=self.config.meter_provider, + server_request_hook=self.config.server_request_hook_handler, + tracer_provider=self.config.tracer_provider, + ) + return self.open_telemetry_middleware + + async def handle(self, scope: Scope, receive: Receive, send: Send, next_app: ASGIApp) -> None: + await self.create_open_telemetry_middleware(next_app)(scope, receive, send) @LitestarBootstrapper.use_instrument() @@ -179,10 +182,12 @@ class LitestarOpentelemetryInstrument(OpentelemetryInstrument): def bootstrap_before(self) -> dict[str, typing.Any]: return { "middleware": [ - LitestarOpentelemetryConfig( - tracer_provider=self.tracer_provider, - middleware_class=LitestarOpenTelemetryInstrumentationMiddleware, - ).middleware, + LitestarOpenTelemetryInstrumentationMiddleware( + LitestarOpentelemetryConfig( + tracer_provider=self.tracer_provider, + middleware_class=LitestarOpenTelemetryInstrumentationMiddleware, + ) + ) ] } From a1b268178d9a282a4afc7da6a057d648143e9615 Mon Sep 17 00:00:00 2001 From: EGOROV Vadim Date: Wed, 25 Feb 2026 15:33:52 +0300 Subject: [PATCH 2/6] fix creating middleware --- microbootstrap/bootstrappers/litestar.py | 25 +++++++++++------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/microbootstrap/bootstrappers/litestar.py b/microbootstrap/bootstrappers/litestar.py index 4aa7a66..21deb3a 100644 --- a/microbootstrap/bootstrappers/litestar.py +++ b/microbootstrap/bootstrappers/litestar.py @@ -156,22 +156,19 @@ def build_litestar_route_details_from_scope( class LitestarOpenTelemetryInstrumentationMiddleware(ASGIMiddleware): def __init__(self, config: OpenTelemetryConfig) -> None: self.config = config - self.open_telemetry_middleware = None def create_open_telemetry_middleware(self, app: ASGIApp) -> OpenTelemetryMiddleware: - if not self.open_telemetry_middleware: - self.open_telemetry_middleware = OpenTelemetryMiddleware( - app=app, - client_request_hook=self.config.client_request_hook_handler, # type: ignore[arg-type] - client_response_hook=self.config.client_response_hook_handler, # type: ignore[arg-type] - default_span_details=build_litestar_route_details_from_scope, - excluded_urls=get_excluded_urls(self.config.exclude_urls_env_key), - meter=self.config.meter, - meter_provider=self.config.meter_provider, - server_request_hook=self.config.server_request_hook_handler, - tracer_provider=self.config.tracer_provider, - ) - return self.open_telemetry_middleware + return OpenTelemetryMiddleware( + app=app, + client_request_hook=self.config.client_request_hook_handler, # type: ignore[arg-type] + client_response_hook=self.config.client_response_hook_handler, # type: ignore[arg-type] + default_span_details=build_litestar_route_details_from_scope, + excluded_urls=get_excluded_urls(self.config.exclude_urls_env_key), + meter=self.config.meter, + meter_provider=self.config.meter_provider, + server_request_hook=self.config.server_request_hook_handler, + tracer_provider=self.config.tracer_provider, + ) async def handle(self, scope: Scope, receive: Receive, send: Send, next_app: ASGIApp) -> None: await self.create_open_telemetry_middleware(next_app)(scope, receive, send) From b249b69297b2b7b98a506bfb43ccdd15c210a5f6 Mon Sep 17 00:00:00 2001 From: EGOROV Vadim Date: Wed, 25 Feb 2026 16:02:59 +0300 Subject: [PATCH 3/6] test opentelemetry --- tests/bootstrappers/test_litestar_opentelemetry.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/bootstrappers/test_litestar_opentelemetry.py b/tests/bootstrappers/test_litestar_opentelemetry.py index 8893b59..495f4b8 100644 --- a/tests/bootstrappers/test_litestar_opentelemetry.py +++ b/tests/bootstrappers/test_litestar_opentelemetry.py @@ -120,16 +120,17 @@ def test_litestar_opentelemetry_instrument_uses_custom_middleware( @pytest.mark.parametrize( - ("path", "expected_span_name"), + ("path", "expected_span_name", "expected_path_template"), [ - ("/users/123", "GET /users/{user_id}"), - ("/users/", "GET /users/"), - ("/", "GET /"), + ("/users/123", "GET /users/{user_id}", "/users/{user_id}"), + ("/users/", "GET /users/", "/users"), + ("/", "GET /", "/"), ], ) def test_litestar_opentelemetry_integration_with_path_templates( path: str, expected_span_name: str, + expected_path_template: str, minimal_opentelemetry_config: OpentelemetryConfig, ) -> None: @litestar.get("/users/{user_id:int}") @@ -158,6 +159,7 @@ async def root() -> dict[str, str]: response: typing.Final = client.get(path) assert response.status_code == HTTP_200_OK assert mock_function.called + assert mock_function.call_args_list[0].args[0].get("path_template") == expected_path_template def test_litestar_opentelemetry_middleware_initialization() -> None: From 1cd3f63909bcdecaad756e1c063dc9fffe18a1af Mon Sep 17 00:00:00 2001 From: EGOROV Vadim Date: Mon, 2 Mar 2026 15:05:43 +0300 Subject: [PATCH 4/6] fix tests --- microbootstrap/bootstrappers/litestar.py | 4 ++-- tests/bootstrappers/test_litestar_opentelemetry.py | 12 ++++++------ tests/instruments/test_opentelemetry.py | 12 +++++++----- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/microbootstrap/bootstrappers/litestar.py b/microbootstrap/bootstrappers/litestar.py index 21deb3a..736bedc 100644 --- a/microbootstrap/bootstrappers/litestar.py +++ b/microbootstrap/bootstrappers/litestar.py @@ -171,7 +171,7 @@ def create_open_telemetry_middleware(self, app: ASGIApp) -> OpenTelemetryMiddlew ) async def handle(self, scope: Scope, receive: Receive, send: Send, next_app: ASGIApp) -> None: - await self.create_open_telemetry_middleware(next_app)(scope, receive, send) + await self.create_open_telemetry_middleware(next_app)(scope, receive, send) # type: ignore[arg-type] @LitestarBootstrapper.use_instrument() @@ -182,7 +182,7 @@ def bootstrap_before(self) -> dict[str, typing.Any]: LitestarOpenTelemetryInstrumentationMiddleware( LitestarOpentelemetryConfig( tracer_provider=self.tracer_provider, - middleware_class=LitestarOpenTelemetryInstrumentationMiddleware, + middleware_class=LitestarOpenTelemetryInstrumentationMiddleware, # type: ignore[arg-type] ) ) ] diff --git a/tests/bootstrappers/test_litestar_opentelemetry.py b/tests/bootstrappers/test_litestar_opentelemetry.py index 495f4b8..71e1be6 100644 --- a/tests/bootstrappers/test_litestar_opentelemetry.py +++ b/tests/bootstrappers/test_litestar_opentelemetry.py @@ -115,8 +115,8 @@ def test_litestar_opentelemetry_instrument_uses_custom_middleware( assert "middleware" in bootstrap_result assert len(bootstrap_result["middleware"]) == 1 - middleware_config: typing.Final = bootstrap_result["middleware"][0] - assert middleware_config.middleware == LitestarOpenTelemetryInstrumentationMiddleware + middleware_config: typing.Final = bootstrap_result["middleware"][0].config + assert middleware_config.middleware.middleware == LitestarOpenTelemetryInstrumentationMiddleware @pytest.mark.parametrize( @@ -177,8 +177,8 @@ def test_litestar_opentelemetry_middleware_initialization() -> None: mock_config.server_request_hook_handler = None mock_config.tracer_provider = None - middleware: typing.Final = LitestarOpenTelemetryInstrumentationMiddleware(app=mock_app, config=mock_config) + middleware: typing.Final = LitestarOpenTelemetryInstrumentationMiddleware(config=mock_config) - assert middleware.app == mock_app - assert hasattr(middleware, "open_telemetry_middleware") - assert middleware.open_telemetry_middleware is not None + assert middleware.config == mock_config + otel_middleware = middleware.create_open_telemetry_middleware(mock_app) + assert otel_middleware is not None diff --git a/tests/instruments/test_opentelemetry.py b/tests/instruments/test_opentelemetry.py index c09aa63..6794749 100644 --- a/tests/instruments/test_opentelemetry.py +++ b/tests/instruments/test_opentelemetry.py @@ -7,13 +7,15 @@ import litestar import pytest from fastapi.testclient import TestClient as FastAPITestClient -from litestar.middleware.base import DefineMiddleware from litestar.testing import TestClient as LitestarTestClient from opentelemetry.instrumentation.dependencies import DependencyConflictError from microbootstrap import OpentelemetryConfig from microbootstrap.bootstrappers.fastapi import FastApiOpentelemetryInstrument -from microbootstrap.bootstrappers.litestar import LitestarOpentelemetryInstrument +from microbootstrap.bootstrappers.litestar import ( + LitestarOpentelemetryInstrument, + LitestarOpenTelemetryInstrumentationMiddleware, +) from microbootstrap.instruments import opentelemetry_instrument from microbootstrap.instruments.opentelemetry_instrument import OpentelemetryInstrument @@ -61,7 +63,7 @@ def test_litestar_opentelemetry_bootstrap( assert "middleware" in opentelemetry_bootstrap_result assert isinstance(opentelemetry_bootstrap_result["middleware"], list) assert len(opentelemetry_bootstrap_result["middleware"]) == 1 - assert isinstance(opentelemetry_bootstrap_result["middleware"][0], DefineMiddleware) + assert isinstance(opentelemetry_bootstrap_result["middleware"][0], LitestarOpenTelemetryInstrumentationMiddleware) def test_litestar_opentelemetry_teardown( @@ -83,9 +85,9 @@ def test_litestar_opentelemetry_bootstrap_working( opentelemetry_bootstrap_result: typing.Final = test_opentelemetry_instrument.bootstrap_before() opentelemetry_middleware = opentelemetry_bootstrap_result["middleware"][0] - assert isinstance(opentelemetry_middleware, DefineMiddleware) + assert isinstance(opentelemetry_middleware, LitestarOpenTelemetryInstrumentationMiddleware) async_mock.__name__ = "test-name" - opentelemetry_middleware.middleware.__call__ = async_mock # type: ignore[operator] + opentelemetry_middleware.handle = async_mock # type: ignore[method-assign] @litestar.get("/test-handler") async def test_handler() -> None: From 9554e0d9ebb0805e3ceb093db2c6e1c98382c892 Mon Sep 17 00:00:00 2001 From: EGOROV Vadim Date: Mon, 2 Mar 2026 15:32:47 +0300 Subject: [PATCH 5/6] fix pyroscope --- pyproject.toml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 83125d7..71b3454 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ dependencies = [ "rich>=13", "sentry-sdk>=2.7", "structlog>=24", - "pyroscope-io; platform_system != 'Windows'", + "pyroscope-io<=1.0.0; platform_system != 'Windows'", "opentelemetry-distro[otlp]>=0.54b1", "opentelemetry-instrumentation-aio-pika>=0.54b1", "opentelemetry-instrumentation-aiohttp-client>=0.54b1", @@ -94,10 +94,17 @@ dev = [ requires = ["uv_build"] build-backend = "uv_build" +[[tool.uv.index]] +url = "https://artifactory.raiffeisen.ru/artifactory/api/pypi/remote-pypi/simple" + [tool.uv.build-backend] module-name = "microbootstrap" module-root = "" +[[tool.uv.index]] +url = "https://pypi.org/simple" +default = true + [tool.mypy] plugins = ["pydantic.mypy"] files = ["microbootstrap", "tests"] From 33882b7c9ac79c2f7a48d438edded0fd9643b6e5 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Tue, 3 Mar 2026 10:13:14 +0300 Subject: [PATCH 6/6] Apply suggestion from @vrslev --- pyproject.toml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 71b3454..ff7afe3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -94,17 +94,10 @@ dev = [ requires = ["uv_build"] build-backend = "uv_build" -[[tool.uv.index]] -url = "https://artifactory.raiffeisen.ru/artifactory/api/pypi/remote-pypi/simple" - [tool.uv.build-backend] module-name = "microbootstrap" module-root = "" -[[tool.uv.index]] -url = "https://pypi.org/simple" -default = true - [tool.mypy] plugins = ["pydantic.mypy"] files = ["microbootstrap", "tests"]