From 83d4863edc1c6a8573bb58469569dc356cf686d9 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Fri, 15 Nov 2024 09:12:12 -0600 Subject: [PATCH 01/37] fastapi: fix wrapping of middlewares --- .../instrumentation/fastapi/__init__.py | 58 ++++++++++++------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py index a19480b234..7d8aef375a 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py @@ -179,10 +179,14 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A from __future__ import annotations import logging +import types from typing import Collection, Literal import fastapi +from starlette.applications import Starlette +from starlette.middleware.error import ServerErrorMiddleware from starlette.routing import Match +from starlette.types import ASGIApp from opentelemetry.instrumentation._semconv import ( _get_schema_url, @@ -280,21 +284,38 @@ def instrument_app( schema_url=_get_schema_url(sem_conv_opt_in_mode), ) - app.add_middleware( - OpenTelemetryMiddleware, - excluded_urls=excluded_urls, - default_span_details=_get_default_span_details, - server_request_hook=server_request_hook, - client_request_hook=client_request_hook, - client_response_hook=client_response_hook, - # Pass in tracer/meter to get __name__and __version__ of fastapi instrumentation - tracer=tracer, - meter=meter, - http_capture_headers_server_request=http_capture_headers_server_request, - http_capture_headers_server_response=http_capture_headers_server_response, - http_capture_headers_sanitize_fields=http_capture_headers_sanitize_fields, - exclude_spans=exclude_spans, - ) + # Instead of using `app.add_middleware` we monkey patch `build_middleware_stack` to insert our middleware + # as the outermost middleware. + # Otherwise `OpenTelemetryMiddleware` would have unhandled exceptions tearing through it and would not be able + # to faithfully record what is returned to the client since it technically cannot know what `ServerErrorMiddleware` is going to do. + + def build_middleware_stack(self: Starlette) -> ASGIApp: + stack = super().build_middleware_stack() + stack = OpenTelemetryMiddleware( + stack, + excluded_urls=excluded_urls, + default_span_details=_get_default_span_details, + server_request_hook=server_request_hook, + client_request_hook=client_request_hook, + client_response_hook=client_response_hook, + # Pass in tracer/meter to get __name__and __version__ of fastapi instrumentation + tracer=tracer, + meter=meter, + http_capture_headers_server_request=http_capture_headers_server_request, + http_capture_headers_server_response=http_capture_headers_server_response, + http_capture_headers_sanitize_fields=http_capture_headers_sanitize_fields, + exclude_spans=exclude_spans, + ) + # Wrap in an outer layer of ServerErrorMiddleware so that any exceptions raised in OpenTelemetryMiddleware + # are handled. + # This should not happen unless there is a bug in OpenTelemetryMiddleware, but if there is we don't want that + # to impact the user's application just because we wrapped the middlewares in this order. + stack = ServerErrorMiddleware(stack) + return stack + + app._original_build_middleware_stack = app.build_middleware_stack + app.build_middleware_stack = types.MethodType(build_middleware_stack, app) + app._is_instrumented_by_opentelemetry = True if app not in _InstrumentedFastAPI._instrumented_fastapi_apps: _InstrumentedFastAPI._instrumented_fastapi_apps.add(app) @@ -305,11 +326,8 @@ def instrument_app( @staticmethod def uninstrument_app(app: fastapi.FastAPI): - app.user_middleware = [ - x - for x in app.user_middleware - if x.cls is not OpenTelemetryMiddleware - ] + app.build_middleware_stack = app._original_build_middleware_stack + del app._original_build_middleware_stack app.middleware_stack = app.build_middleware_stack() app._is_instrumented_by_opentelemetry = False From d635026c6d7672061b88bcef0d93a1b7d1b640f2 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Fri, 15 Nov 2024 09:30:46 -0600 Subject: [PATCH 02/37] fix import, super --- .../src/opentelemetry/instrumentation/fastapi/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py index 7d8aef375a..7dbc28ba4e 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py @@ -184,7 +184,7 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A import fastapi from starlette.applications import Starlette -from starlette.middleware.error import ServerErrorMiddleware +from starlette.middleware.errors import ServerErrorMiddleware from starlette.routing import Match from starlette.types import ASGIApp @@ -290,7 +290,7 @@ def instrument_app( # to faithfully record what is returned to the client since it technically cannot know what `ServerErrorMiddleware` is going to do. def build_middleware_stack(self: Starlette) -> ASGIApp: - stack = super().build_middleware_stack() + stack = type(self).build_middleware_stack(self) stack = OpenTelemetryMiddleware( stack, excluded_urls=excluded_urls, From 23a04a5db10184da7d4bd7486cd4a27682a5a5e8 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Fri, 15 Nov 2024 09:34:34 -0600 Subject: [PATCH 03/37] add test --- .../tests/test_fastapi_instrumentation.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index fdbad4effb..245b3c6c98 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -204,12 +204,20 @@ async def _(param: str): @app.get("/healthzz") async def _(): return {"message": "ok"} + + @app.get("/error") + async def _(): + raise UnhandledException("This is an unhandled exception") app.mount("/sub", app=sub_app) return app +class UnhandledException(Exception): + pass + + class TestBaseManualFastAPI(TestBaseFastAPI): @classmethod def setUpClass(cls): @@ -398,6 +406,26 @@ def test_fastapi_excluded_urls(self): spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 0) + def test_fastapi_unhandled_exception(self): + """If the application has an unhandled error the instrumentation should capture that a 500 response is returned.""" + try: + self._client.get("/error") + except UnhandledException: + pass + else: + self.fail("Expected UnhandledException") + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 3) + for span in spans: + self.assertIn("GET /error", span.name) + self.assertEqual( + span.attributes[SpanAttributes.HTTP_ROUTE], "/error" + ) + self.assertEqual( + span.attributes[SpanAttributes.HTTP_STATUS_CODE], 500 + ) + def test_fastapi_excluded_urls_not_env(self): """Ensure that given fastapi routes are excluded when passed explicitly (not in the environment)""" app = self._create_app_explicit_excluded_urls() From a979966677041fb6777f9c650d6296b18ed60557 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Fri, 15 Nov 2024 09:35:33 -0600 Subject: [PATCH 04/37] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d0ce4370a..813ce1da7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - `opentelemetry-instrumentation-redis` Add missing entry in doc string for `def _instrument` ([#3247](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3247)) +- `opentelemetry-instrumentation-fastapi`: instrument unhandled exceptions + ([#3012](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3012)) ## Version 1.30.0/0.51b0 (2025-02-03) From 8057faa243152483b63a0c120af2f1044114a439 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Fri, 15 Nov 2024 10:05:50 -0600 Subject: [PATCH 05/37] lint --- .../tests/test_fastapi_instrumentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index 245b3c6c98..f776ea2b91 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -204,7 +204,7 @@ async def _(param: str): @app.get("/healthzz") async def _(): return {"message": "ok"} - + @app.get("/error") async def _(): raise UnhandledException("This is an unhandled exception") From c1c430093c337db7e0b32ed7c2cdb8e43f7b9398 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Fri, 15 Nov 2024 10:13:56 -0600 Subject: [PATCH 06/37] lint --- .../src/opentelemetry/instrumentation/fastapi/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py index 7dbc28ba4e..dbdc1e4c1e 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py @@ -314,7 +314,9 @@ def build_middleware_stack(self: Starlette) -> ASGIApp: return stack app._original_build_middleware_stack = app.build_middleware_stack - app.build_middleware_stack = types.MethodType(build_middleware_stack, app) + app.build_middleware_stack = types.MethodType( + build_middleware_stack, app + ) app._is_instrumented_by_opentelemetry = True if app not in _InstrumentedFastAPI._instrumented_fastapi_apps: From 121813e7502a9534f3bc9d12cb717621208b480d Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Fri, 15 Nov 2024 10:33:47 -0600 Subject: [PATCH 07/37] fix --- .../src/opentelemetry/instrumentation/fastapi/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py index dbdc1e4c1e..4c54237ffd 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py @@ -328,8 +328,12 @@ def build_middleware_stack(self: Starlette) -> ASGIApp: @staticmethod def uninstrument_app(app: fastapi.FastAPI): - app.build_middleware_stack = app._original_build_middleware_stack - del app._original_build_middleware_stack + original_build_middleware_stack = getattr( + app, "_original_build_middleware_stack", None + ) + if original_build_middleware_stack: + app.build_middleware_stack = original_build_middleware_stack + del app._original_build_middleware_stack app.middleware_stack = app.build_middleware_stack() app._is_instrumented_by_opentelemetry = False From 04cf5d5183afc5fda7368b1a0b27211275c5cb6a Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Fri, 15 Nov 2024 11:01:01 -0600 Subject: [PATCH 08/37] ci From fdba8204c02633deebb723ae602f0a19ebf4812f Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Tue, 26 Nov 2024 15:32:09 -0800 Subject: [PATCH 09/37] fix wip --- .../instrumentation/fastapi/__init__.py | 69 ++++++++----------- 1 file changed, 28 insertions(+), 41 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py index 4c54237ffd..7dd357240d 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py @@ -203,9 +203,9 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A from opentelemetry.instrumentation.fastapi.package import _instruments from opentelemetry.instrumentation.fastapi.version import __version__ from opentelemetry.instrumentation.instrumentor import BaseInstrumentor -from opentelemetry.metrics import get_meter +from opentelemetry.metrics import Meter, get_meter from opentelemetry.semconv.trace import SpanAttributes -from opentelemetry.trace import get_tracer +from opentelemetry.trace import Tracer, get_tracer from opentelemetry.util.http import ( get_excluded_urls, parse_excluded_urls, @@ -230,9 +230,9 @@ def instrument_app( server_request_hook: ServerRequestHook = None, client_request_hook: ClientRequestHook = None, client_response_hook: ClientResponseHook = None, - tracer_provider=None, - meter_provider=None, - excluded_urls=None, + tracer_provider: Tracer | None = None, + meter_provider: Meter | None = None, + excluded_urls: str | None = None, http_capture_headers_server_request: list[str] | None = None, http_capture_headers_server_response: list[str] | None = None, http_capture_headers_sanitize_fields: list[str] | None = None, @@ -290,9 +290,9 @@ def instrument_app( # to faithfully record what is returned to the client since it technically cannot know what `ServerErrorMiddleware` is going to do. def build_middleware_stack(self: Starlette) -> ASGIApp: - stack = type(self).build_middleware_stack(self) - stack = OpenTelemetryMiddleware( - stack, + app = type(self).build_middleware_stack(self) + app = OpenTelemetryMiddleware( + app, excluded_urls=excluded_urls, default_span_details=_get_default_span_details, server_request_hook=server_request_hook, @@ -310,8 +310,9 @@ def build_middleware_stack(self: Starlette) -> ASGIApp: # are handled. # This should not happen unless there is a bug in OpenTelemetryMiddleware, but if there is we don't want that # to impact the user's application just because we wrapped the middlewares in this order. - stack = ServerErrorMiddleware(stack) - return stack + app = ServerErrorMiddleware(app) + return app + app._original_build_middleware_stack = app.build_middleware_stack app.build_middleware_stack = types.MethodType( @@ -385,43 +386,29 @@ class _InstrumentedFastAPI(fastapi.FastAPI): _server_request_hook: ServerRequestHook = None _client_request_hook: ClientRequestHook = None _client_response_hook: ClientResponseHook = None + _http_capture_headers_server_request: list[str] | None = None + _http_capture_headers_server_response: list[str] | None = None + _http_capture_headers_sanitize_fields: list[str] | None = None + _exclude_spans: list[Literal["receive", "send"]] | None = None + _instrumented_fastapi_apps = set() _sem_conv_opt_in_mode = _StabilityMode.DEFAULT def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - tracer = get_tracer( - __name__, - __version__, - _InstrumentedFastAPI._tracer_provider, - schema_url=_get_schema_url( - _InstrumentedFastAPI._sem_conv_opt_in_mode - ), - ) - meter = get_meter( - __name__, - __version__, - _InstrumentedFastAPI._meter_provider, - schema_url=_get_schema_url( - _InstrumentedFastAPI._sem_conv_opt_in_mode - ), - ) - self.add_middleware( - OpenTelemetryMiddleware, - excluded_urls=_InstrumentedFastAPI._excluded_urls, - default_span_details=_get_default_span_details, - server_request_hook=_InstrumentedFastAPI._server_request_hook, - client_request_hook=_InstrumentedFastAPI._client_request_hook, - client_response_hook=_InstrumentedFastAPI._client_response_hook, - # Pass in tracer/meter to get __name__and __version__ of fastapi instrumentation - tracer=tracer, - meter=meter, - http_capture_headers_server_request=_InstrumentedFastAPI._http_capture_headers_server_request, - http_capture_headers_server_response=_InstrumentedFastAPI._http_capture_headers_server_response, - http_capture_headers_sanitize_fields=_InstrumentedFastAPI._http_capture_headers_sanitize_fields, - exclude_spans=_InstrumentedFastAPI._exclude_spans, + FastAPIInstrumentor.instrument_app( + self, + server_request_hook=self._server_request_hook, + client_request_hook=self._client_request_hook, + client_response_hook=self._client_response_hook, + tracer_provider=self._tracer_provider, + meter_provider=self._meter_provider, + excluded_urls=self._excluded_urls, + http_capture_headers_server_request=self._http_capture_headers_server_request, + http_capture_headers_server_response=self._http_capture_headers_server_response, + http_capture_headers_sanitize_fields=self._http_capture_headers_sanitize_fields, + exclude_spans=self._exclude_spans, ) - self._is_instrumented_by_opentelemetry = True _InstrumentedFastAPI._instrumented_fastapi_apps.add(self) def __del__(self): From 0da827b8b95ef41f1b8027da794a4e6a2ee5eb20 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Tue, 26 Nov 2024 16:32:55 -0800 Subject: [PATCH 10/37] fix --- .../instrumentation/fastapi/__init__.py | 15 +++---- .../tests/test_fastapi_instrumentation.py | 39 ++++--------------- 2 files changed, 13 insertions(+), 41 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py index 7dd357240d..2bbc1df9c3 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py @@ -203,9 +203,9 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A from opentelemetry.instrumentation.fastapi.package import _instruments from opentelemetry.instrumentation.fastapi.version import __version__ from opentelemetry.instrumentation.instrumentor import BaseInstrumentor -from opentelemetry.metrics import Meter, get_meter +from opentelemetry.metrics import MeterProvider, get_meter from opentelemetry.semconv.trace import SpanAttributes -from opentelemetry.trace import Tracer, get_tracer +from opentelemetry.trace import TracerProvider, get_tracer from opentelemetry.util.http import ( get_excluded_urls, parse_excluded_urls, @@ -230,8 +230,8 @@ def instrument_app( server_request_hook: ServerRequestHook = None, client_request_hook: ClientRequestHook = None, client_response_hook: ClientResponseHook = None, - tracer_provider: Tracer | None = None, - meter_provider: Meter | None = None, + tracer_provider: TracerProvider | None = None, + meter_provider: MeterProvider | None = None, excluded_urls: str | None = None, http_capture_headers_server_request: list[str] | None = None, http_capture_headers_server_response: list[str] | None = None, @@ -362,12 +362,7 @@ def _instrument(self, **kwargs): _InstrumentedFastAPI._http_capture_headers_sanitize_fields = ( kwargs.get("http_capture_headers_sanitize_fields") ) - _excluded_urls = kwargs.get("excluded_urls") - _InstrumentedFastAPI._excluded_urls = ( - _excluded_urls_from_env - if _excluded_urls is None - else parse_excluded_urls(_excluded_urls) - ) + _InstrumentedFastAPI._excluded_urls = kwargs.get("excluded_urls") _InstrumentedFastAPI._meter_provider = kwargs.get("meter_provider") _InstrumentedFastAPI._exclude_spans = kwargs.get("exclude_spans") fastapi.FastAPI = _InstrumentedFastAPI diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index f776ea2b91..32b660c262 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -170,7 +170,8 @@ def setUp(self): self._instrumentor = otel_fastapi.FastAPIInstrumentor() self._app = self._create_app() self._app.add_middleware(HTTPSRedirectMiddleware) - self._client = TestClient(self._app) + self._client = TestClient(self._app, base_url="https://testserver:443") + self._client.__enter__() # run the lifespan, initialize the middleware stack def tearDown(self): super().tearDown() @@ -205,19 +206,11 @@ async def _(param: str): async def _(): return {"message": "ok"} - @app.get("/error") - async def _(): - raise UnhandledException("This is an unhandled exception") - app.mount("/sub", app=sub_app) return app -class UnhandledException(Exception): - pass - - class TestBaseManualFastAPI(TestBaseFastAPI): @classmethod def setUpClass(cls): @@ -406,26 +399,6 @@ def test_fastapi_excluded_urls(self): spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 0) - def test_fastapi_unhandled_exception(self): - """If the application has an unhandled error the instrumentation should capture that a 500 response is returned.""" - try: - self._client.get("/error") - except UnhandledException: - pass - else: - self.fail("Expected UnhandledException") - - spans = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans), 3) - for span in spans: - self.assertIn("GET /error", span.name) - self.assertEqual( - span.attributes[SpanAttributes.HTTP_ROUTE], "/error" - ) - self.assertEqual( - span.attributes[SpanAttributes.HTTP_STATUS_CODE], 500 - ) - def test_fastapi_excluded_urls_not_env(self): """Ensure that given fastapi routes are excluded when passed explicitly (not in the environment)""" app = self._create_app_explicit_excluded_urls() @@ -1152,9 +1125,13 @@ def test_request(self): def test_mulitple_way_instrumentation(self): self._instrumentor.instrument_app(self._app) count = 0 - for middleware in self._app.user_middleware: - if middleware.cls is OpenTelemetryMiddleware: + app = self._app.middleware_stack + while True: + if isinstance(app, OpenTelemetryMiddleware): count += 1 + if app is None: + break + app = getattr(app, "app", None) self.assertEqual(count, 1) def test_uninstrument_after_instrument(self): From af0bf2c6382f95916d92ec7bb73df368ab49a93b Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Tue, 26 Nov 2024 16:33:15 -0800 Subject: [PATCH 11/37] fix --- .../src/opentelemetry/instrumentation/fastapi/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py index 2bbc1df9c3..635833fa37 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py @@ -313,7 +313,6 @@ def build_middleware_stack(self: Starlette) -> ASGIApp: app = ServerErrorMiddleware(app) return app - app._original_build_middleware_stack = app.build_middleware_stack app.build_middleware_stack = types.MethodType( build_middleware_stack, app From d3b991021e77dffd6ad8edc62630a18e5eed3cc3 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Tue, 26 Nov 2024 16:39:28 -0800 Subject: [PATCH 12/37] lint --- .../tests/test_fastapi_instrumentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index 32b660c262..b093c0dcc3 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -171,7 +171,7 @@ def setUp(self): self._app = self._create_app() self._app.add_middleware(HTTPSRedirectMiddleware) self._client = TestClient(self._app, base_url="https://testserver:443") - self._client.__enter__() # run the lifespan, initialize the middleware stack + self._client.__enter__() # noqa: C2801 # run the lifespan, initialize the middleware stack def tearDown(self): super().tearDown() From bfe110c09f08594366fb5d45ff6df54978fc9934 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Tue, 26 Nov 2024 16:48:13 -0800 Subject: [PATCH 13/37] lint --- .../tests/test_fastapi_instrumentation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index b093c0dcc3..fda2b32e7a 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -171,7 +171,9 @@ def setUp(self): self._app = self._create_app() self._app.add_middleware(HTTPSRedirectMiddleware) self._client = TestClient(self._app, base_url="https://testserver:443") - self._client.__enter__() # noqa: C2801 # run the lifespan, initialize the middleware stack + # run the lifespan, initialize the middleware stack + # this is more in-line with what happens in a real application when the server starts up + self._client.__enter__() # pylint: disable=unnecessary-dunder-call def tearDown(self): super().tearDown() From 0c32f4d858afc94daade5e0be96f7f12b754a304 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Tue, 26 Nov 2024 22:07:19 -0800 Subject: [PATCH 14/37] Exit? --- .../tests/test_fastapi_instrumentation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index fda2b32e7a..473ae399d8 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -176,6 +176,7 @@ def setUp(self): self._client.__enter__() # pylint: disable=unnecessary-dunder-call def tearDown(self): + self._client.__exit__(None, None, None) # pylint: disable=unnecessary-dunder-call super().tearDown() self.env_patch.stop() self.exclude_patch.stop() From c75b2800a7d391a2e0c07d3ac4d33c704b0fbaef Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Wed, 27 Nov 2024 05:55:58 -0800 Subject: [PATCH 15/37] Update test_fastapi_instrumentation.py Co-authored-by: Riccardo Magliocchetti --- .../tests/test_fastapi_instrumentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index 473ae399d8..adff9f0911 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -1129,7 +1129,7 @@ def test_mulitple_way_instrumentation(self): self._instrumentor.instrument_app(self._app) count = 0 app = self._app.middleware_stack - while True: + while app is not None: if isinstance(app, OpenTelemetryMiddleware): count += 1 if app is None: From 3d7d537e5c342e74e1ba15c5d307126d90ea6dd3 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Wed, 27 Nov 2024 08:07:33 -0800 Subject: [PATCH 16/37] remove break --- .../tests/test_fastapi_instrumentation.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index adff9f0911..4aceb78731 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -1132,8 +1132,6 @@ def test_mulitple_way_instrumentation(self): while app is not None: if isinstance(app, OpenTelemetryMiddleware): count += 1 - if app is None: - break app = getattr(app, "app", None) self.assertEqual(count, 1) From f75dbefabf99450220d40db7bcbdd2d3ff710a7c Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Wed, 27 Nov 2024 08:08:53 -0800 Subject: [PATCH 17/37] fix --- .../tests/test_fastapi_instrumentation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index 4aceb78731..49f6fd8d3b 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -173,10 +173,10 @@ def setUp(self): self._client = TestClient(self._app, base_url="https://testserver:443") # run the lifespan, initialize the middleware stack # this is more in-line with what happens in a real application when the server starts up - self._client.__enter__() # pylint: disable=unnecessary-dunder-call + self._client_teardown = self._client.__enter__() # pylint: disable=unnecessary-dunder-call def tearDown(self): - self._client.__exit__(None, None, None) # pylint: disable=unnecessary-dunder-call + self._client_teardown.__exit__(None, None, None) # pylint: disable=unnecessary-dunder-call super().tearDown() self.env_patch.stop() self.exclude_patch.stop() From 834392f9e811590d9d2b59d95aa6fe0fb5fb3826 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Wed, 27 Nov 2024 08:09:55 -0800 Subject: [PATCH 18/37] remove dunders --- .../tests/test_fastapi_instrumentation.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index 49f6fd8d3b..b54772a1fb 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -15,6 +15,7 @@ # pylint: disable=too-many-lines import unittest +from contextlib import ExitStack from timeit import default_timer from unittest.mock import Mock, patch @@ -173,10 +174,11 @@ def setUp(self): self._client = TestClient(self._app, base_url="https://testserver:443") # run the lifespan, initialize the middleware stack # this is more in-line with what happens in a real application when the server starts up - self._client_teardown = self._client.__enter__() # pylint: disable=unnecessary-dunder-call + self._exit_stack = ExitStack() + self._exit_stack.enter_context(self._client) def tearDown(self): - self._client_teardown.__exit__(None, None, None) # pylint: disable=unnecessary-dunder-call + self._exit_stack.close() super().tearDown() self.env_patch.stop() self.exclude_patch.stop() From 14d8159a804bb167d747ae1954d10c8ae8e0f538 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Fri, 15 Nov 2024 09:34:34 -0600 Subject: [PATCH 19/37] add test --- .../tests/test_fastapi_instrumentation.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index b54772a1fb..7047482d31 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -210,12 +210,20 @@ async def _(param: str): @app.get("/healthzz") async def _(): return {"message": "ok"} + + @app.get("/error") + async def _(): + raise UnhandledException("This is an unhandled exception") app.mount("/sub", app=sub_app) return app +class UnhandledException(Exception): + pass + + class TestBaseManualFastAPI(TestBaseFastAPI): @classmethod def setUpClass(cls): @@ -404,6 +412,26 @@ def test_fastapi_excluded_urls(self): spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 0) + def test_fastapi_unhandled_exception(self): + """If the application has an unhandled error the instrumentation should capture that a 500 response is returned.""" + try: + self._client.get("/error") + except UnhandledException: + pass + else: + self.fail("Expected UnhandledException") + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 3) + for span in spans: + self.assertIn("GET /error", span.name) + self.assertEqual( + span.attributes[SpanAttributes.HTTP_ROUTE], "/error" + ) + self.assertEqual( + span.attributes[SpanAttributes.HTTP_STATUS_CODE], 500 + ) + def test_fastapi_excluded_urls_not_env(self): """Ensure that given fastapi routes are excluded when passed explicitly (not in the environment)""" app = self._create_app_explicit_excluded_urls() From 128cf89e893c0fa415d98c51d0e8b49d79a56b29 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Tue, 3 Dec 2024 08:09:13 -0800 Subject: [PATCH 20/37] lint --- .../tests/test_fastapi_instrumentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index 7047482d31..40c20500da 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -210,7 +210,7 @@ async def _(param: str): @app.get("/healthzz") async def _(): return {"message": "ok"} - + @app.get("/error") async def _(): raise UnhandledException("This is an unhandled exception") From d8ca85e8a72620967e0e40648149ee23326b3945 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Wed, 4 Dec 2024 09:34:46 -0600 Subject: [PATCH 21/37] add endpoint to class --- .../tests/test_fastapi_instrumentation.py | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index 40c20500da..0c2ce97cb6 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -234,6 +234,25 @@ def setUpClass(cls): super(TestBaseManualFastAPI, cls).setUpClass() + def test_fastapi_unhandled_exception(self): + """If the application has an unhandled error the instrumentation should capture that a 500 response is returned.""" + try: + resp = self._client.get("/error") + assert resp.status_code == 500, resp.content # pragma: no cover, for debugging this test if an exception is _not_ raised + except UnhandledException: + pass + else: + self.fail("Expected UnhandledException") + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 3) + span = spans[0] + assert span.name == "GET /error http send" + assert span.attributes[SpanAttributes.HTTP_STATUS_CODE] == 500 + span = spans[2] + assert span.name == "GET /error" + assert span.attributes[SpanAttributes.HTTP_TARGET] == "/error" + def test_sub_app_fastapi_call(self): """ This test is to ensure that a span in case of a sub app targeted contains the correct server url @@ -412,26 +431,6 @@ def test_fastapi_excluded_urls(self): spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 0) - def test_fastapi_unhandled_exception(self): - """If the application has an unhandled error the instrumentation should capture that a 500 response is returned.""" - try: - self._client.get("/error") - except UnhandledException: - pass - else: - self.fail("Expected UnhandledException") - - spans = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans), 3) - for span in spans: - self.assertIn("GET /error", span.name) - self.assertEqual( - span.attributes[SpanAttributes.HTTP_ROUTE], "/error" - ) - self.assertEqual( - span.attributes[SpanAttributes.HTTP_STATUS_CODE], 500 - ) - def test_fastapi_excluded_urls_not_env(self): """Ensure that given fastapi routes are excluded when passed explicitly (not in the environment)""" app = self._create_app_explicit_excluded_urls() @@ -1010,6 +1009,10 @@ async def _(param: str): async def _(): return {"message": "ok"} + @app.get("/error") + async def _(): + raise UnhandledException("This is an unhandled exception") + app.mount("/sub", app=sub_app) return app From 4c4ac689a087195d43c2f92177aa1c34475fb0c7 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Wed, 4 Dec 2024 09:39:17 -0600 Subject: [PATCH 22/37] fmt --- .../tests/test_fastapi_instrumentation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index 0c2ce97cb6..3a84bc067c 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -238,7 +238,9 @@ def test_fastapi_unhandled_exception(self): """If the application has an unhandled error the instrumentation should capture that a 500 response is returned.""" try: resp = self._client.get("/error") - assert resp.status_code == 500, resp.content # pragma: no cover, for debugging this test if an exception is _not_ raised + assert ( + resp.status_code == 500 + ), resp.content # pragma: no cover, for debugging this test if an exception is _not_ raised except UnhandledException: pass else: From d7f7cc6dd5893671fa72384535db539fd50947c7 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Mon, 10 Feb 2025 05:00:17 -0800 Subject: [PATCH 23/37] pr feedback --- .../instrumentation/asgi/__init__.py | 6 ++-- .../instrumentation/fastapi/__init__.py | 33 +++++++++++++++---- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py index b060095160..9770d259a7 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py @@ -665,9 +665,9 @@ def __init__( # pylint: disable=too-many-statements async def __call__( self, - scope: dict[str, Any], - receive: Callable[[], Awaitable[dict[str, Any]]], - send: Callable[[dict[str, Any]], Awaitable[None]], + scope: typing.MutableMapping[str, Any], + receive: Callable[[], Awaitable[typing.MutableMapping[str, Any]]], + send: Callable[[typing.MutableMapping[str, Any]], Awaitable[None]], ) -> None: """The ASGI application diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py index 635833fa37..e106568026 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py @@ -178,6 +178,7 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A from __future__ import annotations +import functools import logging import types from typing import Collection, Literal @@ -226,7 +227,7 @@ class FastAPIInstrumentor(BaseInstrumentor): @staticmethod def instrument_app( - app, + app: fastapi.FastAPI, server_request_hook: ServerRequestHook = None, client_request_hook: ClientRequestHook = None, client_response_hook: ClientResponseHook = None, @@ -290,9 +291,11 @@ def instrument_app( # to faithfully record what is returned to the client since it technically cannot know what `ServerErrorMiddleware` is going to do. def build_middleware_stack(self: Starlette) -> ASGIApp: - app = type(self).build_middleware_stack(self) - app = OpenTelemetryMiddleware( - app, + inner_server_error_middleware: ASGIApp = ( + self._original_build_middleware_stack() + ) # type: ignore + otel_middleware = OpenTelemetryMiddleware( + inner_server_error_middleware, excluded_urls=excluded_urls, default_span_details=_get_default_span_details, server_request_hook=server_request_hook, @@ -310,12 +313,28 @@ def build_middleware_stack(self: Starlette) -> ASGIApp: # are handled. # This should not happen unless there is a bug in OpenTelemetryMiddleware, but if there is we don't want that # to impact the user's application just because we wrapped the middlewares in this order. - app = ServerErrorMiddleware(app) - return app + if isinstance( + inner_server_error_middleware, ServerErrorMiddleware + ): # usually true + outer_server_error_middleware = ServerErrorMiddleware( + app=otel_middleware, + handler=inner_server_error_middleware.handler, + debug=inner_server_error_middleware.debug, + ) + else: + # Something else seems to have patched things, or maybe Starlette changed. + # Just create a default ServerErrorMiddleware. + outer_server_error_middleware = ServerErrorMiddleware( + app=otel_middleware + ) + return outer_server_error_middleware app._original_build_middleware_stack = app.build_middleware_stack app.build_middleware_stack = types.MethodType( - build_middleware_stack, app + functools.wraps(app.build_middleware_stack)( + build_middleware_stack + ), + app, ) app._is_instrumented_by_opentelemetry = True From 00840d770845455808268347d0b050bdf8badf47 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Mon, 10 Feb 2025 05:04:13 -0800 Subject: [PATCH 24/37] move type ignores --- .../src/opentelemetry/instrumentation/fastapi/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py index e106568026..2ec551b682 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py @@ -291,9 +291,9 @@ def instrument_app( # to faithfully record what is returned to the client since it technically cannot know what `ServerErrorMiddleware` is going to do. def build_middleware_stack(self: Starlette) -> ASGIApp: - inner_server_error_middleware: ASGIApp = ( - self._original_build_middleware_stack() - ) # type: ignore + inner_server_error_middleware: ASGIApp = ( # type: ignore + self._original_build_middleware_stack() # type: ignore + ) otel_middleware = OpenTelemetryMiddleware( inner_server_error_middleware, excluded_urls=excluded_urls, From a5db6856defac073cec40c278d7438ec03b15581 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Mon, 10 Feb 2025 06:22:20 -0800 Subject: [PATCH 25/37] fix sphinx? --- docs/nitpick-exceptions.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/nitpick-exceptions.ini b/docs/nitpick-exceptions.ini index bf120765c9..5b9ed89163 100644 --- a/docs/nitpick-exceptions.ini +++ b/docs/nitpick-exceptions.ini @@ -44,6 +44,7 @@ py-class= psycopg.Connection psycopg.AsyncConnection ObjectProxy + fastapi.applications.FastAPI any= ; API From 0870986e265c5b0fae14a69fb7626a9cb0dbfcc0 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 11 Mar 2025 10:54:25 +0100 Subject: [PATCH 26/37] Update CHANGELOG.md --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0e91320c4..bb03ae126f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,7 +51,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `opentelemetry-instrumentation-requests` always record span status code in duration metric ([#3323](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3323)) - ## Version 1.30.0/0.51b0 (2025-02-03) ### Added From 2498309850586a76202ad30b0ee5fa8ac133aeb4 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Wed, 26 Mar 2025 09:30:13 -0500 Subject: [PATCH 27/37] update fastapi versions --- .../opentelemetry-instrumentation-fastapi/pyproject.toml | 2 +- .../src/opentelemetry/instrumentation/fastapi/package.py | 2 +- .../tests/test_fastapi_instrumentation.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/pyproject.toml b/instrumentation/opentelemetry-instrumentation-fastapi/pyproject.toml index ead53e19ee..ab7963c27d 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/pyproject.toml +++ b/instrumentation/opentelemetry-instrumentation-fastapi/pyproject.toml @@ -35,7 +35,7 @@ dependencies = [ [project.optional-dependencies] instruments = [ - "fastapi ~= 0.58", + "fastapi ~= 0.92", ] [project.entry-points.opentelemetry_instrumentor] diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/package.py b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/package.py index d95a2cf6d5..93e4534d3c 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/package.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/package.py @@ -13,7 +13,7 @@ # limitations under the License. -_instruments = ("fastapi ~= 0.58",) +_instruments = ("fastapi ~= 0.92",) _supports_metrics = True diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index 3a84bc067c..e8c23201d4 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -1074,7 +1074,7 @@ def mock_version_with_fastapi(*args, **kwargs): req_name = args[0] if req_name == "fastapi": # TODO: Value now matters - return "0.58" + return "0.109" raise PackageNotFoundError() @@ -1082,7 +1082,7 @@ def mock_version_with_old_fastapi(*args, **kwargs): req_name = args[0] if req_name == "fastapi": # TODO: Value now matters - return "0.57" + return "0.92" raise PackageNotFoundError() From 289f788a24cfd8960aaa68c1794bf220f518d3dc Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Tue, 8 Apr 2025 16:06:53 -0500 Subject: [PATCH 28/37] fix? --- .../tests/test_fastapi_instrumentation.py | 3 ++- .../src/opentelemetry/instrumentation/bootstrap_gen.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index 9ebef96f31..08e71005c0 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -14,6 +14,7 @@ # pylint: disable=too-many-lines +from importlib.metadata import PackageNotFoundError import unittest from contextlib import ExitStack from timeit import default_timer @@ -1090,7 +1091,7 @@ def mock_version_with_old_fastapi(*args, **kwargs): def mock_version_without_fastapi(*args, **kwargs): raise PackageNotFoundError() - + class TestAutoInstrumentation(TestBaseAutoFastAPI): """Test the auto-instrumented variant diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py index 4cca90f1cc..5bf13effe8 100644 --- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py @@ -101,7 +101,7 @@ "instrumentation": "opentelemetry-instrumentation-falcon==0.53b0.dev", }, { - "library": "fastapi ~= 0.58", + "library": "fastapi ~= 0.92", "instrumentation": "opentelemetry-instrumentation-fastapi==0.53b0.dev", }, { From 7ac54aef24325956e61816a791284559d75d7985 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Wed, 9 Apr 2025 09:05:42 -0500 Subject: [PATCH 29/37] generate --- instrumentation/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/README.md b/instrumentation/README.md index 4c89015b78..b648fe84eb 100644 --- a/instrumentation/README.md +++ b/instrumentation/README.md @@ -22,7 +22,7 @@ | [opentelemetry-instrumentation-django](./opentelemetry-instrumentation-django) | django >= 1.10 | Yes | development | [opentelemetry-instrumentation-elasticsearch](./opentelemetry-instrumentation-elasticsearch) | elasticsearch >= 6.0 | No | development | [opentelemetry-instrumentation-falcon](./opentelemetry-instrumentation-falcon) | falcon >= 1.4.1, < 5.0.0 | Yes | migration -| [opentelemetry-instrumentation-fastapi](./opentelemetry-instrumentation-fastapi) | fastapi ~= 0.58 | Yes | migration +| [opentelemetry-instrumentation-fastapi](./opentelemetry-instrumentation-fastapi) | fastapi ~= 0.92 | Yes | migration | [opentelemetry-instrumentation-flask](./opentelemetry-instrumentation-flask) | flask >= 1.0 | Yes | migration | [opentelemetry-instrumentation-grpc](./opentelemetry-instrumentation-grpc) | grpcio >= 1.42.0 | No | development | [opentelemetry-instrumentation-httpx](./opentelemetry-instrumentation-httpx) | httpx >= 0.18.0 | No | migration From 5fa64a55fcc2fd3d47df76a6829ea49d6cd18771 Mon Sep 17 00:00:00 2001 From: Alexander Dorn Date: Wed, 14 May 2025 13:47:53 +0200 Subject: [PATCH 30/37] stop passing on user-supplied error handler This prevents potential side effects, such as logging, to be executed more than once per request handler exception. --- .../src/opentelemetry/instrumentation/fastapi/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py index 8c663a7e52..d96c91e924 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py @@ -322,8 +322,6 @@ def build_middleware_stack(self: Starlette) -> ASGIApp: ): # usually true outer_server_error_middleware = ServerErrorMiddleware( app=otel_middleware, - handler=inner_server_error_middleware.handler, - debug=inner_server_error_middleware.debug, ) else: # Something else seems to have patched things, or maybe Starlette changed. From 17e08fa7edf3905b9b05a570d8b310f2f8e62683 Mon Sep 17 00:00:00 2001 From: emdneto <9735060+emdneto@users.noreply.github.com> Date: Wed, 14 May 2025 11:16:36 -0300 Subject: [PATCH 31/37] fix ci Signed-off-by: emdneto <9735060+emdneto@users.noreply.github.com> --- .../instrumentation/bootstrap_gen.py | 2 +- pyproject.toml | 2 + uv.lock | 72 +++++++++---------- 3 files changed, 39 insertions(+), 37 deletions(-) diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py index cd96d7188c..e6c9c86e0b 100644 --- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py @@ -102,7 +102,7 @@ }, { "library": "fastapi ~= 0.92", - "instrumentation": "opentelemetry-instrumentation-fastapi==0.53b0.dev", + "instrumentation": "opentelemetry-instrumentation-fastapi==0.55b0.dev", }, { "library": "flask >= 1.0", diff --git a/pyproject.toml b/pyproject.toml index f2e9cdf98d..0ac58006ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,6 +66,7 @@ dependencies = [ "opentelemetry-instrumentation-openai-v2[instruments]", ] + # https://docs.astral.sh/uv/reference/settings/ [tool.uv] package = false # https://docs.astral.sh/uv/reference/settings/#package @@ -147,6 +148,7 @@ members = [ # TODO: remove after python 3.8 is dropped exclude = [ "instrumentation-genai/opentelemetry-instrumentation-google-genai", + "instrumentation/opentelemetry-instrumentation-starlette", ] [tool.ruff] diff --git a/uv.lock b/uv.lock index bf2d9f8e73..49c61c854b 100644 --- a/uv.lock +++ b/uv.lock @@ -58,7 +58,6 @@ members = [ "opentelemetry-instrumentation-requests", "opentelemetry-instrumentation-sqlalchemy", "opentelemetry-instrumentation-sqlite3", - "opentelemetry-instrumentation-starlette", "opentelemetry-instrumentation-system-metrics", "opentelemetry-instrumentation-threading", "opentelemetry-instrumentation-tornado", @@ -1330,15 +1329,17 @@ wheels = [ [[package]] name = "fastapi" -version = "0.64.0" +version = "0.115.12" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, - { name = "starlette" }, + { name = "starlette", version = "0.44.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "starlette", version = "0.46.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/fe/064bdb5598ad9dfe942b58884296bcd7b81d1e13e5af48fd328e38b2de97/fastapi-0.64.0.tar.gz", hash = "sha256:9bbd7b7b9291bbc3bbd72cbc82f5d456369802dab0d142a85350b06c5c7e6379", size = 5481759 } +sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/cc/929a628d250bc81ff39aa2a3f8d66b3ea2e07c3c93135e9b0303058ccd59/fastapi-0.64.0-py3-none-any.whl", hash = "sha256:62a438d0ff466640939414436339ce4e303964f3f823b7288e300baa869162e3", size = 50849 }, + { url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164 }, ] [[package]] @@ -3279,7 +3280,7 @@ instruments = [ [package.metadata] requires-dist = [ - { name = "fastapi", marker = "extra == 'instruments'", specifier = "~=0.58" }, + { name = "fastapi", marker = "extra == 'instruments'", specifier = "~=0.92" }, { name = "opentelemetry-api", git = "https://github.com/open-telemetry/opentelemetry-python?subdirectory=opentelemetry-api&branch=main" }, { name = "opentelemetry-instrumentation", editable = "opentelemetry-instrumentation" }, { name = "opentelemetry-instrumentation-asgi", editable = "instrumentation/opentelemetry-instrumentation-asgi" }, @@ -3817,33 +3818,6 @@ requires-dist = [ ] provides-extras = ["instruments"] -[[package]] -name = "opentelemetry-instrumentation-starlette" -source = { editable = "instrumentation/opentelemetry-instrumentation-starlette" } -dependencies = [ - { name = "opentelemetry-api" }, - { name = "opentelemetry-instrumentation" }, - { name = "opentelemetry-instrumentation-asgi" }, - { name = "opentelemetry-semantic-conventions" }, - { name = "opentelemetry-util-http" }, -] - -[package.optional-dependencies] -instruments = [ - { name = "starlette" }, -] - -[package.metadata] -requires-dist = [ - { name = "opentelemetry-api", git = "https://github.com/open-telemetry/opentelemetry-python?subdirectory=opentelemetry-api&branch=main" }, - { name = "opentelemetry-instrumentation", editable = "opentelemetry-instrumentation" }, - { name = "opentelemetry-instrumentation-asgi", editable = "instrumentation/opentelemetry-instrumentation-asgi" }, - { name = "opentelemetry-semantic-conventions", git = "https://github.com/open-telemetry/opentelemetry-python?subdirectory=opentelemetry-semantic-conventions&branch=main" }, - { name = "opentelemetry-util-http", editable = "util/opentelemetry-util-http" }, - { name = "starlette", marker = "extra == 'instruments'", specifier = ">=0.13,<0.15" }, -] -provides-extras = ["instruments"] - [[package]] name = "opentelemetry-instrumentation-system-metrics" source = { editable = "instrumentation/opentelemetry-instrumentation-system-metrics" } @@ -5395,11 +5369,37 @@ wheels = [ [[package]] name = "starlette" -version = "0.13.6" +version = "0.44.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/da/ddd95317478f8f4be8e5445c0c5acf928b1192953834a520afdacf13ddea/starlette-0.13.6.tar.gz", hash = "sha256:ebe8ee08d9be96a3c9f31b2cb2a24dbdf845247b745664bd8a3f9bd0c977fdbc", size = 49216 } +resolution-markers = [ + "python_full_version < '3.9'", +] +dependencies = [ + { name = "anyio", version = "4.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "typing-extensions", marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/b4/910f693584958b687b8f9c628f8217cfef19a42b64d2de7840814937365c/starlette-0.44.0.tar.gz", hash = "sha256:e35166950a3ccccc701962fe0711db0bc14f2ecd37c6f9fe5e3eae0cbaea8715", size = 2575579 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/c5/7ae467eeddb57260c8ce17a3a09f9f5edba35820fc022d7c55b7decd5d3a/starlette-0.44.0-py3-none-any.whl", hash = "sha256:19edeb75844c16dcd4f9dd72f22f9108c1539f3fc9c4c88885654fef64f85aea", size = 73412 }, +] + +[[package]] +name = "starlette" +version = "0.46.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version >= '3.11' and python_full_version < '3.13'", + "python_full_version == '3.10.*'", + "python_full_version == '3.9.*'", +] +dependencies = [ + { name = "anyio", version = "4.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "typing-extensions", marker = "python_full_version == '3.9.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/a4/c9e228d7d47044ce4c83ba002f28ff479e542455f0499198a3f77c94f564/starlette-0.13.6-py3-none-any.whl", hash = "sha256:bd2ffe5e37fb75d014728511f8e68ebf2c80b0fa3d04ca1479f4dc752ae31ac9", size = 59998 }, + { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037 }, ] [[package]] From 8fcf4ec2180fb4410b9f5d863601be755d2e3235 Mon Sep 17 00:00:00 2001 From: emdneto <9735060+emdneto@users.noreply.github.com> Date: Wed, 14 May 2025 11:21:59 -0300 Subject: [PATCH 32/37] fix ruff Signed-off-by: emdneto <9735060+emdneto@users.noreply.github.com> --- .../tests/test_fastapi_instrumentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index 08e71005c0..3d4adb2e1c 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -14,9 +14,9 @@ # pylint: disable=too-many-lines -from importlib.metadata import PackageNotFoundError import unittest from contextlib import ExitStack +from importlib.metadata import PackageNotFoundError from timeit import default_timer from unittest.mock import Mock, call, patch From fcc62f4c04ba372c61047894bf84b5dc00849f08 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Wed, 14 May 2025 09:43:25 -0500 Subject: [PATCH 33/37] remove unused funcs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Emídio Neto <9735060+emdneto@users.noreply.github.com> --- .../tests/test_fastapi_instrumentation.py | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index 3d4adb2e1c..3be7815418 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -1072,26 +1072,6 @@ def client_response_hook(send_span, scope, message): ) -def mock_version_with_fastapi(*args, **kwargs): - req_name = args[0] - if req_name == "fastapi": - # TODO: Value now matters - return "0.109" - raise PackageNotFoundError() - - -def mock_version_with_old_fastapi(*args, **kwargs): - req_name = args[0] - if req_name == "fastapi": - # TODO: Value now matters - return "0.92" - raise PackageNotFoundError() - - -def mock_version_without_fastapi(*args, **kwargs): - raise PackageNotFoundError() - - class TestAutoInstrumentation(TestBaseAutoFastAPI): """Test the auto-instrumented variant From 5a84bcc5309a36d050d95a74fa12bebd827eaf72 Mon Sep 17 00:00:00 2001 From: emdneto <9735060+emdneto@users.noreply.github.com> Date: Wed, 14 May 2025 11:55:52 -0300 Subject: [PATCH 34/37] fix lint,ruff Signed-off-by: emdneto <9735060+emdneto@users.noreply.github.com> --- .../tests/test_fastapi_instrumentation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index 3be7815418..d197088a4f 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -16,7 +16,6 @@ import unittest from contextlib import ExitStack -from importlib.metadata import PackageNotFoundError from timeit import default_timer from unittest.mock import Mock, call, patch From 2a150fd388463dbecdd36037a6be899c6754b423 Mon Sep 17 00:00:00 2001 From: emdneto <9735060+emdneto@users.noreply.github.com> Date: Fri, 16 May 2025 10:23:52 -0300 Subject: [PATCH 35/37] fix changelog Signed-off-by: emdneto <9735060+emdneto@users.noreply.github.com> --- CHANGELOG.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cf8627438..b5275eb9ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Fixed + +- `opentelemetry-instrumentation-fastapi`: fix wrapping of middlewares + ([#3012](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3012)) + ### Breaking changes - `opentelemetry-instrumentation-botocore` Use `cloud.region` instead of `aws.region` span attribute as per semantic conventions. ([#3474](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3474)) - +- `opentelemetry-instrumentation-fastapi`: Drop support for FastAPI versions earlier than `0.98` + ([#3012](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3012)) ## Version 1.33.0/0.54b0 (2025-05-09) @@ -100,8 +106,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `opentelemetry-instrumentation-redis` Add missing entry in doc string for `def _instrument` ([#3247](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3247)) -- `opentelemetry-instrumentation-fastapi`: fix wrapping of middlewares - ([#3012](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3012)) - `opentelemetry-instrumentation-botocore` sns-extension: Change destination name attribute to match topic ARN and redact phone number from attributes ([#3249](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3249)) From 808d42ab22a3fc5f1ec8ffe1ad89a8eec401cbcd Mon Sep 17 00:00:00 2001 From: emdneto <9735060+emdneto@users.noreply.github.com> Date: Fri, 16 May 2025 10:30:15 -0300 Subject: [PATCH 36/37] add changelog note Signed-off-by: emdneto <9735060+emdneto@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5275eb9ee..6ecdc6d396 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `opentelemetry-instrumentation-botocore` Use `cloud.region` instead of `aws.region` span attribute as per semantic conventions. ([#3474](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3474)) -- `opentelemetry-instrumentation-fastapi`: Drop support for FastAPI versions earlier than `0.98` +- `opentelemetry-instrumentation-fastapi`: Drop support for FastAPI versions earlier than `0.92` ([#3012](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3012)) ## Version 1.33.0/0.54b0 (2025-05-09) From 60e1706d3495dc19f0497275fc5be29a933f1ed1 Mon Sep 17 00:00:00 2001 From: emdneto <9735060+emdneto@users.noreply.github.com> Date: Mon, 19 May 2025 12:27:36 -0300 Subject: [PATCH 37/37] fix conflicts with main Signed-off-by: emdneto <9735060+emdneto@users.noreply.github.com> --- .../src/opentelemetry/instrumentation/fastapi/__init__.py | 1 - .../tests/test_fastapi_instrumentation.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py index c939cc1e8b..8ba83985c6 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py @@ -210,7 +210,6 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.metrics import MeterProvider, get_meter from opentelemetry.semconv.attributes.http_attributes import HTTP_ROUTE -from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.trace import TracerProvider, get_tracer from opentelemetry.util.http import ( get_excluded_urls, diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index 26e12839c2..408232842e 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -263,10 +263,10 @@ def test_fastapi_unhandled_exception(self): self.assertEqual(len(spans), 3) span = spans[0] assert span.name == "GET /error http send" - assert span.attributes[SpanAttributes.HTTP_STATUS_CODE] == 500 + assert span.attributes[HTTP_STATUS_CODE] == 500 span = spans[2] assert span.name == "GET /error" - assert span.attributes[SpanAttributes.HTTP_TARGET] == "/error" + assert span.attributes[HTTP_TARGET] == "/error" def test_sub_app_fastapi_call(self): """