From 2b33c49fbfae49b1d5705119e46ca83e2a59496d Mon Sep 17 00:00:00 2001 From: krnr Date: Sat, 25 Oct 2025 08:20:13 +0300 Subject: [PATCH 1/6] Use canonical name for aiohttp request span name --- CHANGELOG.md | 2 + .../aiohttp_server/__init__.py | 4 ++ .../tests/test_aiohttp_server_integration.py | 58 ++++++++++++++++++- 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e206925782..5dd3481f55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +- `opentelemetry-instrumentation-aiohttp-server`: Use `canonical` attribute of the `Resource` as a span name. + ### Added - `opentelemetry-instrumentation-aiohttp-client`: add support for url exclusions via `OTEL_PYTHON_EXCLUDED_URLS` / `OTEL_PYTHON_AIOHTTP_CLIENT_EXCLUDED_URLS` diff --git a/instrumentation/opentelemetry-instrumentation-aiohttp-server/src/opentelemetry/instrumentation/aiohttp_server/__init__.py b/instrumentation/opentelemetry-instrumentation-aiohttp-server/src/opentelemetry/instrumentation/aiohttp_server/__init__.py index 30f967d39f..58742ce7f7 100644 --- a/instrumentation/opentelemetry-instrumentation-aiohttp-server/src/opentelemetry/instrumentation/aiohttp_server/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-aiohttp-server/src/opentelemetry/instrumentation/aiohttp_server/__init__.py @@ -245,6 +245,10 @@ def get_default_span_name(request: web.Request) -> str: The span name. """ span_name = request.path.strip() or f"HTTP {request.method}" + if request.match_info and request.match_info.route.resource: + resource = request.match_info.route.resource + if resource.canonical: + span_name = resource.canonical return span_name diff --git a/instrumentation/opentelemetry-instrumentation-aiohttp-server/tests/test_aiohttp_server_integration.py b/instrumentation/opentelemetry-instrumentation-aiohttp-server/tests/test_aiohttp_server_integration.py index 1528edf012..1ce728c117 100644 --- a/instrumentation/opentelemetry-instrumentation-aiohttp-server/tests/test_aiohttp_server_integration.py +++ b/instrumentation/opentelemetry-instrumentation-aiohttp-server/tests/test_aiohttp_server_integration.py @@ -29,6 +29,7 @@ from opentelemetry.semconv._incubating.attributes.http_attributes import ( HTTP_METHOD, HTTP_STATUS_CODE, + HTTP_TARGET, HTTP_URL, ) from opentelemetry.test.globals_test import ( @@ -106,7 +107,15 @@ async def fixture_server_fixture(tracer, aiohttp_server, suppress): AioHttpServerInstrumentor().instrument() app = aiohttp.web.Application() - app.add_routes([aiohttp.web.get("/test-path", default_handler)]) + app.add_routes( + [ + aiohttp.web.get("/test-path", default_handler), + aiohttp.web.get("/test-path/{url_param}", default_handler), + aiohttp.web.get( + "/object/{object_id}/action/{another_param}", default_handler + ), + ] + ) if suppress: with suppress_http_instrumentation(): server = await aiohttp_server(app) @@ -170,6 +179,53 @@ async def test_status_code_instrumentation( ) +@pytest.mark.asyncio +@pytest.mark.parametrize( + "url, example_paths", + [ + ( + "/test-path/{url_param}", + ( + "/test-path/foo", + "/test-path/bar", + ), + ), + ( + "/object/{object_id}/action/{another_param}", + ( + "/object/1/action/bar", + "/object/234/action/baz", + ), + ), + ], +) +async def test_url_params_instrumentation( + tracer, + server_fixture, + aiohttp_client, + url, + example_paths, +): + _, memory_exporter = tracer + server, _ = server_fixture + + assert len(memory_exporter.get_finished_spans()) == 0 + + client = await aiohttp_client(server) + for path in example_paths: + await client.get(path) + + assert len(memory_exporter.get_finished_spans()) == 2 + + for request_path, span in zip( + example_paths, memory_exporter.get_finished_spans() + ): + assert url == span.name + assert request_path == span.attributes[HTTP_TARGET] + full_url = f"http://{server.host}:{server.port}{request_path}" + assert full_url == span.attributes[HTTP_URL] + + @pytest.mark.asyncio @pytest.mark.parametrize("suppress", [True]) async def test_suppress_instrumentation( From 9d4b5a133f5d58ce2bb705daed6f0babece100f6 Mon Sep 17 00:00:00 2001 From: krnr Date: Wed, 5 Nov 2025 22:46:16 +0300 Subject: [PATCH 2/6] semconv for http server span names --- .../instrumentation/aiohttp_server/__init__.py | 6 +++--- .../tests/test_aiohttp_server_integration.py | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-aiohttp-server/src/opentelemetry/instrumentation/aiohttp_server/__init__.py b/instrumentation/opentelemetry-instrumentation-aiohttp-server/src/opentelemetry/instrumentation/aiohttp_server/__init__.py index 58742ce7f7..524d03a0fb 100644 --- a/instrumentation/opentelemetry-instrumentation-aiohttp-server/src/opentelemetry/instrumentation/aiohttp_server/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-aiohttp-server/src/opentelemetry/instrumentation/aiohttp_server/__init__.py @@ -244,12 +244,12 @@ def get_default_span_name(request: web.Request) -> str: Returns: The span name. """ - span_name = request.path.strip() or f"HTTP {request.method}" + path = request.path.strip() if request.match_info and request.match_info.route.resource: resource = request.match_info.route.resource if resource.canonical: - span_name = resource.canonical - return span_name + path = resource.canonical + return f"{request.method} {path}" def _get_view_func(request: web.Request) -> str: diff --git a/instrumentation/opentelemetry-instrumentation-aiohttp-server/tests/test_aiohttp_server_integration.py b/instrumentation/opentelemetry-instrumentation-aiohttp-server/tests/test_aiohttp_server_integration.py index 1ce728c117..9660242e40 100644 --- a/instrumentation/opentelemetry-instrumentation-aiohttp-server/tests/test_aiohttp_server_integration.py +++ b/instrumentation/opentelemetry-instrumentation-aiohttp-server/tests/test_aiohttp_server_integration.py @@ -181,17 +181,17 @@ async def test_status_code_instrumentation( @pytest.mark.asyncio @pytest.mark.parametrize( - "url, example_paths", + "span_name, example_paths", [ ( - "/test-path/{url_param}", + "GET /test-path/{url_param}", ( "/test-path/foo", "/test-path/bar", ), ), ( - "/object/{object_id}/action/{another_param}", + "GET /object/{object_id}/action/{another_param}", ( "/object/1/action/bar", "/object/234/action/baz", @@ -203,7 +203,7 @@ async def test_url_params_instrumentation( tracer, server_fixture, aiohttp_client, - url, + span_name, example_paths, ): _, memory_exporter = tracer @@ -220,7 +220,7 @@ async def test_url_params_instrumentation( for request_path, span in zip( example_paths, memory_exporter.get_finished_spans() ): - assert url == span.name + assert span_name == span.name assert request_path == span.attributes[HTTP_TARGET] full_url = f"http://{server.host}:{server.port}{request_path}" assert full_url == span.attributes[HTTP_URL] From 44ae5411e577e4fa56792396bc96970669a3806b Mon Sep 17 00:00:00 2001 From: yuri <8479133+krnr@users.noreply.github.com> Date: Thu, 6 Nov 2025 08:26:17 +0300 Subject: [PATCH 3/6] Update CHANGELOG.md Co-authored-by: Tammy Baylis <96076570+tammy-baylis-swi@users.noreply.github.com> --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dd3481f55..11b59a5fa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - `opentelemetry-instrumentation-aiohttp-server`: Use `canonical` attribute of the `Resource` as a span name. + ([#3896](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3896)) ### Added From 1d5dfa354ace51eb2edb56d296cb7857ef7b6c6f Mon Sep 17 00:00:00 2001 From: krnr Date: Thu, 6 Nov 2025 08:50:57 +0300 Subject: [PATCH 4/6] try..except --- .../instrumentation/aiohttp_server/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-aiohttp-server/src/opentelemetry/instrumentation/aiohttp_server/__init__.py b/instrumentation/opentelemetry-instrumentation-aiohttp-server/src/opentelemetry/instrumentation/aiohttp_server/__init__.py index 524d03a0fb..0eb066d818 100644 --- a/instrumentation/opentelemetry-instrumentation-aiohttp-server/src/opentelemetry/instrumentation/aiohttp_server/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-aiohttp-server/src/opentelemetry/instrumentation/aiohttp_server/__init__.py @@ -245,10 +245,12 @@ def get_default_span_name(request: web.Request) -> str: The span name. """ path = request.path.strip() - if request.match_info and request.match_info.route.resource: + try: resource = request.match_info.route.resource - if resource.canonical: + if resource: path = resource.canonical + except AttributeError: + path = "unknown" return f"{request.method} {path}" From b304292cc7aeb55d185baa93c19fda6ea40ec6f5 Mon Sep 17 00:00:00 2001 From: krnr Date: Thu, 6 Nov 2025 09:02:28 +0300 Subject: [PATCH 5/6] pass exception --- .../instrumentation/aiohttp_server/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-aiohttp-server/src/opentelemetry/instrumentation/aiohttp_server/__init__.py b/instrumentation/opentelemetry-instrumentation-aiohttp-server/src/opentelemetry/instrumentation/aiohttp_server/__init__.py index 0eb066d818..aba9a83192 100644 --- a/instrumentation/opentelemetry-instrumentation-aiohttp-server/src/opentelemetry/instrumentation/aiohttp_server/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-aiohttp-server/src/opentelemetry/instrumentation/aiohttp_server/__init__.py @@ -238,11 +238,11 @@ def _parse_active_request_count_attrs(req_attrs): def get_default_span_name(request: web.Request) -> str: - """Default implementation for get_default_span_details + """Returns the span name. Args: request: the request object itself. Returns: - The span name. + The canonical name of a resource if possible or just request path. """ path = request.path.strip() try: @@ -250,7 +250,7 @@ def get_default_span_name(request: web.Request) -> str: if resource: path = resource.canonical except AttributeError: - path = "unknown" + pass return f"{request.method} {path}" From 3de47029bf5c436a7cdf98769c46c582ad77505f Mon Sep 17 00:00:00 2001 From: krnr Date: Sat, 8 Nov 2025 10:40:40 +0300 Subject: [PATCH 6/6] {method} only if 404 --- .../instrumentation/aiohttp_server/__init__.py | 14 ++++++++------ .../tests/test_aiohttp_server_integration.py | 7 +++++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-aiohttp-server/src/opentelemetry/instrumentation/aiohttp_server/__init__.py b/instrumentation/opentelemetry-instrumentation-aiohttp-server/src/opentelemetry/instrumentation/aiohttp_server/__init__.py index aba9a83192..d186b3af92 100644 --- a/instrumentation/opentelemetry-instrumentation-aiohttp-server/src/opentelemetry/instrumentation/aiohttp_server/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-aiohttp-server/src/opentelemetry/instrumentation/aiohttp_server/__init__.py @@ -244,14 +244,16 @@ def get_default_span_name(request: web.Request) -> str: Returns: The canonical name of a resource if possible or just request path. """ - path = request.path.strip() try: resource = request.match_info.route.resource - if resource: - path = resource.canonical - except AttributeError: - pass - return f"{request.method} {path}" + assert resource + path = resource.canonical + except (AttributeError, AssertionError): + path = "" + + if path: + return f"{request.method} {path}" + return f"{request.method}" def _get_view_func(request: web.Request) -> str: diff --git a/instrumentation/opentelemetry-instrumentation-aiohttp-server/tests/test_aiohttp_server_integration.py b/instrumentation/opentelemetry-instrumentation-aiohttp-server/tests/test_aiohttp_server_integration.py index 9660242e40..826a7bc74c 100644 --- a/instrumentation/opentelemetry-instrumentation-aiohttp-server/tests/test_aiohttp_server_integration.py +++ b/instrumentation/opentelemetry-instrumentation-aiohttp-server/tests/test_aiohttp_server_integration.py @@ -197,6 +197,13 @@ async def test_status_code_instrumentation( "/object/234/action/baz", ), ), + ( + "GET", + ( + "/i/dont/exist", + "/me-neither", + ), + ), ], ) async def test_url_params_instrumentation(