diff --git a/CHANGELOG.md b/CHANGELOG.md index e206925782..11b59a5fa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ 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 - `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..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 @@ -238,14 +238,22 @@ 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. """ - span_name = request.path.strip() or f"HTTP {request.method}" - return span_name + try: + resource = request.match_info.route.resource + 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 1528edf012..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 @@ -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,60 @@ async def test_status_code_instrumentation( ) +@pytest.mark.asyncio +@pytest.mark.parametrize( + "span_name, example_paths", + [ + ( + "GET /test-path/{url_param}", + ( + "/test-path/foo", + "/test-path/bar", + ), + ), + ( + "GET /object/{object_id}/action/{another_param}", + ( + "/object/1/action/bar", + "/object/234/action/baz", + ), + ), + ( + "GET", + ( + "/i/dont/exist", + "/me-neither", + ), + ), + ], +) +async def test_url_params_instrumentation( + tracer, + server_fixture, + aiohttp_client, + span_name, + 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 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] + + @pytest.mark.asyncio @pytest.mark.parametrize("suppress", [True]) async def test_suppress_instrumentation(