From b088bcb064908fe74442da7bc51a4a33000d3e99 Mon Sep 17 00:00:00 2001 From: Shubhendra Kushwaha Date: Sat, 10 May 2025 20:48:45 +0530 Subject: [PATCH 1/4] refactor(fastapi): migrate HTTP_ROUTE from SpanAttributes to new semantic conventions Replaced usage of `SpanAttributes.HTTP_ROUTE` with `opentelemetry.semconv.attributes.http_attributes.HTTP_ROUTE` as part of the migration away from `SpanAttributes`. Refs: #3475 --- .../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 1abff1adcb..79be238958 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py @@ -204,7 +204,7 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A from opentelemetry.instrumentation.fastapi.version import __version__ from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.metrics import get_meter -from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.semconv.attributes.http_attributes import HTTP_ROUTE from opentelemetry.trace import get_tracer from opentelemetry.util.http import ( get_excluded_urls, @@ -450,7 +450,7 @@ def _get_default_span_details(scope): if method == "_OTHER": method = "HTTP" if route: - attributes[SpanAttributes.HTTP_ROUTE] = route + attributes[HTTP_ROUTE] = route if method and route: # http span_name = f"{method} {route}" elif route: # websocket From f52eda7210296846e25df3b6c52c2678f59b643a Mon Sep 17 00:00:00 2001 From: Shubhendra Kushwaha Date: Sun, 11 May 2025 09:15:29 +0530 Subject: [PATCH 2/4] refactor(fastapi): refactor tests --- .../tests/test_fastapi_instrumentation.py | 159 ++++++++++-------- 1 file changed, 85 insertions(+), 74 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index 439cebf427..6d2a6a3207 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -55,7 +55,18 @@ NETWORK_PROTOCOL_VERSION, ) from opentelemetry.semconv.attributes.url_attributes import URL_SCHEME -from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.semconv._incubating.attributes.http_attributes import ( + HTTP_FLAVOR, + HTTP_HOST, + HTTP_METHOD, + HTTP_ROUTE, + HTTP_SCHEME, + HTTP_SERVER_NAME, + HTTP_STATUS_CODE, + HTTP_TARGET, + HTTP_URL, +) +from opentelemetry.semconv._incubating.attributes.net_attributes import NET_HOST_PORT from opentelemetry.test.globals_test import reset_trace_globals from opentelemetry.test.test_base import TestBase from opentelemetry.util._importlib_metadata import entry_points @@ -85,15 +96,15 @@ "http.server.active_requests": _server_active_requests_count_attrs_old, "http.server.duration": { *_server_duration_attrs_old, - SpanAttributes.HTTP_TARGET, + HTTP_TARGET, }, "http.server.response.size": { *_server_duration_attrs_old, - SpanAttributes.HTTP_TARGET, + HTTP_TARGET, }, "http.server.request.size": { *_server_duration_attrs_old, - SpanAttributes.HTTP_TARGET, + HTTP_TARGET, }, } @@ -245,8 +256,8 @@ def test_sub_app_fastapi_call(self): span for span in spans if ( - SpanAttributes.HTTP_URL in span.attributes - or SpanAttributes.HTTP_TARGET in span.attributes + HTTP_URL in span.attributes + or HTTP_TARGET in span.attributes ) ] @@ -256,11 +267,11 @@ def test_sub_app_fastapi_call(self): for span in spans_with_http_attributes: self.assertEqual( - "/sub/home", span.attributes[SpanAttributes.HTTP_TARGET] + "/sub/home", span.attributes[HTTP_TARGET] ) self.assertEqual( "https://testserver:443/sub/home", - span.attributes[SpanAttributes.HTTP_URL], + span.attributes[HTTP_URL], ) @@ -309,8 +320,8 @@ def test_sub_app_fastapi_call(self): span for span in spans if ( - SpanAttributes.HTTP_URL in span.attributes - or SpanAttributes.HTTP_TARGET in span.attributes + HTTP_URL in span.attributes + or HTTP_TARGET in span.attributes ) ] @@ -319,11 +330,11 @@ def test_sub_app_fastapi_call(self): for span in spans_with_http_attributes: self.assertEqual( - "/sub/home", span.attributes[SpanAttributes.HTTP_TARGET] + "/sub/home", span.attributes[HTTP_TARGET] ) self.assertEqual( "https://testserver:443/sub/home", - span.attributes[SpanAttributes.HTTP_URL], + span.attributes[HTTP_URL], ) @@ -382,12 +393,12 @@ def test_fastapi_route_attribute_added(self): for span in spans: self.assertIn("GET /user/{username}", span.name) self.assertEqual( - spans[-1].attributes[SpanAttributes.HTTP_ROUTE], "/user/{username}" + spans[-1].attributes[HTTP_ROUTE], "/user/{username}" ) # ensure that at least one attribute that is populated by # the asgi instrumentation is successfully feeding though. self.assertEqual( - spans[-1].attributes[SpanAttributes.HTTP_FLAVOR], "1.1" + spans[-1].attributes[HTTP_FLAVOR], "1.1" ) def test_fastapi_excluded_urls(self): @@ -511,21 +522,21 @@ def test_basic_metric_success(self): self._client.get("/foobar") duration = max(round((default_timer() - start) * 1000), 0) expected_duration_attributes = { - SpanAttributes.HTTP_METHOD: "GET", - SpanAttributes.HTTP_HOST: "testserver:443", - SpanAttributes.HTTP_SCHEME: "https", - SpanAttributes.HTTP_FLAVOR: "1.1", - SpanAttributes.HTTP_SERVER_NAME: "testserver", - SpanAttributes.NET_HOST_PORT: 443, - SpanAttributes.HTTP_STATUS_CODE: 200, - SpanAttributes.HTTP_TARGET: "/foobar", + HTTP_METHOD: "GET", + HTTP_HOST: "testserver:443", + HTTP_SCHEME: "https", + HTTP_FLAVOR: "1.1", + HTTP_SERVER_NAME: "testserver", + NET_HOST_PORT: 443, + HTTP_STATUS_CODE: 200, + HTTP_TARGET: "/foobar", } expected_requests_count_attributes = { - SpanAttributes.HTTP_METHOD: "GET", - SpanAttributes.HTTP_HOST: "testserver:443", - SpanAttributes.HTTP_SCHEME: "https", - SpanAttributes.HTTP_FLAVOR: "1.1", - SpanAttributes.HTTP_SERVER_NAME: "testserver", + HTTP_METHOD: "GET", + HTTP_HOST: "testserver:443", + HTTP_SCHEME: "https", + HTTP_FLAVOR: "1.1", + HTTP_SERVER_NAME: "testserver", } metrics_list = self.memory_metrics_reader.get_metrics_data() for metric in ( @@ -593,14 +604,14 @@ def test_basic_metric_success_both_semconv(self): duration = max(round((default_timer() - start) * 1000), 0) duration_s = max(default_timer() - start, 0) expected_duration_attributes_old = { - SpanAttributes.HTTP_METHOD: "GET", - SpanAttributes.HTTP_HOST: "testserver:443", - SpanAttributes.HTTP_SCHEME: "https", - SpanAttributes.HTTP_FLAVOR: "1.1", - SpanAttributes.HTTP_SERVER_NAME: "testserver", - SpanAttributes.NET_HOST_PORT: 443, - SpanAttributes.HTTP_STATUS_CODE: 200, - SpanAttributes.HTTP_TARGET: "/foobar", + HTTP_METHOD: "GET", + HTTP_HOST: "testserver:443", + HTTP_SCHEME: "https", + HTTP_FLAVOR: "1.1", + HTTP_SERVER_NAME: "testserver", + NET_HOST_PORT: 443, + HTTP_STATUS_CODE: 200, + HTTP_TARGET: "/foobar", } expected_duration_attributes_new = { HTTP_REQUEST_METHOD: "GET", @@ -610,11 +621,11 @@ def test_basic_metric_success_both_semconv(self): HTTP_ROUTE: "/foobar", } expected_requests_count_attributes = { - SpanAttributes.HTTP_METHOD: "GET", - SpanAttributes.HTTP_HOST: "testserver:443", - SpanAttributes.HTTP_SCHEME: "https", - SpanAttributes.HTTP_FLAVOR: "1.1", - SpanAttributes.HTTP_SERVER_NAME: "testserver", + HTTP_METHOD: "GET", + HTTP_HOST: "testserver:443", + HTTP_SCHEME: "https", + HTTP_FLAVOR: "1.1", + HTTP_SERVER_NAME: "testserver", HTTP_REQUEST_METHOD: "GET", URL_SCHEME: "https", } @@ -676,21 +687,21 @@ def test_basic_metric_nonstandard_http_method_success(self): self._client.request("NONSTANDARD", "/foobar") duration = max(round((default_timer() - start) * 1000), 0) expected_duration_attributes = { - SpanAttributes.HTTP_METHOD: "_OTHER", - SpanAttributes.HTTP_HOST: "testserver:443", - SpanAttributes.HTTP_SCHEME: "https", - SpanAttributes.HTTP_FLAVOR: "1.1", - SpanAttributes.HTTP_SERVER_NAME: "testserver", - SpanAttributes.NET_HOST_PORT: 443, - SpanAttributes.HTTP_STATUS_CODE: 405, - SpanAttributes.HTTP_TARGET: "/foobar", + HTTP_METHOD: "_OTHER", + HTTP_HOST: "testserver:443", + HTTP_SCHEME: "https", + HTTP_FLAVOR: "1.1", + HTTP_SERVER_NAME: "testserver", + NET_HOST_PORT: 443, + HTTP_STATUS_CODE: 405, + HTTP_TARGET: "/foobar", } expected_requests_count_attributes = { - SpanAttributes.HTTP_METHOD: "_OTHER", - SpanAttributes.HTTP_HOST: "testserver:443", - SpanAttributes.HTTP_SCHEME: "https", - SpanAttributes.HTTP_FLAVOR: "1.1", - SpanAttributes.HTTP_SERVER_NAME: "testserver", + HTTP_METHOD: "_OTHER", + HTTP_HOST: "testserver:443", + HTTP_SCHEME: "https", + HTTP_FLAVOR: "1.1", + HTTP_SERVER_NAME: "testserver", } metrics_list = self.memory_metrics_reader.get_metrics_data() for metric in ( @@ -758,14 +769,14 @@ def test_basic_metric_nonstandard_http_method_success_both_semconv(self): duration = max(round((default_timer() - start) * 1000), 0) duration_s = max(default_timer() - start, 0) expected_duration_attributes_old = { - SpanAttributes.HTTP_METHOD: "_OTHER", - SpanAttributes.HTTP_HOST: "testserver:443", - SpanAttributes.HTTP_SCHEME: "https", - SpanAttributes.HTTP_FLAVOR: "1.1", - SpanAttributes.HTTP_SERVER_NAME: "testserver", - SpanAttributes.NET_HOST_PORT: 443, - SpanAttributes.HTTP_STATUS_CODE: 405, - SpanAttributes.HTTP_TARGET: "/foobar", + HTTP_METHOD: "_OTHER", + HTTP_HOST: "testserver:443", + HTTP_SCHEME: "https", + HTTP_FLAVOR: "1.1", + HTTP_SERVER_NAME: "testserver", + NET_HOST_PORT: 443, + HTTP_STATUS_CODE: 405, + HTTP_TARGET: "/foobar", } expected_duration_attributes_new = { HTTP_REQUEST_METHOD: "_OTHER", @@ -775,11 +786,11 @@ def test_basic_metric_nonstandard_http_method_success_both_semconv(self): HTTP_ROUTE: "/foobar", } expected_requests_count_attributes = { - SpanAttributes.HTTP_METHOD: "_OTHER", - SpanAttributes.HTTP_HOST: "testserver:443", - SpanAttributes.HTTP_SCHEME: "https", - SpanAttributes.HTTP_FLAVOR: "1.1", - SpanAttributes.HTTP_SERVER_NAME: "testserver", + HTTP_METHOD: "_OTHER", + HTTP_HOST: "testserver:443", + HTTP_SCHEME: "https", + HTTP_FLAVOR: "1.1", + HTTP_SERVER_NAME: "testserver", HTTP_REQUEST_METHOD: "_OTHER", URL_SCHEME: "https", } @@ -1204,8 +1215,8 @@ def test_sub_app_fastapi_call(self): span for span in spans if ( - SpanAttributes.HTTP_URL in span.attributes - or SpanAttributes.HTTP_TARGET in span.attributes + HTTP_URL in span.attributes + or HTTP_TARGET in span.attributes ) ] @@ -1214,11 +1225,11 @@ def test_sub_app_fastapi_call(self): for span in spans_with_http_attributes: self.assertEqual( - "/sub/home", span.attributes[SpanAttributes.HTTP_TARGET] + "/sub/home", span.attributes[HTTP_TARGET] ) self.assertEqual( "https://testserver:443/sub/home", - span.attributes[SpanAttributes.HTTP_URL], + span.attributes[HTTP_URL], ) @@ -1297,8 +1308,8 @@ def test_sub_app_fastapi_call(self): span for span in spans if ( - SpanAttributes.HTTP_URL in span.attributes - or SpanAttributes.HTTP_TARGET in span.attributes + HTTP_URL in span.attributes + or HTTP_TARGET in span.attributes ) ] @@ -1307,11 +1318,11 @@ def test_sub_app_fastapi_call(self): for span in spans_with_http_attributes: self.assertEqual( - "/sub/home", span.attributes[SpanAttributes.HTTP_TARGET] + "/sub/home", span.attributes[HTTP_TARGET] ) self.assertEqual( "https://testserver:443/sub/home", - span.attributes[SpanAttributes.HTTP_URL], + span.attributes[HTTP_URL], ) From 9d13e80fdcf68c7b6424426d8704324c104bb2ac Mon Sep 17 00:00:00 2001 From: Shubhendra Kushwaha Date: Thu, 15 May 2025 23:36:03 +0530 Subject: [PATCH 3/4] refactor(fastapi): remove duplicated import --- .../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 6d2a6a3207..1f418fdd0c 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -59,7 +59,6 @@ HTTP_FLAVOR, HTTP_HOST, HTTP_METHOD, - HTTP_ROUTE, HTTP_SCHEME, HTTP_SERVER_NAME, HTTP_STATUS_CODE, From 5c2ad89bd5c28d1ad40911b3116e47a94ca41a5e Mon Sep 17 00:00:00 2001 From: Shubhendra Kushwaha Date: Fri, 16 May 2025 07:33:48 +0530 Subject: [PATCH 4/4] sort imports --- .../tests/test_fastapi_instrumentation.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py index 1f418fdd0c..eefd8bef30 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/tests/test_fastapi_instrumentation.py @@ -46,15 +46,6 @@ NumberDataPoint, ) from opentelemetry.sdk.resources import Resource -from opentelemetry.semconv.attributes.http_attributes import ( - HTTP_REQUEST_METHOD, - HTTP_RESPONSE_STATUS_CODE, - HTTP_ROUTE, -) -from opentelemetry.semconv.attributes.network_attributes import ( - NETWORK_PROTOCOL_VERSION, -) -from opentelemetry.semconv.attributes.url_attributes import URL_SCHEME from opentelemetry.semconv._incubating.attributes.http_attributes import ( HTTP_FLAVOR, HTTP_HOST, @@ -66,6 +57,15 @@ HTTP_URL, ) from opentelemetry.semconv._incubating.attributes.net_attributes import NET_HOST_PORT +from opentelemetry.semconv.attributes.http_attributes import ( + HTTP_REQUEST_METHOD, + HTTP_RESPONSE_STATUS_CODE, + HTTP_ROUTE, +) +from opentelemetry.semconv.attributes.network_attributes import ( + NETWORK_PROTOCOL_VERSION, +) +from opentelemetry.semconv.attributes.url_attributes import URL_SCHEME from opentelemetry.test.globals_test import reset_trace_globals from opentelemetry.test.test_base import TestBase from opentelemetry.util._importlib_metadata import entry_points