From 3506e1e4d4fe88d31d0b7768a17c1be0efa47af5 Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Wed, 4 Nov 2026 17:21:47 -0700 Subject: [PATCH 1/2] Add url_query to url attr build, if present --- solarwinds_apm/oboe/sampler.py | 10 +++++++++- tests/unit/test_oboe/test_sampler.py | 30 ++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/solarwinds_apm/oboe/sampler.py b/solarwinds_apm/oboe/sampler.py index 3a09d3337..d50eff40b 100644 --- a/solarwinds_apm/oboe/sampler.py +++ b/solarwinds_apm/oboe/sampler.py @@ -26,6 +26,7 @@ from opentelemetry.semconv.attributes.server_attributes import SERVER_ADDRESS from opentelemetry.semconv.attributes.url_attributes import ( URL_PATH, + URL_QUERY, URL_SCHEME, ) from opentelemetry.trace import Link, SpanKind, TraceState, get_current_span @@ -83,8 +84,15 @@ def http_span_metadata(kind: SpanKind, attributes: Attributes): SERVER_ADDRESS, attributes.get(NET_HOST_NAME, "localhost") ) ) - path = str(attributes.get(URL_PATH, attributes.get(HTTP_TARGET, ""))) + path = attributes.get(URL_PATH) + query = attributes.get(URL_QUERY) + if path is None and query is None: + path = attributes.get(HTTP_TARGET, "") + path = str(path or "") + query = str(query or "") url = f"{scheme}://{hostname}{path}" + if query: + url += f"?{query}" return { "http": True, "method": method, diff --git a/tests/unit/test_oboe/test_sampler.py b/tests/unit/test_oboe/test_sampler.py index 46a276338..9f0cd1fdd 100644 --- a/tests/unit/test_oboe/test_sampler.py +++ b/tests/unit/test_oboe/test_sampler.py @@ -29,6 +29,7 @@ from opentelemetry.semconv.attributes.server_attributes import SERVER_ADDRESS from opentelemetry.semconv.attributes.url_attributes import ( URL_PATH, + URL_QUERY, URL_SCHEME, ) from opentelemetry.trace import SpanKind @@ -145,6 +146,35 @@ def test_handles_http_server_spans_properly(self): "url": "https://solarwinds.com", } + def test_handles_http_server_span_with_query_only(self): + span = { + "kind": SpanKind.SERVER, + "attributes": { + HTTP_REQUEST_METHOD: "GET", + URL_SCHEME: "https", + SERVER_ADDRESS: "solarwinds.com", + URL_QUERY: "page=2", + }, + } + output = http_span_metadata(span["kind"], span["attributes"]) + assert output["path"] == "" + assert output["url"] == "https://solarwinds.com?page=2" + + def test_handles_http_server_span_with_path_and_query(self): + span = { + "kind": SpanKind.SERVER, + "attributes": { + HTTP_REQUEST_METHOD: "GET", + URL_SCHEME: "https", + SERVER_ADDRESS: "solarwinds.com", + URL_PATH: "/search", + URL_QUERY: "page=2", + }, + } + output = http_span_metadata(span["kind"], span["attributes"]) + assert output["path"] == "/search" + assert output["url"] == "https://solarwinds.com/search?page=2" + def test_handles_legacy_http_server_spans_properly(self): span = { "kind": SpanKind.SERVER, From ad8b0f1765ef226b5b48491d2821204e7fc285a5 Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Wed, 4 Nov 2026 17:22:33 -0700 Subject: [PATCH 2/2] Rm unused get_url_attrs --- pyproject.toml | 2 - solarwinds_apm/semconv/__init__.py | 0 solarwinds_apm/semconv/trace/__init__.py | 52 ------------------ tests/unit/test_semconv/__init__.py | 2 - tests/unit/test_semconv/test_trace.py | 70 ------------------------ 5 files changed, 126 deletions(-) delete mode 100644 solarwinds_apm/semconv/__init__.py delete mode 100644 solarwinds_apm/semconv/trace/__init__.py delete mode 100644 tests/unit/test_semconv/__init__.py delete mode 100644 tests/unit/test_semconv/test_trace.py diff --git a/pyproject.toml b/pyproject.toml index c034e915a..de9281f4f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,8 +75,6 @@ packages = [ "solarwinds_apm", "solarwinds_apm.api", "solarwinds_apm.oboe", - "solarwinds_apm.semconv", - "solarwinds_apm.semconv.trace", "solarwinds_apm.trace", ] diff --git a/solarwinds_apm/semconv/__init__.py b/solarwinds_apm/semconv/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/solarwinds_apm/semconv/trace/__init__.py b/solarwinds_apm/semconv/trace/__init__.py deleted file mode 100644 index 1bbf8ad8b..000000000 --- a/solarwinds_apm/semconv/trace/__init__.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright SolarWinds Worldwide, LLC. -# SPDX-License-Identifier: Apache-2.0 - -"""Helpers to handle trace from upstream instrumentors conforming to new or old semantic conventions.""" - -from __future__ import annotations - -from typing import Any - -from opentelemetry.semconv.trace import SpanAttributes -from opentelemetry.util.types import Attributes - - -def get_url_attrs( - attributes: Attributes | None = None, -) -> tuple[Any, Any, Any, Any]: - """ - Extract URL components from span attributes with fallback to old semantic conventions. - - Returns URL scheme, host, target, and port from span attributes. - Order of precedence is new semconv > old semconv > none. - - Parameters: - attributes (Attributes | None): The span attributes to extract URL components from. - - Returns: - tuple[Any, Any, Any, Any]: A tuple containing (scheme, host, port, target). - """ - # Upstream OTel instrumentation libraries are individually updating - # to implement support of HTTP semconv opt-in, so APM Python checks both - # https://github.com/open-telemetry/opentelemetry-python-contrib/issues/936 - - scheme = attributes.get(SpanAttributes.URL_SCHEME) - if not scheme: - scheme = attributes.get(SpanAttributes.HTTP_SCHEME) - - host = attributes.get(SpanAttributes.SERVER_ADDRESS) - if not host: - host = attributes.get(SpanAttributes.NET_HOST_NAME) - - port = attributes.get(SpanAttributes.SERVER_PORT) - if not port: - port = attributes.get(SpanAttributes.NET_HOST_PORT) - - # If new, it could be either URL_PATH or URL_QUERY - target = attributes.get(SpanAttributes.URL_PATH) - if not target: - target = attributes.get(SpanAttributes.URL_QUERY) - if not target: - target = attributes.get(SpanAttributes.HTTP_TARGET) - - return scheme, host, port, target diff --git a/tests/unit/test_semconv/__init__.py b/tests/unit/test_semconv/__init__.py deleted file mode 100644 index 0ce0c53ba..000000000 --- a/tests/unit/test_semconv/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# Copyright SolarWinds Worldwide, LLC. -# SPDX-License-Identifier: Apache-2.0 diff --git a/tests/unit/test_semconv/test_trace.py b/tests/unit/test_semconv/test_trace.py deleted file mode 100644 index 7cd724e81..000000000 --- a/tests/unit/test_semconv/test_trace.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright SolarWinds Worldwide, LLC. -# SPDX-License-Identifier: Apache-2.0 - -from solarwinds_apm.semconv.trace import get_url_attrs - - -class TestSemconvTrace: - def test_get_url_attrs__new_path(self): - attributes = { - "url.scheme": "foo", - "server.address": "bar", - "server.port": "baz", - "url.path": "/qux", - } - scheme, host, port, target = get_url_attrs(attributes) - assert scheme == "foo" - assert host == "bar" - assert port == "baz" - assert target == "/qux" - - def test_get_url_attrs__new_query(self): - attributes = { - "url.scheme": "foo", - "server.address": "bar", - "server.port": "baz", - "url.query": "/qux", - } - scheme, host, port, target = get_url_attrs(attributes) - assert scheme == "foo" - assert host == "bar" - assert port == "baz" - assert target == "/qux" - - def test_get_url_attrs__old(self): - attributes = { - "http.scheme": "foo", - "net.host.name": "bar", - "net.host.port": "baz", - "http.target": "/qux", - } - scheme, host, port, target = get_url_attrs(attributes) - assert scheme == "foo" - assert host == "bar" - assert port == "baz" - assert target == "/qux" - - def test_get_url_attrs__neither(self): - attributes = {} - scheme, host, port, target = get_url_attrs(attributes) - assert scheme is None - assert host is None - assert port is None - assert target is None - - def test_get_url_attrs__prefer_new(self): - attributes = { - "url.scheme": "foo", - "server.address": "bar", - "server.port": "baz", - "url.path": "/qux", - "http.scheme": "OLD", - "net.host.name": "OLD", - "net.host.port": "OLD", - "http.target": "/OLD", - } - scheme, host, port, target = get_url_attrs(attributes) - assert scheme == "foo" - assert host == "bar" - assert port == "baz" - assert target == "/qux"