From 340b6e127a2114e331fa21fe03956f8b0cf9152f Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Tue, 2 Sep 2025 12:53:00 -0700 Subject: [PATCH 1/2] Add dbapi comment mysql_client_version fallback --- .../instrumentation/dbapi/__init__.py | 21 ++++- .../tests/test_dbapi_integration.py | 83 +++++++++++++++++++ 2 files changed, 101 insertions(+), 3 deletions(-) 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 a2d63c20e3..f7c68748ba 100644 --- a/instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/__init__.py @@ -478,9 +478,24 @@ def _capture_mysql_version(self, cursor) -> None: "mysql_client_version" ] ): - self._db_api_integration.commenter_data["mysql_client_version"] = ( - cursor._cnx._cmysql.get_client_info() - ) + try: + # Autoinstrumentation and some programmatic calls + self._db_api_integration.commenter_data[ + "mysql_client_version" + ] = cursor._cnx._cmysql.get_client_info() + except AttributeError: + # Other programmatic instrumentation with reassigned wrapped connection + try: + self._db_api_integration.commenter_data[ + "mysql_client_version" + ] = cursor._connection._cmysql.get_client_info() + except AttributeError as exc: + _logger.error( + "Could not set mysql_client_version: %s", exc + ) + self._db_api_integration.commenter_data[ + "mysql_client_version" + ] = "unknown" def _get_commenter_data(self) -> dict: """Uses DB-API integration to return commenter data for sqlcomment""" diff --git a/instrumentation/opentelemetry-instrumentation-dbapi/tests/test_dbapi_integration.py b/instrumentation/opentelemetry-instrumentation-dbapi/tests/test_dbapi_integration.py index 9a7a4a7d12..7d1f6741db 100644 --- a/instrumentation/opentelemetry-instrumentation-dbapi/tests/test_dbapi_integration.py +++ b/instrumentation/opentelemetry-instrumentation-dbapi/tests/test_dbapi_integration.py @@ -1123,6 +1123,89 @@ def test_non_string_sql_conversion(self): spans_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans_list), 1) + def test_capture_mysql_version_primary_success(self): + connect_module = mock.MagicMock() + connect_module.__name__ = "mysql.connector" + connect_module.__version__ = "2.2.9" + db_integration = dbapi.DatabaseApiIntegration( + "instrumenting_module_test_name", + "mysql", + enable_commenter=True, + connect_module=connect_module, + ) + mock_cursor = mock.MagicMock() + mock_cursor._cnx._cmysql.get_client_info.return_value = "8.0.32" + mock_connection = db_integration.wrapped_connection( + mock_connect, {}, {} + ) + cursor = mock_connection.cursor() + cursor._cnx = mock_cursor._cnx + cursor.execute("SELECT 1;") + mock_cursor._cnx._cmysql.get_client_info.assert_called_once() + self.assertEqual( + db_integration.commenter_data["mysql_client_version"], "8.0.32" + ) + + def test_capture_mysql_version_fallback_success(self): + connect_module = mock.MagicMock() + connect_module.__name__ = "mysql.connector" + connect_module.__version__ = "2.2.9" + db_integration = dbapi.DatabaseApiIntegration( + "instrumenting_module_test_name", + "mysql", + enable_commenter=True, + connect_module=connect_module, + ) + mock_cursor = mock.MagicMock() + mock_cursor._cnx._cmysql.get_client_info.side_effect = AttributeError( + "Primary method failed" + ) + mock_cursor._connection._cmysql.get_client_info.return_value = "8.0.33" + mock_connection = db_integration.wrapped_connection( + mock_connect, {}, {} + ) + cursor = mock_connection.cursor() + cursor._cnx = mock_cursor._cnx + cursor._connection = mock_cursor._connection + cursor.execute("SELECT 1;") + mock_cursor._cnx._cmysql.get_client_info.assert_called_once() + mock_cursor._connection._cmysql.get_client_info.assert_called_once() + self.assertEqual( + db_integration.commenter_data["mysql_client_version"], "8.0.33" + ) + + @mock.patch("opentelemetry.instrumentation.dbapi._logger") + def test_capture_mysql_version_fallback(self, mock_logger): + connect_module = mock.MagicMock() + connect_module.__name__ = "mysql.connector" + connect_module.__version__ = "2.2.9" + db_integration = dbapi.DatabaseApiIntegration( + "instrumenting_module_test_name", + "mysql", + enable_commenter=True, + connect_module=connect_module, + ) + mock_cursor = mock.MagicMock() + mock_cursor._cnx._cmysql.get_client_info.side_effect = AttributeError( + "Primary method failed" + ) + mock_cursor._connection._cmysql.get_client_info.side_effect = ( + AttributeError("Fallback method failed") + ) + mock_connection = db_integration.wrapped_connection( + mock_connect, {}, {} + ) + cursor = mock_connection.cursor() + cursor._cnx = mock_cursor._cnx + cursor._connection = mock_cursor._connection + cursor.execute("SELECT 1;") + mock_cursor._cnx._cmysql.get_client_info.assert_called_once() + mock_cursor._connection._cmysql.get_client_info.assert_called_once() + mock_logger.error.assert_called_once() + self.assertEqual( + db_integration.commenter_data["mysql_client_version"], "unknown" + ) + # pylint: disable=unused-argument def mock_connect(*args, **kwargs): From ff9f01cea9330e3f50a3f6df05d865e091761451 Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Tue, 2 Sep 2025 12:56:17 -0700 Subject: [PATCH 2/2] Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01a840094e..06e51270f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#3507](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3507)) - Fix documentation order of sections and headers for Django, Flask, MySQL, mysqlclient, psycopg, psycopg2, pymysql, sqlalchemy instrumentations. ([#3719](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3719)) +- `opentelemetry-instrumentation-dbapi`: Fix sqlcomment calculation of mysql_client_version field if connection reassignment, with "unknown" fallback + ([#3729](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3729)) ### Added