diff --git a/manifests/dotnet.yml b/manifests/dotnet.yml index f50d4c240cb..47cda07ace6 100644 --- a/manifests/dotnet.yml +++ b/manifests/dotnet.yml @@ -772,6 +772,7 @@ manifest: tests/integrations/test_inferred_proxy.py::Test_AWS_API_Gateway_Inferred_Span_Creation_With_Error: v3.15.0 tests/integrations/test_inferred_proxy.py::Test_AWS_API_Gateway_Inferred_Span_Creation_v2: missing_feature tests/integrations/test_mongo.py::Test_Mongo: missing_feature (Endpoint is not implemented on weblog) + tests/integrations/test_otel_db_semantics.py::Test_PostgresOtelSemantics: missing_feature (DB OTel semantics not implemented; DD_TRACE_OTEL_SEMANTICS_ENABLED covers HTTP only) tests/integrations/test_otel_drop_in.py::Test_Otel_Drop_In: missing_feature tests/integrations/test_service_overrides.py::Test_SqlServiceNameSource: v3.40.0 tests/integrations/test_sql.py::Test_Sql: missing_feature (Endpoint is not implemented on weblog) diff --git a/manifests/golang.yml b/manifests/golang.yml index e6e5c7ee55b..a58530b3e4f 100644 --- a/manifests/golang.yml +++ b/manifests/golang.yml @@ -1051,6 +1051,7 @@ manifest: net-http-orchestrion: v1.72.1 tests/integrations/test_inferred_proxy.py::Test_AWS_API_Gateway_Inferred_Span_Creation_v2: missing_feature tests/integrations/test_mongo.py::Test_Mongo: missing_feature (Endpoint is not implemented on weblog) + tests/integrations/test_otel_db_semantics.py::Test_PostgresOtelSemantics: missing_feature (DB OTel semantics not implemented; DD_TRACE_OTEL_SEMANTICS_ENABLED covers HTTP only) tests/integrations/test_otel_drop_in.py::Test_Otel_Drop_In: missing_feature tests/integrations/test_service_overrides.py::Test_SqlServiceNameSource: missing_feature tests/integrations/test_sql.py::Test_Sql: missing_feature (Endpoint is not implemented on weblog) diff --git a/manifests/java.yml b/manifests/java.yml index dd5467c3b7c..3990cddfdeb 100644 --- a/manifests/java.yml +++ b/manifests/java.yml @@ -3645,6 +3645,7 @@ manifest: "*": v1.61.0-SNAPSHOT spring-boot-3-native: irrelevant (GraalVM. Tracing support only) tests/integrations/test_mongo.py::Test_Mongo: bug (APMAPI-729) + tests/integrations/test_otel_db_semantics.py::Test_PostgresOtelSemantics: missing_feature (DB OTel semantics not implemented; DD_TRACE_OTEL_SEMANTICS_ENABLED covers HTTP only) tests/integrations/test_otel_drop_in.py::Test_Otel_Drop_In: - weblog_declaration: "*": missing_feature diff --git a/manifests/php.yml b/manifests/php.yml index 1071aa96a66..e3d8f600df3 100644 --- a/manifests/php.yml +++ b/manifests/php.yml @@ -697,6 +697,7 @@ manifest: tests/integrations/test_mongo.py::Test_Mongo: - declaration: missing_feature (mongodb PHP extension not available in apache-mod containers) excluded_weblog: [php-fpm-7.0, php-fpm-7.1, php-fpm-7.2, php-fpm-7.3, php-fpm-7.4, php-fpm-8.0, php-fpm-8.1, php-fpm-8.2, php-fpm-8.5] + tests/integrations/test_otel_db_semantics.py::Test_PostgresOtelSemantics: missing_feature (DB OTel semantics not implemented; DD_TRACE_OTEL_SEMANTICS_ENABLED covers HTTP only) tests/integrations/test_otel_drop_in.py::Test_Otel_Drop_In: - declaration: missing_feature (OTel SDK requires PHP >= 8.1) excluded_weblog: [apache-mod-8.1, apache-mod-8.1-zts, apache-mod-8.2, apache-mod-8.2-zts, php-fpm-8.1, php-fpm-8.2, php-fpm-8.5] diff --git a/manifests/python.yml b/manifests/python.yml index e4db8bce7ac..03c451737e4 100644 --- a/manifests/python.yml +++ b/manifests/python.yml @@ -1752,6 +1752,7 @@ manifest: uwsgi-poc: v4.2.0 uds-flask: v4.2.0 tests/integrations/test_mongo.py::Test_Mongo: missing_feature (Endpoint is not implemented on weblog) + tests/integrations/test_otel_db_semantics.py::Test_PostgresOtelSemantics: missing_feature (DB OTel semantics not implemented; DD_TRACE_OTEL_SEMANTICS_ENABLED covers HTTP only) tests/integrations/test_otel_drop_in.py::Test_Otel_Drop_In: missing_feature tests/integrations/test_service_overrides.py::Test_SqlServiceNameSource: irrelevant (Only implemented for Java) tests/integrations/test_sql.py::Test_Sql: missing_feature (Endpoint is not implemented on weblog) diff --git a/manifests/ruby.yml b/manifests/ruby.yml index de6a303e1f1..33d0a2e7e2a 100644 --- a/manifests/ruby.yml +++ b/manifests/ruby.yml @@ -2042,6 +2042,7 @@ manifest: rails72: v2.33.0 # TODO: a lower version might be supported rails52: v2.33.0 # TODO: a lower version might be supported tests/integrations/test_mongo.py::Test_Mongo: missing_feature (Endpoint is not implemented on weblog) + tests/integrations/test_otel_db_semantics.py::Test_PostgresOtelSemantics: missing_feature (DB OTel semantics not implemented; DD_TRACE_OTEL_SEMANTICS_ENABLED covers HTTP only) tests/integrations/test_otel_drop_in.py::Test_Otel_Drop_In: missing_feature tests/integrations/test_service_overrides.py::Test_SqlServiceNameSource: - weblog_declaration: diff --git a/manifests/rust.yml b/manifests/rust.yml index b6ab8ddc88c..05f14e1ea23 100644 --- a/manifests/rust.yml +++ b/manifests/rust.yml @@ -52,6 +52,7 @@ manifest: tests/integrations/test_dsm.py::Test_DsmKafka::test_dsm_kafka_without_cluster_id: irrelevant tests/integrations/test_dsm.py::Test_DsmRabbitmq::test_dsm_rabbitmq_dotnet_legacy: irrelevant (legacy dotnet behavior) tests/integrations/test_mongo.py::Test_Mongo: missing_feature (Endpoint is not implemented on weblog) + tests/integrations/test_otel_db_semantics.py::Test_PostgresOtelSemantics: missing_feature (DB OTel semantics not implemented; DD_TRACE_OTEL_SEMANTICS_ENABLED covers HTTP only) tests/integrations/test_service_overrides.py::Test_SqlServiceNameSource: irrelevant (Only implemented for Java) tests/integrations/test_sql.py::Test_Sql: missing_feature (Endpoint is not implemented on weblog) tests/otel/test_tracing_otlp.py::Test_Otel_Tracing_OTLP: missing_feature diff --git a/tests/integrations/test_otel_db_semantics.py b/tests/integrations/test_otel_db_semantics.py new file mode 100644 index 00000000000..7c20f2f5637 --- /dev/null +++ b/tests/integrations/test_otel_db_semantics.py @@ -0,0 +1,101 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the the Apache License Version 2.0. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2026 Datadog, Inc. + +"""Validate that database (postgres) SQL spans honor the OpenTelemetry database semantic +conventions when ``DD_TRACE_OTEL_SEMANTICS_ENABLED=true`` (the OTEL_SEMANTICS_DB scenario). + +This is the database counterpart to tests/test_otel_http_semantics.py. When the flag is on, SQL +spans should emit the OpenTelemetry DB attribute names instead of the Datadog ones (the Datadog +names are replaced, not added alongside). + +NOTE: no tracer implements DB OTel semantics yet — the flag currently covers HTTP only — so these +tests are gated ``missing_feature`` everywhere. They encode the target contract and activate +per-tracer as database support lands. + +Spec: https://opentelemetry.io/docs/specs/semconv/db/database-spans/ +""" + +from utils import context, features, scenarios + +from .utils import BaseDbIntegrationsTestClass + + +@features.semantic_core_validations +@scenarios.otel_semantics_db +class Test_PostgresOtelSemantics(BaseDbIntegrationsTestClass): + """Postgres SQL spans emit OpenTelemetry database semantic-convention attribute names.""" + + db_service = "postgresql" + + def _setup_queries(self): + # the inherited _setup issues the /db queries once (idempotent, shared across the class) + self._setup() + + # one setup per test method, all aliased to the shared query setup (mirrors BaseDbIntegrationsTestClass) + setup_db_system_name = _setup_queries + setup_db_namespace = _setup_queries + setup_db_operation_name = _setup_queries + setup_db_query_text = _setup_queries + setup_db_collection_name = _setup_queries + setup_server_address = _setup_queries + setup_server_port = _setup_queries + + def _tracer_span_metas(self, excluded_operations: tuple[str, ...] = ("select_error",)): + for db_operation, request in self.get_requests(excluded_operations=excluded_operations): + yield db_operation, self.get_span_from_tracer(request).meta + + def test_db_system_name(self): + """``db.type`` / ``db.system`` become ``db.system.name`` (the stable spec name) = postgresql.""" + for db_operation, meta in self._tracer_span_metas(): + assert meta.get("db.system.name") == "postgresql", f"failing for {db_operation}" + assert "db.system" not in meta, "experimental db.system must be absent (stable name is db.system.name)" + assert "db.type" not in meta, "legacy db.type must be absent in OTel mode" + + def test_db_namespace(self): + """``db.name`` / ``db.instance`` become ``db.namespace`` (the database name).""" + db_container = context.get_container_by_dd_integration_name(self.db_service) + for db_operation, meta in self._tracer_span_metas(): + assert meta.get("db.namespace") == db_container.db_instance, f"failing for {db_operation}" + assert "db.name" not in meta, "legacy db.name must be absent in OTel mode" + assert "db.instance" not in meta, "legacy db.instance must be absent in OTel mode" + + def test_db_operation_name(self): + """``db.operation`` becomes ``db.operation.name`` (the SQL operation, e.g. select/insert).""" + for db_operation, meta in self._tracer_span_metas(excluded_operations=("select_error", "procedure")): + assert db_operation in meta.get("db.operation.name", "").lower(), f"failing for {db_operation}" + assert "db.operation" not in meta, "legacy db.operation must be absent in OTel mode" + + def test_db_query_text(self): + """``db.statement`` becomes ``db.query.text``.""" + for db_operation, meta in self._tracer_span_metas(excluded_operations=("select_error", "procedure")): + assert db_operation in meta.get("db.query.text", "").lower(), f"failing for {db_operation}" + assert "db.statement" not in meta, "legacy db.statement must be absent in OTel mode" + + def test_db_collection_name(self): + """``db.sql.table`` becomes ``db.collection.name`` (the primary table).""" + for db_operation, meta in self._tracer_span_metas(excluded_operations=("select_error", "procedure")): + assert meta.get("db.collection.name"), f"db.collection.name expected, failing for {db_operation}" + assert "db.sql.table" not in meta, "legacy db.sql.table must be absent in OTel mode" + + def test_server_address(self): + """``out.host`` becomes ``server.address``.""" + for db_operation, meta in self._tracer_span_metas(): + assert meta.get("server.address"), f"server.address expected, failing for {db_operation}" + assert "out.host" not in meta, "legacy out.host must be absent in OTel mode" + + def test_server_port(self): + """``out.port`` / ``network.destination.port`` become ``server.port``. + + The postgres port is non-default, so per the spec server.port is expected. Numeric values + may live in ``metrics`` rather than ``meta`` depending on the tracer, so check both. + """ + for db_operation, request in self.get_requests(excluded_operations=("select_error",)): + span = self.get_span_from_tracer(request) + meta, metrics = span.meta, span.metrics + port = meta.get("server.port", metrics.get("server.port")) + assert port is not None, f"server.port expected (non-default port), failing for {db_operation}" + _ = int(port) + for legacy in ("out.port", "network.destination.port"): + assert legacy not in meta, f"legacy {legacy} must be absent in meta in OTel mode ({db_operation})" + assert legacy not in metrics, f"legacy {legacy} must be absent in metrics in OTel mode ({db_operation})" diff --git a/utils/_context/_scenarios/__init__.py b/utils/_context/_scenarios/__init__.py index e3d0af02435..69e9a77ceff 100644 --- a/utils/_context/_scenarios/__init__.py +++ b/utils/_context/_scenarios/__init__.py @@ -12,6 +12,7 @@ CrossedTracingLibraryScenario, DbmDynamicServiceScenario, IntegrationsScenario, + OtelSemanticsDbScenario, AWSIntegrationsScenario, ) from .open_telemetry import OpenTelemetryScenario @@ -255,6 +256,8 @@ class _Scenarios: scenario_groups=[scenario_groups.open_telemetry], ) + otel_semantics_db = OtelSemanticsDbScenario() + # Telemetry scenarios telemetry_dependency_loaded_test_for_dependency_collection_disabled = EndToEndScenario( "TELEMETRY_DEPENDENCY_LOADED_TEST_FOR_DEPENDENCY_COLLECTION_DISABLED", diff --git a/utils/_context/_scenarios/integrations.py b/utils/_context/_scenarios/integrations.py index 8369dc045aa..0a2922f6c60 100644 --- a/utils/_context/_scenarios/integrations.py +++ b/utils/_context/_scenarios/integrations.py @@ -91,6 +91,24 @@ def __init__(self) -> None: ) +class OtelSemanticsDbScenario(EndToEndScenario): + def __init__(self) -> None: + super().__init__( + "OTEL_SEMANTICS_DB", + weblog_env={ + "DD_TRACE_OTEL_SEMANTICS_ENABLED": "true", + "DD_TRACE_OTEL_ENABLED": "1", + "DD_DBM_PROPAGATION_MODE": "full", + }, + other_weblog_containers=(PostgresContainer,), + doc=( + "Like OTEL_SEMANTICS but for database (postgres) spans: validates that SQL spans emit " + "OpenTelemetry database semantic-convention attributes when DD_TRACE_OTEL_SEMANTICS_ENABLED=true" + ), + scenario_groups=[scenario_groups.open_telemetry, scenario_groups.integrations], + ) + + class AWSIntegrationsScenario(EndToEndScenario): unique_id: str = ""