Skip to content

Commit 0b3ce6f

Browse files
tammy-baylis-swixrmx
authored andcommitted
DB drivers: db.statement inclusion of sqlcomment as opt-in (open-telemetry#3121)
* db-api opt-in for enable_attribute_commenter * Refactor db-api traced_execution * Changelog * Update comment * psycopg(2), mysqlclient, pymysql support enable_attribute_commenter * Changelog
1 parent 16ac4f2 commit 0b3ce6f

File tree

7 files changed

+338
-0
lines changed

7 files changed

+338
-0
lines changed

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5656
([#3115](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3115))
5757

5858

59+
### Breaking changes
60+
61+
- `opentelemetry-instrumentation-dbapi` including sqlcomment in `db.statement` span attribute value is now opt-in
62+
([#3115](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3115))
63+
- `opentelemetry-instrumentation-psycopg2`, `opentelemetry-instrumentation-psycopg`, `opentelemetry-instrumentation-mysqlclient`, `opentelemetry-instrumentation-pymysql`: including sqlcomment in `db.statement` span attribute value is now opt-in
64+
([#3121](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3121))
65+
66+
5967
## Version 1.29.0/0.50b0 (2024-12-11)
6068

6169
### Added

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

+26
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,26 @@
126126
::
127127
Enabling this flag will add traceparent values /*traceparent='00-03afa25236b8cd948fa853d67038ac79-405ff022e8247c46-01'*/
128128
129+
SQLComment in span attribute
130+
****************************
131+
If sqlcommenter is enabled, you can optionally configure MySQLClient instrumentation to append sqlcomment to query span attribute for convenience of your platform.
132+
133+
.. code:: python
134+
135+
from opentelemetry.instrumentation.mysqlclient import MySQLClientInstrumentor
136+
137+
MySQLClientInstrumentor().instrument(
138+
enable_commenter=True,
139+
enable_attribute_commenter=True,
140+
)
141+
142+
143+
For example,
144+
::
145+
146+
Invoking cursor.execute("select * from auth_users") will lead to sql query "select * from auth_users" but when SQLCommenter and attribute_commenter are enabled
147+
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.
148+
129149
API
130150
---
131151
"""
@@ -159,6 +179,9 @@ def _instrument(self, **kwargs): # pylint: disable=no-self-use
159179
tracer_provider = kwargs.get("tracer_provider")
160180
enable_sqlcommenter = kwargs.get("enable_commenter", False)
161181
commenter_options = kwargs.get("commenter_options", {})
182+
enable_attribute_commenter = kwargs.get(
183+
"enable_attribute_commenter", False
184+
)
162185

163186
dbapi.wrap_connect(
164187
__name__,
@@ -170,6 +193,7 @@ def _instrument(self, **kwargs): # pylint: disable=no-self-use
170193
tracer_provider=tracer_provider,
171194
enable_commenter=enable_sqlcommenter,
172195
commenter_options=commenter_options,
196+
enable_attribute_commenter=enable_attribute_commenter,
173197
)
174198

175199
def _uninstrument(self, **kwargs): # pylint: disable=no-self-use
@@ -182,6 +206,7 @@ def instrument_connection(
182206
tracer_provider=None,
183207
enable_commenter=None,
184208
commenter_options=None,
209+
enable_attribute_commenter=None,
185210
):
186211
"""Enable instrumentation in a mysqlclient connection.
187212
@@ -220,6 +245,7 @@ def instrument_connection(
220245
enable_commenter=enable_commenter,
221246
commenter_options=commenter_options,
222247
connect_module=MySQLdb,
248+
enable_attribute_commenter=enable_attribute_commenter,
223249
)
224250

225251
@staticmethod

instrumentation/opentelemetry-instrumentation-mysqlclient/tests/test_mysqlclient_integration.py

+116
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import opentelemetry.instrumentation.mysqlclient
2020
from opentelemetry.instrumentation.mysqlclient import MySQLClientInstrumentor
2121
from opentelemetry.sdk import resources
22+
from opentelemetry.semconv.trace import SpanAttributes
2223
from opentelemetry.test.test_base import TestBase
2324

2425

@@ -110,12 +111,14 @@ def test_instrument_connection_enable_commenter_dbapi_kwargs(
110111
cnx,
111112
enable_commenter=True,
112113
commenter_options={"foo": True},
114+
enable_attribute_commenter=True,
113115
)
114116
cursor = cnx.cursor()
115117
cursor.execute("Select 1;")
116118
kwargs = mock_instrument_connection.call_args[1]
117119
self.assertEqual(kwargs["enable_commenter"], True)
118120
self.assertEqual(kwargs["commenter_options"], {"foo": True})
121+
self.assertEqual(kwargs["enable_attribute_commenter"], True)
119122

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

154202
def test_instrument_connection_with_dbapi_sqlcomment_enabled_with_options(
155203
self,
@@ -191,6 +239,10 @@ def test_instrument_connection_with_dbapi_sqlcomment_enabled_with_options(
191239
mock_cursor.execute.call_args[0][0],
192240
f"Select 1 /*db_driver='MySQLdb%%3Afoobar',dbapi_threadsafety='123',mysql_client_version='foobaz',traceparent='00-{trace_id}-{span_id}-01'*/;",
193241
)
242+
self.assertEqual(
243+
span.attributes[SpanAttributes.DB_STATEMENT],
244+
"Select 1;",
245+
)
194246

195247
def test_instrument_connection_with_dbapi_sqlcomment_not_enabled_default(
196248
self,
@@ -221,6 +273,12 @@ def test_instrument_connection_with_dbapi_sqlcomment_not_enabled_default(
221273
mock_cursor.execute.call_args[0][0],
222274
"Select 1;",
223275
)
276+
spans_list = self.memory_exporter.get_finished_spans()
277+
span = spans_list[0]
278+
self.assertEqual(
279+
span.attributes[SpanAttributes.DB_STATEMENT],
280+
"Select 1;",
281+
)
224282

225283
@mock.patch("opentelemetry.instrumentation.dbapi.wrap_connect")
226284
@mock.patch("MySQLdb.connect")
@@ -233,10 +291,12 @@ def test_instrument_enable_commenter_dbapi_kwargs(
233291
MySQLClientInstrumentor()._instrument(
234292
enable_commenter=True,
235293
commenter_options={"foo": True},
294+
enable_attribute_commenter=True,
236295
)
237296
kwargs = mock_wrap_connect.call_args[1]
238297
self.assertEqual(kwargs["enable_commenter"], True)
239298
self.assertEqual(kwargs["commenter_options"], {"foo": True})
299+
self.assertEqual(kwargs["enable_attribute_commenter"], True)
240300

241301
def test_instrument_with_dbapi_sqlcomment_enabled(
242302
self,
@@ -274,6 +334,52 @@ def test_instrument_with_dbapi_sqlcomment_enabled(
274334
mock_cursor.execute.call_args[0][0],
275335
f"Select 1 /*db_driver='MySQLdb%%3Afoobar',dbapi_level='123',dbapi_threadsafety='123',driver_paramstyle='test',mysql_client_version='foobaz',traceparent='00-{trace_id}-{span_id}-01'*/;",
276336
)
337+
self.assertEqual(
338+
span.attributes[SpanAttributes.DB_STATEMENT],
339+
"Select 1;",
340+
)
341+
342+
def test_instrument_with_dbapi_sqlcomment_enabled_stmt_enabled(
343+
self,
344+
):
345+
mock_connect_module = mock.MagicMock(
346+
__name__="MySQLdb",
347+
threadsafety="123",
348+
apilevel="123",
349+
paramstyle="test",
350+
)
351+
mock_connect_module._mysql.get_client_info.return_value = "foobaz"
352+
mock_cursor = mock_connect_module.connect().cursor()
353+
mock_connection = mock.MagicMock()
354+
mock_connection.cursor.return_value = mock_cursor
355+
356+
with mock.patch(
357+
"opentelemetry.instrumentation.mysqlclient.MySQLdb",
358+
mock_connect_module,
359+
), mock.patch(
360+
"opentelemetry.instrumentation.dbapi.util_version",
361+
return_value="foobar",
362+
):
363+
MySQLClientInstrumentor()._instrument(
364+
enable_commenter=True,
365+
enable_attribute_commenter=True,
366+
)
367+
cnx = mock_connect_module.connect(database="test")
368+
cursor = cnx.cursor()
369+
cursor.execute("Select 1;")
370+
371+
spans_list = self.memory_exporter.get_finished_spans()
372+
span = spans_list[0]
373+
span_id = format(span.get_span_context().span_id, "016x")
374+
trace_id = format(span.get_span_context().trace_id, "032x")
375+
self.assertEqual(
376+
mock_cursor.execute.call_args[0][0],
377+
f"Select 1 /*db_driver='MySQLdb%%3Afoobar',dbapi_level='123',dbapi_threadsafety='123',driver_paramstyle='test',mysql_client_version='foobaz',traceparent='00-{trace_id}-{span_id}-01'*/;",
378+
)
379+
self.assertEqual(
380+
span.attributes[SpanAttributes.DB_STATEMENT],
381+
f"Select 1 /*db_driver='MySQLdb%%3Afoobar',dbapi_level='123',dbapi_threadsafety='123',driver_paramstyle='test',mysql_client_version='foobaz',traceparent='00-{trace_id}-{span_id}-01'*/;",
382+
)
277383

278384
def test_instrument_with_dbapi_sqlcomment_enabled_with_options(
279385
self,
@@ -316,6 +422,10 @@ def test_instrument_with_dbapi_sqlcomment_enabled_with_options(
316422
mock_cursor.execute.call_args[0][0],
317423
f"Select 1 /*db_driver='MySQLdb%%3Afoobar',dbapi_threadsafety='123',mysql_client_version='foobaz',traceparent='00-{trace_id}-{span_id}-01'*/;",
318424
)
425+
self.assertEqual(
426+
span.attributes[SpanAttributes.DB_STATEMENT],
427+
"Select 1;",
428+
)
319429

320430
def test_instrument_with_dbapi_sqlcomment_not_enabled_default(
321431
self,
@@ -346,6 +456,12 @@ def test_instrument_with_dbapi_sqlcomment_not_enabled_default(
346456
mock_cursor.execute.call_args[0][0],
347457
"Select 1;",
348458
)
459+
spans_list = self.memory_exporter.get_finished_spans()
460+
span = spans_list[0]
461+
self.assertEqual(
462+
span.attributes[SpanAttributes.DB_STATEMENT],
463+
"Select 1;",
464+
)
349465

350466
@mock.patch("MySQLdb.connect")
351467
# pylint: disable=unused-argument

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

+26
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,26 @@
8080
::
8181
Enabling this flag will add traceparent values /*traceparent='00-03afa25236b8cd948fa853d67038ac79-405ff022e8247c46-01'*/
8282
83+
SQLComment in span attribute
84+
****************************
85+
If sqlcommenter is enabled, you can optionally configure psycopg instrumentation to append sqlcomment to query span attribute for convenience of your platform.
86+
87+
.. code:: python
88+
89+
from opentelemetry.instrumentation.psycopg import PsycopgInstrumentor
90+
91+
PsycopgInstrumentor().instrument(
92+
enable_commenter=True,
93+
enable_attribute_commenter=True,
94+
)
95+
96+
97+
For example,
98+
::
99+
100+
Invoking cursor.execute("select * from auth_users") will lead to postgresql query "select * from auth_users" but when SQLCommenter and attribute_commenter are enabled
101+
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.
102+
83103
Usage
84104
-----
85105
@@ -159,6 +179,9 @@ def _instrument(self, **kwargs):
159179
tracer_provider = kwargs.get("tracer_provider")
160180
enable_sqlcommenter = kwargs.get("enable_commenter", False)
161181
commenter_options = kwargs.get("commenter_options", {})
182+
enable_attribute_commenter = kwargs.get(
183+
"enable_attribute_commenter", False
184+
)
162185
dbapi.wrap_connect(
163186
__name__,
164187
psycopg,
@@ -170,6 +193,7 @@ def _instrument(self, **kwargs):
170193
db_api_integration_factory=DatabaseApiIntegration,
171194
enable_commenter=enable_sqlcommenter,
172195
commenter_options=commenter_options,
196+
enable_attribute_commenter=enable_attribute_commenter,
173197
)
174198

175199
dbapi.wrap_connect(
@@ -183,6 +207,7 @@ def _instrument(self, **kwargs):
183207
db_api_integration_factory=DatabaseApiIntegration,
184208
enable_commenter=enable_sqlcommenter,
185209
commenter_options=commenter_options,
210+
enable_attribute_commenter=enable_attribute_commenter,
186211
)
187212
dbapi.wrap_connect(
188213
__name__,
@@ -195,6 +220,7 @@ def _instrument(self, **kwargs):
195220
db_api_integration_factory=DatabaseApiAsyncIntegration,
196221
enable_commenter=enable_sqlcommenter,
197222
commenter_options=commenter_options,
223+
enable_attribute_commenter=enable_attribute_commenter,
198224
)
199225

200226
def _uninstrument(self, **kwargs):

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

+24
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,26 @@
8080
::
8181
Enabling this flag will add traceparent values /*traceparent='00-03afa25236b8cd948fa853d67038ac79-405ff022e8247c46-01'*/
8282
83+
SQLComment in span attribute
84+
****************************
85+
If sqlcommenter is enabled, you can optionally configure psycopg2 instrumentation to append sqlcomment to query span attribute for convenience of your platform.
86+
87+
.. code:: python
88+
89+
from opentelemetry.instrumentation.psycopg2 import Psycopg2Instrumentor
90+
91+
Psycopg2Instrumentor().instrument(
92+
enable_commenter=True,
93+
enable_attribute_commenter=True,
94+
)
95+
96+
97+
For example,
98+
::
99+
100+
Invoking cursor.execute("select * from auth_users") will lead to postgresql query "select * from auth_users" but when SQLCommenter and attribute_commenter are enabled
101+
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.
102+
83103
Usage
84104
-----
85105
@@ -156,6 +176,9 @@ def _instrument(self, **kwargs):
156176
tracer_provider = kwargs.get("tracer_provider")
157177
enable_sqlcommenter = kwargs.get("enable_commenter", False)
158178
commenter_options = kwargs.get("commenter_options", {})
179+
enable_attribute_commenter = kwargs.get(
180+
"enable_attribute_commenter", False
181+
)
159182
dbapi.wrap_connect(
160183
__name__,
161184
psycopg2,
@@ -167,6 +190,7 @@ def _instrument(self, **kwargs):
167190
db_api_integration_factory=DatabaseApiIntegration,
168191
enable_commenter=enable_sqlcommenter,
169192
commenter_options=commenter_options,
193+
enable_attribute_commenter=enable_attribute_commenter,
170194
)
171195

172196
def _uninstrument(self, **kwargs):

0 commit comments

Comments
 (0)