From 340b6e127a2114e331fa21fe03956f8b0cf9152f Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Tue, 2 Sep 2025 12:53:00 -0700 Subject: [PATCH 01/11] 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 02/11] 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 From e873ea0091e186b3f02fe6534515737d4490bd59 Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Mon, 20 Oct 2025 15:03:27 -0700 Subject: [PATCH 03/11] Add docker test_mysql_sqlcommenter --- .../tests/mysql/test_mysql_sqlcommenter.py | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py diff --git a/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py b/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py new file mode 100644 index 0000000000..ae0e2ad175 --- /dev/null +++ b/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py @@ -0,0 +1,53 @@ +# Copyright 2025, OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +import mysql.connector + +from opentelemetry.instrumentation.mysql import MySQLInstrumentor +from opentelemetry.test.test_base import TestBase + +MYSQL_USER = os.getenv("MYSQL_USER", "testuser") +MYSQL_PASSWORD = os.getenv("MYSQL_PASSWORD", "testpassword") +MYSQL_HOST = os.getenv("MYSQL_HOST", "localhost") +MYSQL_PORT = int(os.getenv("MYSQL_PORT", "3306")) +MYSQL_DB_NAME = os.getenv("MYSQL_DB_NAME", "opentelemetry-tests") + +class TestFunctionalMySqlCommenter(TestBase): + def setUp(self): + super().setUp() + self._tracer = self.tracer_provider.get_tracer(__name__) + MySQLInstrumentor().instrument(enable_commenter=True) + self._connection = mysql.connector.connect( + user=MYSQL_USER, + password=MYSQL_PASSWORD, + host=MYSQL_HOST, + port=MYSQL_PORT, + database=MYSQL_DB_NAME, + ) + self._cursor = self._connection.cursor() + + def tearDown(self): + self._cursor.close() + self._connection.close() + MySQLInstrumentor().uninstrument() + super().tearDown() + + def test_commenter_enabled(self): + self._cursor.execute("SELECT 1;") + self.assertRegex( + self._cursor._query.query.decode("ascii"), + r"SELECT 1 /\*db_driver='mysql.connector(.*)',dbapi_level='\d.\d',dbapi_threadsafety=\d,driver_paramstyle=(.*),mysql_client_version=\d*,traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;", + ) From 7fa0d6065c0e94a79bccfded46ff2cf5d433787f Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Mon, 20 Oct 2025 15:31:38 -0700 Subject: [PATCH 04/11] Update dbapi docker-tests --- .../tests/mysql/test_mysql_sqlcommenter.py | 52 ++++++++++++++----- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py b/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py index ae0e2ad175..a76286c879 100644 --- a/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py +++ b/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py @@ -25,29 +25,55 @@ MYSQL_PORT = int(os.getenv("MYSQL_PORT", "3306")) MYSQL_DB_NAME = os.getenv("MYSQL_DB_NAME", "opentelemetry-tests") + class TestFunctionalMySqlCommenter(TestBase): - def setUp(self): - super().setUp() - self._tracer = self.tracer_provider.get_tracer(__name__) + def test_commenter_enabled_direct_reference(self): MySQLInstrumentor().instrument(enable_commenter=True) - self._connection = mysql.connector.connect( + cnx = mysql.connector.connect( user=MYSQL_USER, password=MYSQL_PASSWORD, host=MYSQL_HOST, port=MYSQL_PORT, database=MYSQL_DB_NAME, ) - self._cursor = self._connection.cursor() + cursor = cnx.cursor() - def tearDown(self): - self._cursor.close() - self._connection.close() + cursor.execute("SELECT 1;") + self.assertRegex( + cursor.statement, + r"SELECT 1 /\*db_driver='mysql\.connector[^']*',dbapi_level='\d\.\d',dbapi_threadsafety=\d,driver_paramstyle='[^']*',mysql_client_version='[^']*',traceparent='[^']*'\*/;", + ) + self.assertRegex( + cursor.statement, r"mysql_client_version='(?!unknown)[^']+" + ) + + cursor.close() + cnx.close() MySQLInstrumentor().uninstrument() - super().tearDown() - def test_commenter_enabled(self): - self._cursor.execute("SELECT 1;") + def test_commenter_enabled_connection_proxy(self): + cnx = mysql.connector.connect( + user=MYSQL_USER, + password=MYSQL_PASSWORD, + host=MYSQL_HOST, + port=MYSQL_PORT, + database=MYSQL_DB_NAME, + ) + instrumented_cnx = MySQLInstrumentor().instrument_connection( + connection=cnx, + enable_commenter=True, + ) + cursor = instrumented_cnx.cursor() + + cursor.execute("SELECT 1;") + self.assertRegex( + cursor.statement, + r"SELECT 1 /\*db_driver='mysql\.connector[^']*',dbapi_level='\d\.\d',dbapi_threadsafety=\d,driver_paramstyle='[^']*',mysql_client_version='[^']*',traceparent='[^']*'\*/;", + ) self.assertRegex( - self._cursor._query.query.decode("ascii"), - r"SELECT 1 /\*db_driver='mysql.connector(.*)',dbapi_level='\d.\d',dbapi_threadsafety=\d,driver_paramstyle=(.*),mysql_client_version=\d*,traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;", + cursor.statement, r"mysql_client_version='(?!unknown)[^']+" ) + + cursor.close() + MySQLInstrumentor().uninstrument_connection(instrumented_cnx) + cnx.close() From d0508f3b07c4399709608ffc3bf14c070f7404de Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Mon, 20 Oct 2025 15:39:47 -0700 Subject: [PATCH 05/11] Add test fetchall --- .../tests/mysql/test_mysql_sqlcommenter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py b/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py index a76286c879..5c669b19e5 100644 --- a/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py +++ b/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py @@ -39,6 +39,7 @@ def test_commenter_enabled_direct_reference(self): cursor = cnx.cursor() cursor.execute("SELECT 1;") + cursor.fetchall() self.assertRegex( cursor.statement, r"SELECT 1 /\*db_driver='mysql\.connector[^']*',dbapi_level='\d\.\d',dbapi_threadsafety=\d,driver_paramstyle='[^']*',mysql_client_version='[^']*',traceparent='[^']*'\*/;", @@ -66,6 +67,7 @@ def test_commenter_enabled_connection_proxy(self): cursor = instrumented_cnx.cursor() cursor.execute("SELECT 1;") + cursor.fetchall() self.assertRegex( cursor.statement, r"SELECT 1 /\*db_driver='mysql\.connector[^']*',dbapi_level='\d\.\d',dbapi_threadsafety=\d,driver_paramstyle='[^']*',mysql_client_version='[^']*',traceparent='[^']*'\*/;", From 5d89aceecbf3575f7c8d29a4c0672b51c47070c4 Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Mon, 20 Oct 2025 15:47:56 -0700 Subject: [PATCH 06/11] Add test case for cmysql unknown --- .../tests/mysql/test_mysql_sqlcommenter.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py b/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py index 5c669b19e5..bdbcc028d3 100644 --- a/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py +++ b/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py @@ -79,3 +79,43 @@ def test_commenter_enabled_connection_proxy(self): cursor.close() MySQLInstrumentor().uninstrument_connection(instrumented_cnx) cnx.close() + + def test_commenter_enabled_unknown_mysql_client_version(self): + MySQLInstrumentor().instrument(enable_commenter=True) + cnx = mysql.connector.connect( + user=MYSQL_USER, + password=MYSQL_PASSWORD, + host=MYSQL_HOST, + port=MYSQL_PORT, + database=MYSQL_DB_NAME, + ) + cursor = cnx.cursor() + + # Temporarily remove _cmysql + original_cmysql = None + try: + if hasattr(cursor._cnx, "_cmysql"): + original_cmysql = cursor._cnx._cmysql + delattr(cursor._cnx, "_cmysql") + except AttributeError: + pass + + cursor.execute("SELECT 1;") + cursor.fetchall() + + self.assertRegex( + cursor.statement, + r"SELECT 1 /\*db_driver='mysql\.connector[^']*',dbapi_level='\d\.\d',dbapi_threadsafety=\d,driver_paramstyle='[^']*',mysql_client_version='unknown',traceparent='[^']*'\*/;", + ) + self.assertIn("mysql_client_version='unknown'", cursor.statement) + + # Restore _cmysql + if original_cmysql is not None: + try: + cursor._cnx._cmysql = original_cmysql + except AttributeError: + pass + + cursor.close() + cnx.close() + MySQLInstrumentor().uninstrument() From d3add813551c9d0dc6b8d06c5a73c9ac3ee52c4d Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Mon, 20 Oct 2025 15:57:33 -0700 Subject: [PATCH 07/11] Change to patch --- .../tests/mysql/test_mysql_sqlcommenter.py | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py b/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py index bdbcc028d3..b0e78296ad 100644 --- a/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py +++ b/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py @@ -13,6 +13,7 @@ # limitations under the License. import os +from unittest.mock import patch import mysql.connector @@ -91,17 +92,10 @@ def test_commenter_enabled_unknown_mysql_client_version(self): ) cursor = cnx.cursor() - # Temporarily remove _cmysql - original_cmysql = None - try: - if hasattr(cursor._cnx, "_cmysql"): - original_cmysql = cursor._cnx._cmysql - delattr(cursor._cnx, "_cmysql") - except AttributeError: - pass - - cursor.execute("SELECT 1;") - cursor.fetchall() + # Mock get_client_info to raise AttributeError + with patch.object(cursor._cnx._cmysql, 'get_client_info', side_effect=AttributeError("Mocked error")): + cursor.execute("SELECT 1;") + cursor.fetchall() self.assertRegex( cursor.statement, @@ -109,13 +103,6 @@ def test_commenter_enabled_unknown_mysql_client_version(self): ) self.assertIn("mysql_client_version='unknown'", cursor.statement) - # Restore _cmysql - if original_cmysql is not None: - try: - cursor._cnx._cmysql = original_cmysql - except AttributeError: - pass - cursor.close() cnx.close() MySQLInstrumentor().uninstrument() From a9642ae23affc7af2c13775b9391203c00d6809d Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Mon, 20 Oct 2025 16:12:19 -0700 Subject: [PATCH 08/11] Rm test attempt --- .../tests/mysql/test_mysql_sqlcommenter.py | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py b/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py index b0e78296ad..5c669b19e5 100644 --- a/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py +++ b/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py @@ -13,7 +13,6 @@ # limitations under the License. import os -from unittest.mock import patch import mysql.connector @@ -80,29 +79,3 @@ def test_commenter_enabled_connection_proxy(self): cursor.close() MySQLInstrumentor().uninstrument_connection(instrumented_cnx) cnx.close() - - def test_commenter_enabled_unknown_mysql_client_version(self): - MySQLInstrumentor().instrument(enable_commenter=True) - cnx = mysql.connector.connect( - user=MYSQL_USER, - password=MYSQL_PASSWORD, - host=MYSQL_HOST, - port=MYSQL_PORT, - database=MYSQL_DB_NAME, - ) - cursor = cnx.cursor() - - # Mock get_client_info to raise AttributeError - with patch.object(cursor._cnx._cmysql, 'get_client_info', side_effect=AttributeError("Mocked error")): - cursor.execute("SELECT 1;") - cursor.fetchall() - - self.assertRegex( - cursor.statement, - r"SELECT 1 /\*db_driver='mysql\.connector[^']*',dbapi_level='\d\.\d',dbapi_threadsafety=\d,driver_paramstyle='[^']*',mysql_client_version='unknown',traceparent='[^']*'\*/;", - ) - self.assertIn("mysql_client_version='unknown'", cursor.statement) - - cursor.close() - cnx.close() - MySQLInstrumentor().uninstrument() From 961c5fe12afd64e71d002340d78781c031871f8a Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Thu, 15 Jan 2026 15:33:17 -0800 Subject: [PATCH 09/11] Changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19c6a96a79..7bae6384a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,6 +76,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#4081](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4081)) - `opentelemetry-instrumentation-system-metrics`: Use proper numeric `cpython.gc.generation` attribute in CPython metrics, out of spec `generation` attribute is deprecated and will be removed in the future ([#4092](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4092)) +- `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)) ### Breaking changes @@ -201,8 +203,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#3615](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3615)) - `opentelemetry-instrumentation-fastapi`: Don't pass bounded server_request_hook when using `FastAPIInstrumentor.instrument()` ([#3701](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3701)) -- `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 From a8ecf3edb383ecaca1d1ae8e8b5bb60f6b8e0723 Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Wed, 21 Jan 2026 17:14:00 -0800 Subject: [PATCH 10/11] simplify commenter_data assn --- .../instrumentation/dbapi/__init__.py | 19 +++++++++---------- .../tests/test_dbapi_integration.py | 2 +- 2 files changed, 10 insertions(+), 11 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 d63e534252..cd154616a6 100644 --- a/instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/__init__.py @@ -634,22 +634,21 @@ def _capture_mysql_version(self, cursor) -> None: ): try: # Autoinstrumentation and some programmatic calls - self._db_api_integration.commenter_data[ - "mysql_client_version" - ] = cursor._cnx._cmysql.get_client_info() + 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() + client_version = ( + cursor._connection._cmysql.get_client_info() + ) except AttributeError as exc: - _logger.error( + _logger.debug( "Could not set mysql_client_version: %s", exc ) - self._db_api_integration.commenter_data[ - "mysql_client_version" - ] = "unknown" + client_version = "unknown" + self._db_api_integration.commenter_data["mysql_client_version"] = ( + client_version + ) 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 fa883759ef..f9fb9c952b 100644 --- a/instrumentation/opentelemetry-instrumentation-dbapi/tests/test_dbapi_integration.py +++ b/instrumentation/opentelemetry-instrumentation-dbapi/tests/test_dbapi_integration.py @@ -1258,7 +1258,7 @@ def test_capture_mysql_version_fallback(self, mock_logger): 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() + mock_logger.debug.assert_called_once() self.assertEqual( db_integration.commenter_data["mysql_client_version"], "unknown" ) From 32496472006606b9352bac65da0d50c80efe290f Mon Sep 17 00:00:00 2001 From: tammy-baylis-swi Date: Wed, 21 Jan 2026 17:53:16 -0800 Subject: [PATCH 11/11] Add Pure Python mysql_client_version unknown test --- .../tests/mysql/test_mysql_sqlcommenter.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py b/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py index 5c669b19e5..d1e16993c9 100644 --- a/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py +++ b/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_sqlcommenter.py @@ -79,3 +79,26 @@ def test_commenter_enabled_connection_proxy(self): cursor.close() MySQLInstrumentor().uninstrument_connection(instrumented_cnx) cnx.close() + + def test_commenter_mysql_client_version_fallback_to_unknown(self): + """Test that mysql_client_version falls back to 'unknown' with pure Python implementation""" + MySQLInstrumentor().instrument(enable_commenter=True) + cnx = mysql.connector.connect( + user=MYSQL_USER, + password=MYSQL_PASSWORD, + host=MYSQL_HOST, + port=MYSQL_PORT, + database=MYSQL_DB_NAME, + use_pure=True, # Use pure Python - no _cmysql module + ) + cursor = cnx.cursor() + + cursor.execute("SELECT 1;") + cursor.fetchall() + + # With use_pure=True, _cmysql is not available, should fall back to 'unknown' + self.assertRegex(cursor.statement, r"mysql_client_version='unknown'") + + cursor.close() + cnx.close() + MySQLInstrumentor().uninstrument()