From 3a3a2000d6edeceb270c42e90af03e94ecee2980 Mon Sep 17 00:00:00 2001 From: Mergepath Date: Sun, 2 Aug 2026 05:58:13 +0000 Subject: [PATCH] fix: address #4789 - `opentelemetry-instrumentation-mysql`: expose `capture_parameters` Closes #4789 --- .../instrumentation/mysql/__init__.py | 17 +++++ .../tests/test_mysql_integration.py | 68 +++++++++++++++++++ 2 files changed, 85 insertions(+) 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 8de6402aeb..0186592b74 100644 --- a/instrumentation/opentelemetry-instrumentation-mysql/src/opentelemetry/instrumentation/mysql/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-mysql/src/opentelemetry/instrumentation/mysql/__init__.py @@ -134,6 +134,17 @@ Warning: 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 +****************** +You can optionally enable capture of query parameters in the +``db.statement.parameters`` span attribute. + +.. code:: python + + from opentelemetry.instrumentation.mysql import MySQLInstrumentor + + MySQLInstrumentor().instrument(capture_parameters=True) + API --- """ @@ -171,6 +182,7 @@ def _instrument(self, **kwargs): enable_attribute_commenter = kwargs.get( "enable_attribute_commenter", False ) + capture_parameters = kwargs.get("capture_parameters", False) dbapi.wrap_connect( __name__, @@ -183,6 +195,7 @@ def _instrument(self, **kwargs): enable_commenter=enable_sqlcommenter, commenter_options=commenter_options, enable_attribute_commenter=enable_attribute_commenter, + capture_parameters=capture_parameters, ) def _uninstrument(self, **kwargs): @@ -197,6 +210,7 @@ def instrument_connection( enable_commenter=None, commenter_options=None, enable_attribute_commenter=None, + capture_parameters=False, ): """Enable instrumentation in a MySQL connection. @@ -214,6 +228,8 @@ def instrument_connection( Optional configurations for tags to be appended at the sql query. enable_attribute_commenter: Optional flag to enable/disable addition of sqlcomment to span attribute (default False). Requires enable_commenter=True. + capture_parameters: + Optional flag to enable/disable capture of query parameters (default False). Returns: An instrumented MySQL connection with OpenTelemetry tracing enabled. @@ -229,6 +245,7 @@ def instrument_connection( commenter_options=commenter_options, connect_module=mysql.connector, enable_attribute_commenter=enable_attribute_commenter, + capture_parameters=capture_parameters, ) def uninstrument_connection(self, connection): diff --git a/instrumentation/opentelemetry-instrumentation-mysql/tests/test_mysql_integration.py b/instrumentation/opentelemetry-instrumentation-mysql/tests/test_mysql_integration.py index 1ec5461fde..1f2ee6d999 100644 --- a/instrumentation/opentelemetry-instrumentation-mysql/tests/test_mysql_integration.py +++ b/instrumentation/opentelemetry-instrumentation-mysql/tests/test_mysql_integration.py @@ -193,6 +193,7 @@ def test_instrument_connection_enable_commenter_dbapi_kwargs( enable_commenter=True, commenter_options={"foo": True}, enable_attribute_commenter=True, + capture_parameters=True, ) cursor = cnx.cursor() cursor.execute("SELECT * FROM test") @@ -200,6 +201,20 @@ def test_instrument_connection_enable_commenter_dbapi_kwargs( self.assertEqual(kwargs["enable_commenter"], True) self.assertEqual(kwargs["commenter_options"], {"foo": True}) self.assertEqual(kwargs["enable_attribute_commenter"], True) + self.assertEqual(kwargs["capture_parameters"], True) + + @mock.patch("opentelemetry.instrumentation.dbapi.instrument_connection") + @mock.patch("mysql.connector") + # pylint: disable=unused-argument + def test_instrument_connection_defaults_capture_parameters_dbapi_kwargs( + self, + mock_connect, + mock_instrument_connection, + ): + cnx = mysql.connector.connect(database="test") + MySQLInstrumentor().instrument_connection(cnx) + kwargs = mock_instrument_connection.call_args[1] + self.assertEqual(kwargs["capture_parameters"], False) def test_instrument_connection_with_dbapi_sqlcomment_enabled(self): mock_connect_module, mock_connection, mock_cursor = ( @@ -324,6 +339,26 @@ def test_instrument_connection_with_dbapi_sqlcomment_not_enabled_default( "Select 1;", ) + def test_instrument_connection_with_capture_parameters(self): + mock_connect_module, mock_connection, _ = make_mysql_commenter_mocks() + + with mock.patch( + "opentelemetry.instrumentation.mysql.mysql.connector", + mock_connect_module, + ): + cnx_proxy = MySQLInstrumentor().instrument_connection( + mock_connection, + capture_parameters=True, + ) + cnx_proxy.cursor().execute("Select %s;", ("param1Value", False)) + + spans_list = self.memory_exporter.get_finished_spans() + span = spans_list[0] + self.assertEqual( + span.attributes["db.statement.parameters"], + "('param1Value', False)", + ) + @mock.patch("opentelemetry.instrumentation.dbapi.wrap_connect") @mock.patch("mysql.connector") # pylint: disable=unused-argument @@ -336,11 +371,25 @@ def test_instrument_enable_commenter_dbapi_kwargs( enable_commenter=True, commenter_options={"foo": True}, enable_attribute_commenter=True, + capture_parameters=True, ) kwargs = mock_wrap_connect.call_args[1] self.assertEqual(kwargs["enable_commenter"], True) self.assertEqual(kwargs["commenter_options"], {"foo": True}) self.assertEqual(kwargs["enable_attribute_commenter"], True) + self.assertEqual(kwargs["capture_parameters"], True) + + @mock.patch("opentelemetry.instrumentation.dbapi.wrap_connect") + @mock.patch("mysql.connector") + # pylint: disable=unused-argument + def test_instrument_defaults_capture_parameters_dbapi_kwargs( + self, + mock_connect, + mock_wrap_connect, + ): + MySQLInstrumentor().instrument() + kwargs = mock_wrap_connect.call_args[1] + self.assertEqual(kwargs["capture_parameters"], False) def test_instrument_with_dbapi_sqlcomment_enabled( self, @@ -462,6 +511,25 @@ def test_instrument_with_dbapi_sqlcomment_not_enabled_default( "Select 1;", ) + def test_instrument_with_capture_parameters(self): + mock_connect_module, _, _ = make_mysql_commenter_mocks() + + with mock.patch( + "opentelemetry.instrumentation.mysql.mysql.connector", + mock_connect_module, + ): + MySQLInstrumentor()._instrument(capture_parameters=True) + cnx = mock_connect_module.connect(database="test") + cursor = cnx.cursor() + cursor.execute("Select %s;", ("param1Value", False)) + + spans_list = self.memory_exporter.get_finished_spans() + span = spans_list[0] + self.assertEqual( + span.attributes["db.statement.parameters"], + "('param1Value', False)", + ) + @mock.patch("mysql.connector.connect") # pylint: disable=unused-argument def test_uninstrument_connection(self, mock_connect):