diff --git a/CHANGELOG.md b/CHANGELOG.md index 07db28fe5c..1b54e99832 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `opentelemetry-instrumentation-boto`: Remove instrumentation ([#4303](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4303)) +### Added + +- `opentelemetry-instrumentation-dbapi`: implement new semantic convention opt-in migration + ([#4109](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4109)) + ## Version 1.40.0/0.61b0 (2026-03-04) ### Added diff --git a/instrumentation/README.md b/instrumentation/README.md index 8d9a247945..19c23f2c31 100644 --- a/instrumentation/README.md +++ b/instrumentation/README.md @@ -17,7 +17,7 @@ | [opentelemetry-instrumentation-celery](./opentelemetry-instrumentation-celery) | celery >= 4.0, < 6.0 | No | development | [opentelemetry-instrumentation-click](./opentelemetry-instrumentation-click) | click >= 8.1.3, < 9.0.0 | No | development | [opentelemetry-instrumentation-confluent-kafka](./opentelemetry-instrumentation-confluent-kafka) | confluent-kafka >= 1.8.2, < 3.0.0 | No | development -| [opentelemetry-instrumentation-dbapi](./opentelemetry-instrumentation-dbapi) | dbapi | No | development +| [opentelemetry-instrumentation-dbapi](./opentelemetry-instrumentation-dbapi) | dbapi | No | migration | [opentelemetry-instrumentation-django](./opentelemetry-instrumentation-django) | django >= 2.0 | Yes | development | [opentelemetry-instrumentation-elasticsearch](./opentelemetry-instrumentation-elasticsearch) | elasticsearch >= 6.0 | No | development | [opentelemetry-instrumentation-falcon](./opentelemetry-instrumentation-falcon) | falcon >= 1.4.1, < 5.0.0 | Yes | migration @@ -28,21 +28,21 @@ | [opentelemetry-instrumentation-jinja2](./opentelemetry-instrumentation-jinja2) | jinja2 >= 2.7, < 4.0 | No | development | [opentelemetry-instrumentation-kafka-python](./opentelemetry-instrumentation-kafka-python) | kafka-python >= 2.0, < 3.0,kafka-python-ng >= 2.0, < 3.0 | No | development | [opentelemetry-instrumentation-logging](./opentelemetry-instrumentation-logging) | logging | No | development -| [opentelemetry-instrumentation-mysql](./opentelemetry-instrumentation-mysql) | mysql-connector-python >= 8.0, < 10.0 | No | development -| [opentelemetry-instrumentation-mysqlclient](./opentelemetry-instrumentation-mysqlclient) | mysqlclient < 3 | No | development +| [opentelemetry-instrumentation-mysql](./opentelemetry-instrumentation-mysql) | mysql-connector-python >= 8.0, < 10.0 | No | migration +| [opentelemetry-instrumentation-mysqlclient](./opentelemetry-instrumentation-mysqlclient) | mysqlclient < 3 | No | migration | [opentelemetry-instrumentation-pika](./opentelemetry-instrumentation-pika) | pika >= 0.12.0 | No | development -| [opentelemetry-instrumentation-psycopg](./opentelemetry-instrumentation-psycopg) | psycopg >= 3.1.0 | No | development -| [opentelemetry-instrumentation-psycopg2](./opentelemetry-instrumentation-psycopg2) | psycopg2 >= 2.7.3.1,psycopg2-binary >= 2.7.3.1 | No | development +| [opentelemetry-instrumentation-psycopg](./opentelemetry-instrumentation-psycopg) | psycopg >= 3.1.0 | No | migration +| [opentelemetry-instrumentation-psycopg2](./opentelemetry-instrumentation-psycopg2) | psycopg2 >= 2.7.3.1,psycopg2-binary >= 2.7.3.1 | No | migration | [opentelemetry-instrumentation-pymemcache](./opentelemetry-instrumentation-pymemcache) | pymemcache >= 1.3.5, < 5 | No | development | [opentelemetry-instrumentation-pymongo](./opentelemetry-instrumentation-pymongo) | pymongo >= 3.1, < 5.0 | No | development -| [opentelemetry-instrumentation-pymssql](./opentelemetry-instrumentation-pymssql) | pymssql >= 2.1.5, < 3 | No | development -| [opentelemetry-instrumentation-pymysql](./opentelemetry-instrumentation-pymysql) | PyMySQL < 2 | No | development +| [opentelemetry-instrumentation-pymssql](./opentelemetry-instrumentation-pymssql) | pymssql >= 2.1.5, < 3 | No | migration +| [opentelemetry-instrumentation-pymysql](./opentelemetry-instrumentation-pymysql) | PyMySQL < 2 | No | migration | [opentelemetry-instrumentation-pyramid](./opentelemetry-instrumentation-pyramid) | pyramid >= 1.7 | Yes | migration | [opentelemetry-instrumentation-redis](./opentelemetry-instrumentation-redis) | redis >= 2.6 | No | development | [opentelemetry-instrumentation-remoulade](./opentelemetry-instrumentation-remoulade) | remoulade >= 0.50 | No | development | [opentelemetry-instrumentation-requests](./opentelemetry-instrumentation-requests) | requests ~= 2.0 | Yes | migration | [opentelemetry-instrumentation-sqlalchemy](./opentelemetry-instrumentation-sqlalchemy) | sqlalchemy >= 1.0.0, < 2.1.0 | Yes | migration -| [opentelemetry-instrumentation-sqlite3](./opentelemetry-instrumentation-sqlite3) | sqlite3 | No | development +| [opentelemetry-instrumentation-sqlite3](./opentelemetry-instrumentation-sqlite3) | sqlite3 | No | migration | [opentelemetry-instrumentation-starlette](./opentelemetry-instrumentation-starlette) | starlette >= 0.13 | Yes | development | [opentelemetry-instrumentation-system-metrics](./opentelemetry-instrumentation-system-metrics) | psutil >= 5 | No | development | [opentelemetry-instrumentation-threading](./opentelemetry-instrumentation-threading) | threading | No | development diff --git a/instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/__init__.py b/instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/__init__.py index 8be92c13fe..c4d365b90d 100644 --- a/instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/__init__.py @@ -139,9 +139,9 @@ SQLComment in span attribute **************************** If sqlcommenter is enabled, you can opt into the inclusion of sqlcomment in -the query span ``db.statement`` attribute for your needs. If ``commenter_options`` -have been set, the span attribute comment will also be configured by this -setting. +the query span ``db.statement`` and/or ``db.query.text`` attribute for your +needs. If ``commenter_options`` have been set, the span attribute comment +will also be configured by this setting. .. code:: python @@ -151,7 +151,7 @@ # Opts into sqlcomment for MySQL trace integration. - # Opts into sqlcomment for `db.statement` span attribute. + # Opts into sqlcomment for `db.statement` and/or `db.query.text` span attribute. wrap_connect( __name__, mysql.connector, @@ -186,6 +186,17 @@ from wrapt import ObjectProxy as BaseObjectProxy from opentelemetry import trace as trace_api +from opentelemetry.instrumentation._semconv import ( + _get_schema_url, + _OpenTelemetrySemanticConventionStability, + _OpenTelemetryStabilitySignalType, + _set_db_name, + _set_db_statement, + _set_db_system, + _set_db_user, + _set_http_net_peer_name_client, + _set_http_peer_port_client, +) from opentelemetry.instrumentation.dbapi.version import __version__ from opentelemetry.instrumentation.sqlcommenter_utils import _add_sql_comment from opentelemetry.instrumentation.utils import ( @@ -193,16 +204,6 @@ is_instrumentation_enabled, unwrap, ) -from opentelemetry.semconv._incubating.attributes.db_attributes import ( - DB_NAME, - DB_STATEMENT, - DB_SYSTEM, - DB_USER, -) -from opentelemetry.semconv._incubating.attributes.net_attributes import ( - NET_PEER_NAME, - NET_PEER_PORT, -) from opentelemetry.trace import SpanKind, TracerProvider, get_tracer from opentelemetry.util._importlib_metadata import version as util_version @@ -244,7 +245,7 @@ def trace_integration( enable_commenter: Flag to enable/disable sqlcommenter. db_api_integration_factory: The `DatabaseApiIntegration` to use. If none is passed the default one is used. - enable_attribute_commenter: Flag to enable/disable sqlcomment inclusion in `db.statement` span attribute. Only available if enable_commenter=True. + enable_attribute_commenter: Flag to enable/disable sqlcomment inclusion in `db.statement` and/or `db.query.text` span attribute. Only available if enable_commenter=True. commenter_options: Configurations for tags to be appended at the sql query. """ wrap_connect( @@ -294,7 +295,7 @@ def wrap_connect( db_api_integration_factory: The `DatabaseApiIntegration` to use. If none is passed the default one is used. commenter_options: Configurations for tags to be appended at the sql query. - enable_attribute_commenter: Flag to enable/disable sqlcomment inclusion in `db.statement` span attribute. Only available if enable_commenter=True. + enable_attribute_commenter: Flag to enable/disable sqlcomment inclusion in `db.statement` and/or `db.query.text` span attribute. Only available if enable_commenter=True. """ db_api_integration_factory = ( @@ -372,7 +373,7 @@ def instrument_connection( enable_commenter: Flag to enable/disable sqlcommenter. commenter_options: Configurations for tags to be appended at the sql query. connect_module: Module name where connect method is available. - enable_attribute_commenter: Flag to enable/disable sqlcomment inclusion in `db.statement` span attribute. Only available if enable_commenter=True. + enable_attribute_commenter: Flag to enable/disable sqlcomment inclusion in `db.statement` and/or `db.query.text` span attribute. Only available if enable_commenter=True. db_api_integration_factory: A class or factory function to use as a replacement for :class:`DatabaseApiIntegration`. Can be used to obtain connection attributes from the connect method instead of @@ -437,6 +438,12 @@ def __init__( connect_module: Callable[..., Any] | None = None, enable_attribute_commenter: bool = False, ): + # Initialize semantic conventions opt-in if needed + _OpenTelemetrySemanticConventionStability._initialize() + self._sem_conv_opt_in_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode( + _OpenTelemetryStabilitySignalType.DATABASE, + ) + if connection_attributes is None: self.connection_attributes = { "database": "database", @@ -452,7 +459,7 @@ def __init__( self._name, instrumenting_library_version=self._version, tracer_provider=tracer_provider, - schema_url="https://opentelemetry.io/schemas/1.11.0", + schema_url=_get_schema_url(self._sem_conv_opt_in_mode), ) self.capture_parameters = capture_parameters self.enable_commenter = enable_commenter @@ -565,13 +572,21 @@ def get_connection_attributes(self, connection: object) -> None: if user and isinstance(user, bytes): user = user.decode() if user is not None: - self.span_attributes[DB_USER] = str(user) + _set_db_user( + self.span_attributes, str(user), self._sem_conv_opt_in_mode + ) host = self.connection_props.get("host") if host is not None: - self.span_attributes[NET_PEER_NAME] = host + _set_http_net_peer_name_client( + self.span_attributes, + host, + self._sem_conv_opt_in_mode, + ) port = self.connection_props.get("port") if port is not None: - self.span_attributes[NET_PEER_PORT] = port + _set_http_peer_port_client( + self.span_attributes, port, self._sem_conv_opt_in_mode + ) # pylint: disable=abstract-method,no-member @@ -699,9 +714,24 @@ def _populate_span( if not span.is_recording(): return statement = self.get_statement(cursor, args) - span.set_attribute(DB_SYSTEM, self._db_api_integration.database_system) - span.set_attribute(DB_NAME, self._db_api_integration.database) - span.set_attribute(DB_STATEMENT, statement) + sem_conv_mode = self._db_api_integration._sem_conv_opt_in_mode + span_attrs = {} + + _set_db_system( + span_attrs, + self._db_api_integration.database_system, + sem_conv_mode, + ) + _set_db_name( + span_attrs, + self._db_api_integration.database, + sem_conv_mode, + ) + _set_db_statement(span_attrs, statement, sem_conv_mode) + + # Set all collected attributes + for attribute_key, attribute_value in span_attrs.items(): + span.set_attribute(attribute_key, attribute_value) for ( attribute_key, @@ -752,14 +782,14 @@ def traced_execution( if span.is_recording(): if args and self._commenter_enabled: if self._enable_attribute_commenter: - # sqlcomment is added to executed query and db.statement span attribute + # sqlcomment is added to executed query and db.statement and/or db.query.text span attribute args = self._update_args_with_added_sql_comment( args, cursor ) self._populate_span(span, cursor, *args) else: # sqlcomment is only added to executed query - # so db.statement is set before add_sql_comment + # so db.statement and/or db.query.text are set before add_sql_comment self._populate_span(span, cursor, *args) args = self._update_args_with_added_sql_comment( args, cursor @@ -790,14 +820,14 @@ async def traced_execution_async( if span.is_recording(): if args and self._commenter_enabled: if self._enable_attribute_commenter: - # sqlcomment is added to executed query and db.statement span attribute + # sqlcomment is added to executed query and db.statement and/or db.query.text span attribute args = self._update_args_with_added_sql_comment( args, cursor ) self._populate_span(span, cursor, *args) else: # sqlcomment is only added to executed query - # so db.statement is set before add_sql_comment + # so db.statement and/or db.query.text are set before add_sql_comment self._populate_span(span, cursor, *args) args = self._update_args_with_added_sql_comment( args, cursor diff --git a/instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/package.py b/instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/package.py index 7a66a17a93..c2e299b484 100644 --- a/instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/package.py +++ b/instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/package.py @@ -14,3 +14,5 @@ _instruments = tuple() + +_semconv_status = "migration" diff --git a/instrumentation/opentelemetry-instrumentation-dbapi/tests/test_dbapi_integration.py b/instrumentation/opentelemetry-instrumentation-dbapi/tests/test_dbapi_integration.py index 27b43f05ad..108a6c443e 100644 --- a/instrumentation/opentelemetry-instrumentation-dbapi/tests/test_dbapi_integration.py +++ b/instrumentation/opentelemetry-instrumentation-dbapi/tests/test_dbapi_integration.py @@ -21,6 +21,10 @@ from opentelemetry import context from opentelemetry import trace as trace_api from opentelemetry.instrumentation import dbapi +from opentelemetry.instrumentation._semconv import ( + OTEL_SEMCONV_STABILITY_OPT_IN, + _OpenTelemetrySemanticConventionStability, +) from opentelemetry.instrumentation.utils import suppress_instrumentation from opentelemetry.sdk import resources from opentelemetry.semconv._incubating.attributes import net_attributes @@ -34,28 +38,66 @@ NET_PEER_NAME, NET_PEER_PORT, ) +from opentelemetry.semconv.attributes.db_attributes import ( + DB_NAMESPACE, + DB_QUERY_TEXT, + DB_SYSTEM_NAME, +) +from opentelemetry.semconv.attributes.server_attributes import ( + SERVER_ADDRESS, + SERVER_PORT, +) from opentelemetry.test.test_base import TestBase +def _get_default_connection_props(): + """Returns standard connection properties for testing.""" + return { + "database": "testdatabase", + "server_host": "testhost", + "server_port": 123, + "user": "testuser", + } + + +def _get_default_connection_attributes(): + """Returns standard connection attributes for testing.""" + return { + "database": "database", + "port": "server_port", + "host": "server_host", + "user": "user", + } + + # pylint: disable=too-many-public-methods class TestDBApiIntegration(TestBase): def setUp(self): super().setUp() self.tracer = self.tracer_provider.get_tracer(__name__) + test_name = "" + if hasattr(self, "_testMethodName"): + test_name = self._testMethodName + sem_conv_mode = "default" + if "new_semconv" in test_name: + sem_conv_mode = "database" + elif "both_semconv" in test_name: + sem_conv_mode = "database/dup" + self.env_patch = mock.patch.dict( + "os.environ", + { + OTEL_SEMCONV_STABILITY_OPT_IN: sem_conv_mode, + }, + ) + + _OpenTelemetrySemanticConventionStability._initialized = False + + self.env_patch.start() + def test_span_succeeded(self): - connection_props = { - "database": "testdatabase", - "server_host": "testhost", - "server_port": 123, - "user": "testuser", - } - connection_attributes = { - "database": "database", - "port": "server_port", - "host": "server_host", - "user": "user", - } + connection_props = _get_default_connection_props() + connection_attributes = _get_default_connection_attributes() db_integration = dbapi.DatabaseApiIntegration( "instrumenting_module_test_name", "testcomponent", @@ -81,6 +123,82 @@ def test_span_succeeded(self): self.assertEqual(span.attributes[NET_PEER_PORT], 123) self.assertIs(span.status.status_code, trace_api.StatusCode.UNSET) + def test_span_succeeded_new_semconv(self): + connection_props = _get_default_connection_props() + connection_attributes = _get_default_connection_attributes() + db_integration = dbapi.DatabaseApiIntegration( + "instrumenting_module_test_name", + "testcomponent", + connection_attributes, + ) + mock_connection = db_integration.wrapped_connection( + mock_connect, {}, connection_props + ) + cursor = mock_connection.cursor() + cursor.execute("Test query", ("param1Value", False)) + spans_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans_list), 1) + span = spans_list[0] + self.assertEqual(span.name, "Test") + self.assertIs(span.kind, trace_api.SpanKind.CLIENT) + + # Stable attributes only + self.assertEqual(span.attributes[DB_SYSTEM_NAME], "testcomponent") + self.assertEqual(span.attributes[DB_NAMESPACE], "testdatabase") + self.assertEqual(span.attributes[DB_QUERY_TEXT], "Test query") + self.assertFalse("db.statement.parameters" in span.attributes) + # db.user removed in stable - no replacement + self.assertFalse(DB_USER in span.attributes) + self.assertEqual(span.attributes[SERVER_ADDRESS], "testhost") + self.assertEqual(span.attributes[SERVER_PORT], 123) + + # Old attributes should not be present + self.assertFalse(DB_SYSTEM in span.attributes) + self.assertFalse(DB_NAME in span.attributes) + self.assertFalse(DB_STATEMENT in span.attributes) + self.assertFalse(NET_PEER_NAME in span.attributes) + self.assertFalse(NET_PEER_PORT in span.attributes) + + self.assertIs(span.status.status_code, trace_api.StatusCode.UNSET) + + def test_span_succeeded_both_semconv(self): + connection_props = _get_default_connection_props() + connection_attributes = _get_default_connection_attributes() + db_integration = dbapi.DatabaseApiIntegration( + "instrumenting_module_test_name", + "testcomponent", + connection_attributes, + ) + mock_connection = db_integration.wrapped_connection( + mock_connect, {}, connection_props + ) + cursor = mock_connection.cursor() + cursor.execute("Test query", ("param1Value", False)) + spans_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans_list), 1) + span = spans_list[0] + self.assertEqual(span.name, "Test") + self.assertIs(span.kind, trace_api.SpanKind.CLIENT) + + # Both old and new attributes should be present + # Old attributes + self.assertEqual(span.attributes[DB_SYSTEM], "testcomponent") + self.assertEqual(span.attributes[DB_NAME], "testdatabase") + self.assertEqual(span.attributes[DB_STATEMENT], "Test query") + self.assertEqual(span.attributes[DB_USER], "testuser") + self.assertEqual(span.attributes[NET_PEER_NAME], "testhost") + self.assertEqual(span.attributes[NET_PEER_PORT], 123) + + # New stable attributes + self.assertEqual(span.attributes[DB_SYSTEM_NAME], "testcomponent") + self.assertEqual(span.attributes[DB_NAMESPACE], "testdatabase") + self.assertEqual(span.attributes[DB_QUERY_TEXT], "Test query") + self.assertEqual(span.attributes[SERVER_ADDRESS], "testhost") + self.assertEqual(span.attributes[SERVER_PORT], 123) + + self.assertFalse("db.statement.parameters" in span.attributes) + self.assertIs(span.status.status_code, trace_api.StatusCode.UNSET) + def test_span_name(self): db_integration = dbapi.DatabaseApiIntegration( "instrumenting_module_test_name", "testcomponent", {} @@ -109,18 +227,88 @@ def test_span_name(self): self.assertEqual(spans_list[5].name, "query") def test_span_succeeded_with_capture_of_statement_parameters(self): - connection_props = { - "database": "testdatabase", - "server_host": "testhost", - "server_port": 123, - "user": "testuser", - } - connection_attributes = { - "database": "database", - "port": "server_port", - "host": "server_host", - "user": "user", - } + connection_props = _get_default_connection_props() + connection_attributes = _get_default_connection_attributes() + db_integration = dbapi.DatabaseApiIntegration( + "instrumenting_module_test_name", + "testcomponent", + connection_attributes, + capture_parameters=True, + ) + mock_connection = db_integration.wrapped_connection( + mock_connect, {}, connection_props + ) + cursor = mock_connection.cursor() + cursor.execute("Test query", ("param1Value", False)) + spans_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans_list), 1) + span = spans_list[0] + self.assertEqual(span.name, "Test") + self.assertIs(span.kind, trace_api.SpanKind.CLIENT) + + self.assertEqual(span.attributes[DB_SYSTEM], "testcomponent") + self.assertEqual(span.attributes[DB_NAME], "testdatabase") + self.assertEqual(span.attributes[DB_STATEMENT], "Test query") + self.assertEqual( + span.attributes["db.statement.parameters"], + "('param1Value', False)", + ) + self.assertEqual(span.attributes[DB_USER], "testuser") + self.assertEqual( + span.attributes[net_attributes.NET_PEER_NAME], "testhost" + ) + self.assertEqual(span.attributes[net_attributes.NET_PEER_PORT], 123) + self.assertIs(span.status.status_code, trace_api.StatusCode.UNSET) + + def test_span_succeeded_with_capture_of_statement_parameters_new_semconv( + self, + ): + connection_props = _get_default_connection_props() + connection_attributes = _get_default_connection_attributes() + db_integration = dbapi.DatabaseApiIntegration( + "instrumenting_module_test_name", + "testcomponent", + connection_attributes, + capture_parameters=True, + ) + mock_connection = db_integration.wrapped_connection( + mock_connect, {}, connection_props + ) + cursor = mock_connection.cursor() + cursor.execute("Test query", ("param1Value", False)) + spans_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans_list), 1) + span = spans_list[0] + self.assertEqual(span.name, "Test") + self.assertIs(span.kind, trace_api.SpanKind.CLIENT) + + # Stable attributes only + self.assertEqual(span.attributes[DB_SYSTEM_NAME], "testcomponent") + self.assertEqual(span.attributes[DB_NAMESPACE], "testdatabase") + self.assertEqual(span.attributes[DB_QUERY_TEXT], "Test query") + self.assertEqual( + span.attributes["db.statement.parameters"], + "('param1Value', False)", + ) + # db.user removed in stable - no replacement + self.assertFalse(DB_USER in span.attributes) + self.assertEqual(span.attributes[SERVER_ADDRESS], "testhost") + self.assertEqual(span.attributes[SERVER_PORT], 123) + + # Old attributes should not be present + self.assertFalse(DB_SYSTEM in span.attributes) + self.assertFalse(DB_NAME in span.attributes) + self.assertFalse(DB_STATEMENT in span.attributes) + self.assertFalse(NET_PEER_NAME in span.attributes) + self.assertFalse(NET_PEER_PORT in span.attributes) + + self.assertIs(span.status.status_code, trace_api.StatusCode.UNSET) + + def test_span_succeeded_with_capture_of_statement_parameters_both_semconv( + self, + ): + connection_props = _get_default_connection_props() + connection_attributes = _get_default_connection_attributes() db_integration = dbapi.DatabaseApiIntegration( "instrumenting_module_test_name", "testcomponent", @@ -138,6 +326,8 @@ def test_span_succeeded_with_capture_of_statement_parameters(self): self.assertEqual(span.name, "Test") self.assertIs(span.kind, trace_api.SpanKind.CLIENT) + # Both old and new attributes should be present + # Old attributes self.assertEqual(span.attributes[DB_SYSTEM], "testcomponent") self.assertEqual(span.attributes[DB_NAME], "testdatabase") self.assertEqual(span.attributes[DB_STATEMENT], "Test query") @@ -150,21 +340,19 @@ def test_span_succeeded_with_capture_of_statement_parameters(self): span.attributes[net_attributes.NET_PEER_NAME], "testhost" ) self.assertEqual(span.attributes[net_attributes.NET_PEER_PORT], 123) + + # New stable attributes + self.assertEqual(span.attributes[DB_SYSTEM_NAME], "testcomponent") + self.assertEqual(span.attributes[DB_NAMESPACE], "testdatabase") + self.assertEqual(span.attributes[DB_QUERY_TEXT], "Test query") + self.assertEqual(span.attributes[SERVER_ADDRESS], "testhost") + self.assertEqual(span.attributes[SERVER_PORT], 123) + self.assertIs(span.status.status_code, trace_api.StatusCode.UNSET) def test_span_not_recording(self): - connection_props = { - "database": "testdatabase", - "server_host": "testhost", - "server_port": 123, - "user": "testuser", - } - connection_attributes = { - "database": "database", - "port": "server_port", - "host": "server_host", - "user": "user", - } + connection_props = _get_default_connection_props() + connection_attributes = _get_default_connection_attributes() mock_span = mock.Mock() mock_span.is_recording.return_value = False db_integration = dbapi.DatabaseApiIntegration( diff --git a/instrumentation/opentelemetry-instrumentation-mysql/src/opentelemetry/instrumentation/mysql/__init__.py b/instrumentation/opentelemetry-instrumentation-mysql/src/opentelemetry/instrumentation/mysql/__init__.py index b6c278f50b..580a89f548 100644 --- a/instrumentation/opentelemetry-instrumentation-mysql/src/opentelemetry/instrumentation/mysql/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-mysql/src/opentelemetry/instrumentation/mysql/__init__.py @@ -127,23 +127,23 @@ SQLComment in span attribute **************************** If sqlcommenter is enabled, you can opt into the inclusion of sqlcomment in -the query span ``db.statement`` attribute for your needs. If ``commenter_options`` -have been set, the span attribute comment will also be configured by this -setting. +the query span ``db.statement`` and/or ``db.query.text`` attribute for your +needs. If ``commenter_options`` have been set, the span attribute comment +will also be configured by this setting. .. code:: python from opentelemetry.instrumentation.mysql import MySQLInstrumentor # Opts into sqlcomment for mysql-connector trace integration. - # Opts into sqlcomment for `db.statement` span attribute. + # Opts into sqlcomment for `db.statement` and/or `db.query.text` span attribute. MySQLInstrumentor().instrument( enable_commenter=True, enable_attribute_commenter=True, ) Warning: - Capture of sqlcomment in ``db.statement`` may have high cardinality without platform normalization. See `Semantic Conventions for database spans `_ for more information. + Capture of sqlcomment in ``db.statement``/``db.query.text`` may have high cardinality without platform normalization. See `Semantic Conventions for database spans `_ for more information. API --- diff --git a/instrumentation/opentelemetry-instrumentation-mysql/src/opentelemetry/instrumentation/mysql/package.py b/instrumentation/opentelemetry-instrumentation-mysql/src/opentelemetry/instrumentation/mysql/package.py index 4d84c1161b..844b069de0 100644 --- a/instrumentation/opentelemetry-instrumentation-mysql/src/opentelemetry/instrumentation/mysql/package.py +++ b/instrumentation/opentelemetry-instrumentation-mysql/src/opentelemetry/instrumentation/mysql/package.py @@ -14,3 +14,5 @@ _instruments = ("mysql-connector-python >= 8.0, < 10.0",) + +_semconv_status = "migration" diff --git a/instrumentation/opentelemetry-instrumentation-mysqlclient/src/opentelemetry/instrumentation/mysqlclient/__init__.py b/instrumentation/opentelemetry-instrumentation-mysqlclient/src/opentelemetry/instrumentation/mysqlclient/__init__.py index b45e0f97af..1b2fd5c00a 100644 --- a/instrumentation/opentelemetry-instrumentation-mysqlclient/src/opentelemetry/instrumentation/mysqlclient/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-mysqlclient/src/opentelemetry/instrumentation/mysqlclient/__init__.py @@ -121,14 +121,14 @@ from opentelemetry.instrumentation.mysqlclient import MySQLClientInstrumentor # Opts into sqlcomment for mysqlclient trace integration. - # Opts into sqlcomment for `db.statement` span attribute. + # Opts into sqlcomment for `db.statement` and/or `db.query.text` span attribute. MySQLClientInstrumentor().instrument( enable_commenter=True, enable_attribute_commenter=True, ) Warning: - Capture of sqlcomment in ``db.statement`` may have high cardinality without platform normalization. See `Semantic Conventions for database spans `_ for more information. + Capture of sqlcomment in ``db.statement``/``db.query.text`` may have high cardinality without platform normalization. See `Semantic Conventions for database spans `_ for more information. API --- diff --git a/instrumentation/opentelemetry-instrumentation-mysqlclient/src/opentelemetry/instrumentation/mysqlclient/package.py b/instrumentation/opentelemetry-instrumentation-mysqlclient/src/opentelemetry/instrumentation/mysqlclient/package.py index 9469194728..8b5acda1bd 100644 --- a/instrumentation/opentelemetry-instrumentation-mysqlclient/src/opentelemetry/instrumentation/mysqlclient/package.py +++ b/instrumentation/opentelemetry-instrumentation-mysqlclient/src/opentelemetry/instrumentation/mysqlclient/package.py @@ -14,3 +14,5 @@ _instruments = ("mysqlclient < 3",) + +_semconv_status = "migration" diff --git a/instrumentation/opentelemetry-instrumentation-psycopg/src/opentelemetry/instrumentation/psycopg/__init__.py b/instrumentation/opentelemetry-instrumentation-psycopg/src/opentelemetry/instrumentation/psycopg/__init__.py index 28896be138..5c62e49ca1 100644 --- a/instrumentation/opentelemetry-instrumentation-psycopg/src/opentelemetry/instrumentation/psycopg/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-psycopg/src/opentelemetry/instrumentation/psycopg/__init__.py @@ -119,23 +119,23 @@ SQLComment in span attribute **************************** If sqlcommenter is enabled, you can opt into the inclusion of sqlcomment in -the query span ``db.statement`` attribute for your needs. If ``commenter_options`` -have been set, the span attribute comment will also be configured by this -setting. +the query span ``db.statement`` and/or ``db.query.text`` attribute for your +needs. If ``commenter_options`` have been set, the span attribute comment +will also be configured by this setting. .. code:: python from opentelemetry.instrumentation.psycopg import PsycopgInstrumentor # Opts into sqlcomment for Psycopg trace integration. - # Opts into sqlcomment for `db.statement` span attribute. + # Opts into sqlcomment for `db.statement` and/or `db.query.text` span attribute. PsycopgInstrumentor().instrument( enable_commenter=True, enable_attribute_commenter=True, ) Warning: - Capture of sqlcomment in ``db.statement`` may have high cardinality without platform normalization. See `Semantic Conventions for database spans `_ for more information. + Capture of sqlcomment in ``db.statement``/``db.query.text`` may have high cardinality without platform normalization. See `Semantic Conventions for database spans `_ for more information. API --- diff --git a/instrumentation/opentelemetry-instrumentation-psycopg/src/opentelemetry/instrumentation/psycopg/package.py b/instrumentation/opentelemetry-instrumentation-psycopg/src/opentelemetry/instrumentation/psycopg/package.py index a3ee72d1ae..53d38521ea 100644 --- a/instrumentation/opentelemetry-instrumentation-psycopg/src/opentelemetry/instrumentation/psycopg/package.py +++ b/instrumentation/opentelemetry-instrumentation-psycopg/src/opentelemetry/instrumentation/psycopg/package.py @@ -14,3 +14,5 @@ from __future__ import annotations _instruments: tuple[str, ...] = ("psycopg >= 3.1.0",) + +_semconv_status = "migration" diff --git a/instrumentation/opentelemetry-instrumentation-psycopg2/src/opentelemetry/instrumentation/psycopg2/__init__.py b/instrumentation/opentelemetry-instrumentation-psycopg2/src/opentelemetry/instrumentation/psycopg2/__init__.py index 4c8b1b6e02..10f23d304b 100644 --- a/instrumentation/opentelemetry-instrumentation-psycopg2/src/opentelemetry/instrumentation/psycopg2/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-psycopg2/src/opentelemetry/instrumentation/psycopg2/__init__.py @@ -118,23 +118,23 @@ SQLComment in span attribute **************************** If sqlcommenter is enabled, you can opt into the inclusion of sqlcomment in -the query span ``db.statement`` attribute for your needs. If ``commenter_options`` -have been set, the span attribute comment will also be configured by this -setting. +the query span ``db.statement`` and/or ``db.query.text`` attribute for your +needs. If ``commenter_options`` have been set, the span attribute comment +will also be configured by this setting. .. code:: python from opentelemetry.instrumentation.psycopg2 import Psycopg2Instrumentor # Opts into sqlcomment for Psycopg2 trace integration. - # Opts into sqlcomment for `db.statement` span attribute. + # Opts into sqlcomment for `db.statement` and/or `db.query.text` span attribute. Psycopg2Instrumentor().instrument( enable_commenter=True, enable_attribute_commenter=True, ) Warning: - Capture of sqlcomment in ``db.statement`` may have high cardinality without platform normalization. See `Semantic Conventions for database spans `_ for more information. + Capture of sqlcomment in ``db.statement``/``db.query.text`` may have high cardinality without platform normalization. See `Semantic Conventions for database spans `_ for more information. Capture parameters ****************** diff --git a/instrumentation/opentelemetry-instrumentation-psycopg2/src/opentelemetry/instrumentation/psycopg2/package.py b/instrumentation/opentelemetry-instrumentation-psycopg2/src/opentelemetry/instrumentation/psycopg2/package.py index 1aef25432c..6ec6f9beac 100644 --- a/instrumentation/opentelemetry-instrumentation-psycopg2/src/opentelemetry/instrumentation/psycopg2/package.py +++ b/instrumentation/opentelemetry-instrumentation-psycopg2/src/opentelemetry/instrumentation/psycopg2/package.py @@ -21,3 +21,5 @@ _instruments_psycopg2, _instruments_psycopg2_binary, ) + +_semconv_status = "migration" diff --git a/instrumentation/opentelemetry-instrumentation-pymssql/src/opentelemetry/instrumentation/pymssql/package.py b/instrumentation/opentelemetry-instrumentation-pymssql/src/opentelemetry/instrumentation/pymssql/package.py index 92bc9e0193..875ec38cc4 100644 --- a/instrumentation/opentelemetry-instrumentation-pymssql/src/opentelemetry/instrumentation/pymssql/package.py +++ b/instrumentation/opentelemetry-instrumentation-pymssql/src/opentelemetry/instrumentation/pymssql/package.py @@ -14,3 +14,5 @@ _instruments = ("pymssql >= 2.1.5, < 3",) + +_semconv_status = "migration" diff --git a/instrumentation/opentelemetry-instrumentation-pymysql/src/opentelemetry/instrumentation/pymysql/__init__.py b/instrumentation/opentelemetry-instrumentation-pymysql/src/opentelemetry/instrumentation/pymysql/__init__.py index 2dae113746..9e8e9bbb65 100644 --- a/instrumentation/opentelemetry-instrumentation-pymysql/src/opentelemetry/instrumentation/pymysql/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-pymysql/src/opentelemetry/instrumentation/pymysql/__init__.py @@ -135,23 +135,23 @@ SQLComment in span attribute **************************** If sqlcommenter is enabled, you can opt into the inclusion of sqlcomment in -the query span ``db.statement`` attribute for your needs. If ``commenter_options`` -have been set, the span attribute comment will also be configured by this -setting. +the query span ``db.statement`` and/or ``db.query.text`` attribute for your +needs. If ``commenter_options`` have been set, the span attribute comment +will also be configured by this setting. .. code:: python from opentelemetry.instrumentation.pymysql import PyMySQLInstrumentor # Opts into sqlcomment for PyMySQL trace integration. - # Opts into sqlcomment for `db.statement` span attribute. + # Opts into sqlcomment for `db.statement` and/or `db.query.text` span attribute. PyMySQLInstrumentor().instrument( enable_commenter=True, enable_attribute_commenter=True, ) Warning: - Capture of sqlcomment in ``db.statement`` may have high cardinality without platform normalization. See `Semantic Conventions for database spans `_ for more information. + Capture of sqlcomment in ``db.statement``/``db.query.text`` may have high cardinality without platform normalization. See `Semantic Conventions for database spans `_ for more information. API --- diff --git a/instrumentation/opentelemetry-instrumentation-pymysql/src/opentelemetry/instrumentation/pymysql/package.py b/instrumentation/opentelemetry-instrumentation-pymysql/src/opentelemetry/instrumentation/pymysql/package.py index 509f0ebf5f..1aed059743 100644 --- a/instrumentation/opentelemetry-instrumentation-pymysql/src/opentelemetry/instrumentation/pymysql/package.py +++ b/instrumentation/opentelemetry-instrumentation-pymysql/src/opentelemetry/instrumentation/pymysql/package.py @@ -14,3 +14,5 @@ _instruments = ("PyMySQL < 2",) + +_semconv_status = "migration" diff --git a/instrumentation/opentelemetry-instrumentation-sqlite3/src/opentelemetry/instrumentation/sqlite3/package.py b/instrumentation/opentelemetry-instrumentation-sqlite3/src/opentelemetry/instrumentation/sqlite3/package.py index 0de6133b4a..6d3e41aa34 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlite3/src/opentelemetry/instrumentation/sqlite3/package.py +++ b/instrumentation/opentelemetry-instrumentation-sqlite3/src/opentelemetry/instrumentation/sqlite3/package.py @@ -14,3 +14,5 @@ from __future__ import annotations _instruments: tuple[str, ...] = tuple() + +_semconv_status = "migration" diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/_semconv.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/_semconv.py index 1edd18d038..78f8b2f4a1 100644 --- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/_semconv.py +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/_semconv.py @@ -334,8 +334,8 @@ def set_int_attribute( if value: try: result[key] = int(value) - except ValueError: - pass + except (ValueError, TypeError): + return def _set_http_method(