Skip to content

Commit d2c7076

Browse files
mysql support enable_attribute_commenter
1 parent f7e9540 commit d2c7076

File tree

2 files changed

+141
-0
lines changed

2 files changed

+141
-0
lines changed

instrumentation/opentelemetry-instrumentation-mysql/src/opentelemetry/instrumentation/mysql/__init__.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,26 @@
119119
::
120120
Enabling this flag will add traceparent values /*traceparent='00-03afa25236b8cd948fa853d67038ac79-405ff022e8247c46-01'*/
121121
122+
SQLComment in span attribute
123+
****************************
124+
If sqlcommenter is enabled, you can optionally configure mysql-connector instrumentation to append sqlcomment to query span attribute for convenience of your platform.
125+
126+
.. code:: python
127+
128+
from opentelemetry.instrumentation.mysql import MySQLInstrumentor
129+
130+
MySQLInstrumentor().instrument(
131+
enable_commenter=True,
132+
enable_attribute_commenter=True,
133+
)
134+
135+
136+
For example,
137+
::
138+
139+
Invoking cursor.execute("select * from auth_users") will lead to sql query "select * from auth_users" but when SQLCommenter and attribute_commenter are enabled
140+
the query will get appended with some configurable tags like "select * from auth_users /*tag=value*/;" for both server query and `db.statement` span attribute.
141+
122142
API
123143
---
124144
"""
@@ -153,6 +173,9 @@ def _instrument(self, **kwargs):
153173
tracer_provider = kwargs.get("tracer_provider")
154174
enable_sqlcommenter = kwargs.get("enable_commenter", False)
155175
commenter_options = kwargs.get("commenter_options", {})
176+
enable_attribute_commenter = kwargs.get(
177+
"enable_attribute_commenter", False
178+
)
156179

157180
dbapi.wrap_connect(
158181
__name__,
@@ -164,6 +187,7 @@ def _instrument(self, **kwargs):
164187
tracer_provider=tracer_provider,
165188
enable_commenter=enable_sqlcommenter,
166189
commenter_options=commenter_options,
190+
enable_attribute_commenter=enable_attribute_commenter,
167191
)
168192

169193
def _uninstrument(self, **kwargs):
@@ -177,6 +201,7 @@ def instrument_connection(
177201
tracer_provider=None,
178202
enable_commenter=None,
179203
commenter_options=None,
204+
enable_attribute_commenter=None,
180205
):
181206
"""Enable instrumentation in a MySQL connection.
182207
@@ -192,6 +217,8 @@ def instrument_connection(
192217
Optional flag to enable/disable sqlcommenter (default False).
193218
commenter_options:
194219
Optional configurations for tags to be appended at the sql query.
220+
enable_attribute_commenter:
221+
Optional flag to enable/disable addition of sqlcomment to span attribute (default False). Requires enable_commenter=True.
195222
196223
Returns:
197224
An instrumented MySQL connection with OpenTelemetry tracing enabled.
@@ -206,6 +233,7 @@ def instrument_connection(
206233
enable_commenter=enable_commenter,
207234
commenter_options=commenter_options,
208235
connect_module=mysql.connector,
236+
enable_attribute_commenter=enable_attribute_commenter,
209237
)
210238

211239
def uninstrument_connection(self, connection):

instrumentation/opentelemetry-instrumentation-mysql/tests/test_mysql_integration.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from opentelemetry import trace as trace_api
2121
from opentelemetry.instrumentation.mysql import MySQLInstrumentor
2222
from opentelemetry.sdk import resources
23+
from opentelemetry.semconv.trace import SpanAttributes
2324
from opentelemetry.test.test_base import TestBase
2425

2526

@@ -115,12 +116,14 @@ def test_instrument_connection_enable_commenter_dbapi_kwargs(
115116
cnx,
116117
enable_commenter=True,
117118
commenter_options={"foo": True},
119+
enable_attribute_commenter=True,
118120
)
119121
cursor = cnx.cursor()
120122
cursor.execute("SELECT * FROM test")
121123
kwargs = mock_instrument_connection.call_args[1]
122124
self.assertEqual(kwargs["enable_commenter"], True)
123125
self.assertEqual(kwargs["commenter_options"], {"foo": True})
126+
self.assertEqual(kwargs["enable_attribute_commenter"], True)
124127

125128
def test_instrument_connection_with_dbapi_sqlcomment_enabled(self):
126129
mock_connect_module = mock.MagicMock(
@@ -153,6 +156,49 @@ def test_instrument_connection_with_dbapi_sqlcomment_enabled(self):
153156
mock_cursor.execute.call_args[0][0],
154157
f"Select 1 /*db_driver='mysql.connector%%3Afoobar',dbapi_level='123',dbapi_threadsafety='123',driver_paramstyle='test',mysql_client_version='foobaz',traceparent='00-{trace_id}-{span_id}-01'*/;",
155158
)
159+
self.assertEqual(
160+
span.attributes[SpanAttributes.DB_STATEMENT],
161+
"Select 1;",
162+
)
163+
164+
def test_instrument_connection_with_dbapi_sqlcomment_enabled_stmt_enabled(
165+
self,
166+
):
167+
mock_connect_module = mock.MagicMock(
168+
__name__="mysql.connector",
169+
__version__="foobar",
170+
threadsafety="123",
171+
apilevel="123",
172+
paramstyle="test",
173+
)
174+
mock_cursor = mock_connect_module.connect().cursor()
175+
mock_cursor._cnx._cmysql.get_client_info.return_value = "foobaz"
176+
mock_connection = mock.MagicMock()
177+
mock_connection.cursor.return_value = mock_cursor
178+
179+
with mock.patch(
180+
"opentelemetry.instrumentation.mysql.mysql.connector",
181+
mock_connect_module,
182+
):
183+
cnx_proxy = MySQLInstrumentor().instrument_connection(
184+
mock_connection,
185+
enable_commenter=True,
186+
enable_attribute_commenter=True,
187+
)
188+
cnx_proxy.cursor().execute("Select 1;")
189+
190+
spans_list = self.memory_exporter.get_finished_spans()
191+
span = spans_list[0]
192+
span_id = format(span.get_span_context().span_id, "016x")
193+
trace_id = format(span.get_span_context().trace_id, "032x")
194+
self.assertEqual(
195+
mock_cursor.execute.call_args[0][0],
196+
f"Select 1 /*db_driver='mysql.connector%%3Afoobar',dbapi_level='123',dbapi_threadsafety='123',driver_paramstyle='test',mysql_client_version='foobaz',traceparent='00-{trace_id}-{span_id}-01'*/;",
197+
)
198+
self.assertEqual(
199+
span.attributes[SpanAttributes.DB_STATEMENT],
200+
f"Select 1 /*db_driver='mysql.connector%%3Afoobar',dbapi_level='123',dbapi_threadsafety='123',driver_paramstyle='test',mysql_client_version='foobaz',traceparent='00-{trace_id}-{span_id}-01'*/;",
201+
)
156202

157203
def test_instrument_connection_with_dbapi_sqlcomment_enabled_with_options(
158204
self,
@@ -192,6 +238,10 @@ def test_instrument_connection_with_dbapi_sqlcomment_enabled_with_options(
192238
mock_cursor.execute.call_args[0][0],
193239
f"Select 1 /*db_driver='mysql.connector%%3Afoobar',dbapi_threadsafety='123',mysql_client_version='foobaz',traceparent='00-{trace_id}-{span_id}-01'*/;",
194240
)
241+
self.assertEqual(
242+
span.attributes[SpanAttributes.DB_STATEMENT],
243+
"Select 1;",
244+
)
195245

196246
def test_instrument_connection_with_dbapi_sqlcomment_not_enabled_default(
197247
self,
@@ -221,6 +271,12 @@ def test_instrument_connection_with_dbapi_sqlcomment_not_enabled_default(
221271
mock_cursor.execute.call_args[0][0],
222272
"Select 1;",
223273
)
274+
spans_list = self.memory_exporter.get_finished_spans()
275+
span = spans_list[0]
276+
self.assertEqual(
277+
span.attributes[SpanAttributes.DB_STATEMENT],
278+
"Select 1;",
279+
)
224280

225281
@mock.patch("opentelemetry.instrumentation.dbapi.wrap_connect")
226282
@mock.patch("mysql.connector")
@@ -233,10 +289,12 @@ def test_instrument_enable_commenter_dbapi_kwargs(
233289
MySQLInstrumentor()._instrument(
234290
enable_commenter=True,
235291
commenter_options={"foo": True},
292+
enable_attribute_commenter=True,
236293
)
237294
kwargs = mock_wrap_connect.call_args[1]
238295
self.assertEqual(kwargs["enable_commenter"], True)
239296
self.assertEqual(kwargs["commenter_options"], {"foo": True})
297+
self.assertEqual(kwargs["enable_attribute_commenter"], True)
240298

241299
def test_instrument_with_dbapi_sqlcomment_enabled(
242300
self,
@@ -273,6 +331,51 @@ def test_instrument_with_dbapi_sqlcomment_enabled(
273331
mock_cursor.execute.call_args[0][0],
274332
f"Select 1 /*db_driver='mysql.connector%%3Afoobar',dbapi_level='123',dbapi_threadsafety='123',driver_paramstyle='test',mysql_client_version='foobaz',traceparent='00-{trace_id}-{span_id}-01'*/;",
275333
)
334+
self.assertEqual(
335+
span.attributes[SpanAttributes.DB_STATEMENT],
336+
"Select 1;",
337+
)
338+
339+
def test_instrument_with_dbapi_sqlcomment_enabled_stmt_enabled(
340+
self,
341+
):
342+
mock_connect_module = mock.MagicMock(
343+
__name__="mysql.connector",
344+
__version__="foobar",
345+
threadsafety="123",
346+
apilevel="123",
347+
paramstyle="test",
348+
)
349+
mock_cursor = mock_connect_module.connect().cursor()
350+
mock_cursor._cnx._cmysql.get_client_info.return_value = "foobaz"
351+
mock_cursor = mock_connect_module.connect().cursor()
352+
mock_connection = mock.MagicMock()
353+
mock_connection.cursor.return_value = mock_cursor
354+
355+
with mock.patch(
356+
"opentelemetry.instrumentation.mysql.mysql.connector",
357+
mock_connect_module,
358+
):
359+
MySQLInstrumentor()._instrument(
360+
enable_commenter=True,
361+
enable_attribute_commenter=True,
362+
)
363+
cnx = mock_connect_module.connect(database="test")
364+
cursor = cnx.cursor()
365+
cursor.execute("Select 1;")
366+
367+
spans_list = self.memory_exporter.get_finished_spans()
368+
span = spans_list[0]
369+
span_id = format(span.get_span_context().span_id, "016x")
370+
trace_id = format(span.get_span_context().trace_id, "032x")
371+
self.assertEqual(
372+
mock_cursor.execute.call_args[0][0],
373+
f"Select 1 /*db_driver='mysql.connector%%3Afoobar',dbapi_level='123',dbapi_threadsafety='123',driver_paramstyle='test',mysql_client_version='foobaz',traceparent='00-{trace_id}-{span_id}-01'*/;",
374+
)
375+
self.assertEqual(
376+
span.attributes[SpanAttributes.DB_STATEMENT],
377+
f"Select 1 /*db_driver='mysql.connector%%3Afoobar',dbapi_level='123',dbapi_threadsafety='123',driver_paramstyle='test',mysql_client_version='foobaz',traceparent='00-{trace_id}-{span_id}-01'*/;",
378+
)
276379

277380
def test_instrument_with_dbapi_sqlcomment_enabled_with_options(
278381
self,
@@ -314,6 +417,10 @@ def test_instrument_with_dbapi_sqlcomment_enabled_with_options(
314417
mock_cursor.execute.call_args[0][0],
315418
f"Select 1 /*db_driver='mysql.connector%%3Afoobar',dbapi_threadsafety='123',mysql_client_version='foobaz',traceparent='00-{trace_id}-{span_id}-01'*/;",
316419
)
420+
self.assertEqual(
421+
span.attributes[SpanAttributes.DB_STATEMENT],
422+
"Select 1;",
423+
)
317424

318425
def test_instrument_with_dbapi_sqlcomment_not_enabled_default(
319426
self,
@@ -343,6 +450,12 @@ def test_instrument_with_dbapi_sqlcomment_not_enabled_default(
343450
mock_cursor.execute.call_args[0][0],
344451
"Select 1;",
345452
)
453+
spans_list = self.memory_exporter.get_finished_spans()
454+
span = spans_list[0]
455+
self.assertEqual(
456+
span.attributes[SpanAttributes.DB_STATEMENT],
457+
"Select 1;",
458+
)
346459

347460
@mock.patch("mysql.connector.connect")
348461
# pylint: disable=unused-argument

0 commit comments

Comments
 (0)