Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit bff2a7f

Browse files
tammy-baylis-swixrmx
authored andcommittedJan 24, 2025
DB-API: db.statement inclusion of sqlcomment as opt-in (open-telemetry#3115)
1 parent 54d6e6d commit bff2a7f

File tree

3 files changed

+479
-45
lines changed

3 files changed

+479
-45
lines changed
 

Diff for: ‎CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4242
- `opentelemetry-instrumentation-sqlalchemy` including sqlcomment in `db.statement` span attribute value is now opt-in
4343
([#3112](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3112))
4444

45+
### Breaking changes
46+
47+
- `opentelemetry-instrumentation-dbapi` including sqlcomment in `db.statement` span attribute value is now opt-in
48+
([#3115](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3115))
49+
50+
4551
## Version 1.29.0/0.50b0 (2024-12-11)
4652

4753
### Added

Diff for: ‎instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/__init__.py

+68-44
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ def trace_integration(
7171
capture_parameters: bool = False,
7272
enable_commenter: bool = False,
7373
db_api_integration_factory=None,
74+
enable_attribute_commenter: bool = False,
7475
):
7576
"""Integrate with DB API library.
7677
https://www.python.org/dev/peps/pep-0249/
@@ -88,6 +89,7 @@ def trace_integration(
8889
enable_commenter: Flag to enable/disable sqlcommenter.
8990
db_api_integration_factory: The `DatabaseApiIntegration` to use. If none is passed the
9091
default one is used.
92+
enable_attribute_commenter: Flag to enable/disable sqlcomment inclusion in `db.statement` span attribute. Only available if enable_commenter=True.
9193
"""
9294
wrap_connect(
9395
__name__,
@@ -100,6 +102,7 @@ def trace_integration(
100102
capture_parameters=capture_parameters,
101103
enable_commenter=enable_commenter,
102104
db_api_integration_factory=db_api_integration_factory,
105+
enable_attribute_commenter=enable_attribute_commenter,
103106
)
104107

105108

@@ -115,6 +118,7 @@ def wrap_connect(
115118
enable_commenter: bool = False,
116119
db_api_integration_factory=None,
117120
commenter_options: dict = None,
121+
enable_attribute_commenter: bool = False,
118122
):
119123
"""Integrate with DB API library.
120124
https://www.python.org/dev/peps/pep-0249/
@@ -133,6 +137,7 @@ def wrap_connect(
133137
db_api_integration_factory: The `DatabaseApiIntegration` to use. If none is passed the
134138
default one is used.
135139
commenter_options: Configurations for tags to be appended at the sql query.
140+
enable_attribute_commenter: Flag to enable/disable sqlcomment inclusion in `db.statement` span attribute. Only available if enable_commenter=True.
136141
137142
"""
138143
db_api_integration_factory = (
@@ -156,6 +161,7 @@ def wrap_connect_(
156161
enable_commenter=enable_commenter,
157162
commenter_options=commenter_options,
158163
connect_module=connect_module,
164+
enable_attribute_commenter=enable_attribute_commenter,
159165
)
160166
return db_integration.wrapped_connection(wrapped, args, kwargs)
161167

@@ -191,6 +197,7 @@ def instrument_connection(
191197
enable_commenter: bool = False,
192198
commenter_options: dict = None,
193199
connect_module: typing.Callable[..., typing.Any] = None,
200+
enable_attribute_commenter: bool = False,
194201
):
195202
"""Enable instrumentation in a database connection.
196203
@@ -206,6 +213,7 @@ def instrument_connection(
206213
enable_commenter: Flag to enable/disable sqlcommenter.
207214
commenter_options: Configurations for tags to be appended at the sql query.
208215
connect_module: Module name where connect method is available.
216+
enable_attribute_commenter: Flag to enable/disable sqlcomment inclusion in `db.statement` span attribute. Only available if enable_commenter=True.
209217
210218
Returns:
211219
An instrumented connection.
@@ -224,6 +232,7 @@ def instrument_connection(
224232
enable_commenter=enable_commenter,
225233
commenter_options=commenter_options,
226234
connect_module=connect_module,
235+
enable_attribute_commenter=enable_attribute_commenter,
227236
)
228237
db_integration.get_connection_attributes(connection)
229238
return get_traced_connection_proxy(connection, db_integration)
@@ -257,6 +266,7 @@ def __init__(
257266
enable_commenter: bool = False,
258267
commenter_options: dict = None,
259268
connect_module: typing.Callable[..., typing.Any] = None,
269+
enable_attribute_commenter: bool = False,
260270
):
261271
self.connection_attributes = connection_attributes
262272
if self.connection_attributes is None:
@@ -277,6 +287,7 @@ def __init__(
277287
self.capture_parameters = capture_parameters
278288
self.enable_commenter = enable_commenter
279289
self.commenter_options = commenter_options
290+
self.enable_attribute_commenter = enable_attribute_commenter
280291
self.database_system = database_system
281292
self.connection_props = {}
282293
self.span_attributes = {}
@@ -434,9 +445,52 @@ def __init__(self, db_api_integration: DatabaseApiIntegration) -> None:
434445
if self._db_api_integration.commenter_options
435446
else {}
436447
)
448+
self._enable_attribute_commenter = (
449+
self._db_api_integration.enable_attribute_commenter
450+
)
437451
self._connect_module = self._db_api_integration.connect_module
438452
self._leading_comment_remover = re.compile(r"^/\*.*?\*/")
439453

454+
def _capture_mysql_version(self, cursor) -> None:
455+
"""Lazy capture of mysql-connector client version using cursor, if applicable"""
456+
if (
457+
self._db_api_integration.database_system == "mysql"
458+
and self._db_api_integration.connect_module.__name__
459+
== "mysql.connector"
460+
and not self._db_api_integration.commenter_data[
461+
"mysql_client_version"
462+
]
463+
):
464+
self._db_api_integration.commenter_data["mysql_client_version"] = (
465+
cursor._cnx._cmysql.get_client_info()
466+
)
467+
468+
def _get_commenter_data(self) -> dict:
469+
"""Uses DB-API integration to return commenter data for sqlcomment"""
470+
commenter_data = dict(self._db_api_integration.commenter_data)
471+
if self._commenter_options.get("opentelemetry_values", True):
472+
commenter_data.update(**_get_opentelemetry_values())
473+
return {
474+
k: v
475+
for k, v in commenter_data.items()
476+
if self._commenter_options.get(k, True)
477+
}
478+
479+
def _update_args_with_added_sql_comment(self, args, cursor) -> tuple:
480+
"""Updates args with cursor info and adds sqlcomment to query statement"""
481+
try:
482+
args_list = list(args)
483+
self._capture_mysql_version(cursor)
484+
commenter_data = self._get_commenter_data()
485+
statement = _add_sql_comment(args_list[0], **commenter_data)
486+
args_list[0] = statement
487+
args = tuple(args_list)
488+
except Exception as exc: # pylint: disable=broad-except
489+
_logger.exception(
490+
"Exception while generating sql comment: %s", exc
491+
)
492+
return args
493+
440494
def _populate_span(
441495
self,
442496
span: trace_api.Span,
@@ -497,52 +551,22 @@ def traced_execution(
497551
) as span:
498552
if span.is_recording():
499553
if args and self._commenter_enabled:
500-
try:
501-
args_list = list(args)
502-
503-
# lazy capture of mysql-connector client version using cursor
504-
if (
505-
self._db_api_integration.database_system == "mysql"
506-
and self._db_api_integration.connect_module.__name__
507-
== "mysql.connector"
508-
and not self._db_api_integration.commenter_data[
509-
"mysql_client_version"
510-
]
511-
):
512-
self._db_api_integration.commenter_data[
513-
"mysql_client_version"
514-
] = cursor._cnx._cmysql.get_client_info()
515-
516-
commenter_data = dict(
517-
self._db_api_integration.commenter_data
518-
)
519-
if self._commenter_options.get(
520-
"opentelemetry_values", True
521-
):
522-
commenter_data.update(
523-
**_get_opentelemetry_values()
524-
)
525-
526-
# Filter down to just the requested attributes.
527-
commenter_data = {
528-
k: v
529-
for k, v in commenter_data.items()
530-
if self._commenter_options.get(k, True)
531-
}
532-
statement = _add_sql_comment(
533-
args_list[0], **commenter_data
554+
if self._enable_attribute_commenter:
555+
# sqlcomment is added to executed query and db.statement span attribute
556+
args = self._update_args_with_added_sql_comment(
557+
args, cursor
534558
)
535-
536-
args_list[0] = statement
537-
args = tuple(args_list)
538-
539-
except Exception as exc: # pylint: disable=broad-except
540-
_logger.exception(
541-
"Exception while generating sql comment: %s", exc
559+
self._populate_span(span, cursor, *args)
560+
else:
561+
# sqlcomment is only added to executed query
562+
# so db.statement is set before add_sql_comment
563+
self._populate_span(span, cursor, *args)
564+
args = self._update_args_with_added_sql_comment(
565+
args, cursor
542566
)
543-
544-
self._populate_span(span, cursor, *args)
545-
567+
else:
568+
# no sqlcomment anywhere
569+
self._populate_span(span, cursor, *args)
546570
return query_method(*args, **kwargs)
547571

548572

Diff for: ‎instrumentation/opentelemetry-instrumentation-dbapi/tests/test_dbapi_integration.py

+405-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
# pylint: disable=too-many-lines
1516

1617
import logging
1718
import re
@@ -277,6 +278,47 @@ def test_executemany_comment(self):
277278
cursor.query,
278279
r"Select 1 /\*dbapi_threadsafety=123,driver_paramstyle='test',libpq_version=123,traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
279280
)
281+
spans_list = self.memory_exporter.get_finished_spans()
282+
self.assertEqual(len(spans_list), 1)
283+
span = spans_list[0]
284+
self.assertEqual(
285+
span.attributes[SpanAttributes.DB_STATEMENT],
286+
"Select 1;",
287+
)
288+
289+
def test_executemany_comment_stmt_enabled(self):
290+
connect_module = mock.MagicMock()
291+
connect_module.__name__ = "test"
292+
connect_module.__version__ = mock.MagicMock()
293+
connect_module.__libpq_version__ = 123
294+
connect_module.apilevel = 123
295+
connect_module.threadsafety = 123
296+
connect_module.paramstyle = "test"
297+
298+
db_integration = dbapi.DatabaseApiIntegration(
299+
"testname",
300+
"postgresql",
301+
enable_commenter=True,
302+
commenter_options={"db_driver": False, "dbapi_level": False},
303+
connect_module=connect_module,
304+
enable_attribute_commenter=True,
305+
)
306+
mock_connection = db_integration.wrapped_connection(
307+
mock_connect, {}, {}
308+
)
309+
cursor = mock_connection.cursor()
310+
cursor.executemany("Select 1;")
311+
self.assertRegex(
312+
cursor.query,
313+
r"Select 1 /\*dbapi_threadsafety=123,driver_paramstyle='test',libpq_version=123,traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
314+
)
315+
spans_list = self.memory_exporter.get_finished_spans()
316+
self.assertEqual(len(spans_list), 1)
317+
span = spans_list[0]
318+
self.assertRegex(
319+
span.attributes[SpanAttributes.DB_STATEMENT],
320+
r"Select 1 /\*dbapi_threadsafety=123,driver_paramstyle='test',libpq_version=123,traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
321+
)
280322

281323
def test_executemany_comment_non_pep_249_compliant(self):
282324
class MockConnectModule:
@@ -306,8 +348,54 @@ def __getattr__(self, name):
306348
cursor.query,
307349
r"Select 1 /\*dbapi_level='1.0',dbapi_threadsafety='unknown',driver_paramstyle='unknown',libpq_version=123,traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
308350
)
351+
spans_list = self.memory_exporter.get_finished_spans()
352+
self.assertEqual(len(spans_list), 1)
353+
span = spans_list[0]
354+
self.assertEqual(
355+
span.attributes[SpanAttributes.DB_STATEMENT],
356+
"Select 1;",
357+
)
309358

310-
def test_executemany_comment_matches_db_statement_attribute(self):
359+
def test_executemany_comment_non_pep_249_compliant_stmt_enabled(self):
360+
class MockConnectModule:
361+
def __getattr__(self, name):
362+
if name == "__name__":
363+
return "test"
364+
if name == "__version__":
365+
return mock.MagicMock()
366+
if name == "__libpq_version__":
367+
return 123
368+
raise AttributeError("attribute missing")
369+
370+
connect_module = MockConnectModule()
371+
db_integration = dbapi.DatabaseApiIntegration(
372+
"testname",
373+
"postgresql",
374+
enable_commenter=True,
375+
connect_module=connect_module,
376+
commenter_options={"db_driver": False},
377+
enable_attribute_commenter=True,
378+
)
379+
mock_connection = db_integration.wrapped_connection(
380+
mock_connect, {}, {}
381+
)
382+
cursor = mock_connection.cursor()
383+
cursor.executemany("Select 1;")
384+
self.assertRegex(
385+
cursor.query,
386+
r"Select 1 /\*dbapi_level='1.0',dbapi_threadsafety='unknown',driver_paramstyle='unknown',libpq_version=123,traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
387+
)
388+
spans_list = self.memory_exporter.get_finished_spans()
389+
self.assertEqual(len(spans_list), 1)
390+
span = spans_list[0]
391+
self.assertRegex(
392+
span.attributes[SpanAttributes.DB_STATEMENT],
393+
r"Select 1 /\*dbapi_level='1.0',dbapi_threadsafety='unknown',driver_paramstyle='unknown',libpq_version=123,traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
394+
)
395+
396+
def test_executemany_comment_stmt_enabled_matches_db_statement_attribute(
397+
self,
398+
):
311399
connect_module = mock.MagicMock()
312400
connect_module.__version__ = mock.MagicMock()
313401
connect_module.__libpq_version__ = 123
@@ -321,6 +409,7 @@ def test_executemany_comment_matches_db_statement_attribute(self):
321409
enable_commenter=True,
322410
commenter_options={"db_driver": False, "dbapi_level": False},
323411
connect_module=connect_module,
412+
enable_attribute_commenter=True,
324413
)
325414
mock_connection = db_integration.wrapped_connection(
326415
mock_connect, {}, {}
@@ -371,6 +460,50 @@ def test_compatible_build_version_psycopg_psycopg2_libpq(self):
371460
cursor.query,
372461
r"Select 1 /\*dbapi_threadsafety=123,driver_paramstyle='test',libpq_version=123,traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
373462
)
463+
spans_list = self.memory_exporter.get_finished_spans()
464+
self.assertEqual(len(spans_list), 1)
465+
span = spans_list[0]
466+
self.assertEqual(
467+
span.attributes[SpanAttributes.DB_STATEMENT],
468+
"Select 1;",
469+
)
470+
471+
def test_compatible_build_version_psycopg_psycopg2_libpq_stmt_enabled(
472+
self,
473+
):
474+
connect_module = mock.MagicMock()
475+
connect_module.__name__ = "test"
476+
connect_module.__version__ = mock.MagicMock()
477+
connect_module.pq = mock.MagicMock()
478+
connect_module.pq.__build_version__ = 123
479+
connect_module.apilevel = 123
480+
connect_module.threadsafety = 123
481+
connect_module.paramstyle = "test"
482+
483+
db_integration = dbapi.DatabaseApiIntegration(
484+
"testname",
485+
"postgresql",
486+
enable_commenter=True,
487+
commenter_options={"db_driver": False, "dbapi_level": False},
488+
connect_module=connect_module,
489+
enable_attribute_commenter=True,
490+
)
491+
mock_connection = db_integration.wrapped_connection(
492+
mock_connect, {}, {}
493+
)
494+
cursor = mock_connection.cursor()
495+
cursor.executemany("Select 1;")
496+
self.assertRegex(
497+
cursor.query,
498+
r"Select 1 /\*dbapi_threadsafety=123,driver_paramstyle='test',libpq_version=123,traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
499+
)
500+
spans_list = self.memory_exporter.get_finished_spans()
501+
self.assertEqual(len(spans_list), 1)
502+
span = spans_list[0]
503+
self.assertRegex(
504+
span.attributes[SpanAttributes.DB_STATEMENT],
505+
r"Select 1 /\*dbapi_threadsafety=123,driver_paramstyle='test',libpq_version=123,traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
506+
)
374507

375508
def test_executemany_psycopg2_integration_comment(self):
376509
connect_module = mock.MagicMock()
@@ -397,6 +530,47 @@ def test_executemany_psycopg2_integration_comment(self):
397530
cursor.query,
398531
r"Select 1 /\*db_driver='psycopg2%%3A1.2.3',dbapi_threadsafety=123,driver_paramstyle='test',libpq_version=123,traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
399532
)
533+
spans_list = self.memory_exporter.get_finished_spans()
534+
self.assertEqual(len(spans_list), 1)
535+
span = spans_list[0]
536+
self.assertEqual(
537+
span.attributes[SpanAttributes.DB_STATEMENT],
538+
"Select 1;",
539+
)
540+
541+
def test_executemany_psycopg2_integration_comment_stmt_enabled(self):
542+
connect_module = mock.MagicMock()
543+
connect_module.__name__ = "psycopg2"
544+
connect_module.__version__ = "1.2.3"
545+
connect_module.__libpq_version__ = 123
546+
connect_module.apilevel = 123
547+
connect_module.threadsafety = 123
548+
connect_module.paramstyle = "test"
549+
550+
db_integration = dbapi.DatabaseApiIntegration(
551+
"testname",
552+
"postgresql",
553+
enable_commenter=True,
554+
commenter_options={"db_driver": True, "dbapi_level": False},
555+
connect_module=connect_module,
556+
enable_attribute_commenter=True,
557+
)
558+
mock_connection = db_integration.wrapped_connection(
559+
mock_connect, {}, {}
560+
)
561+
cursor = mock_connection.cursor()
562+
cursor.executemany("Select 1;")
563+
self.assertRegex(
564+
cursor.query,
565+
r"Select 1 /\*db_driver='psycopg2%%3A1.2.3',dbapi_threadsafety=123,driver_paramstyle='test',libpq_version=123,traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
566+
)
567+
spans_list = self.memory_exporter.get_finished_spans()
568+
self.assertEqual(len(spans_list), 1)
569+
span = spans_list[0]
570+
self.assertRegex(
571+
span.attributes[SpanAttributes.DB_STATEMENT],
572+
r"Select 1 /\*db_driver='psycopg2%%3A1.2.3',dbapi_threadsafety=123,driver_paramstyle='test',libpq_version=123,traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
573+
)
400574

401575
def test_executemany_psycopg_integration_comment(self):
402576
connect_module = mock.MagicMock()
@@ -424,6 +598,48 @@ def test_executemany_psycopg_integration_comment(self):
424598
cursor.query,
425599
r"Select 1 /\*db_driver='psycopg%%3A1.2.3',dbapi_threadsafety=123,driver_paramstyle='test',libpq_version=123,traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
426600
)
601+
spans_list = self.memory_exporter.get_finished_spans()
602+
self.assertEqual(len(spans_list), 1)
603+
span = spans_list[0]
604+
self.assertEqual(
605+
span.attributes[SpanAttributes.DB_STATEMENT],
606+
"Select 1;",
607+
)
608+
609+
def test_executemany_psycopg_integration_comment_stmt_enabled(self):
610+
connect_module = mock.MagicMock()
611+
connect_module.__name__ = "psycopg"
612+
connect_module.__version__ = "1.2.3"
613+
connect_module.pq = mock.MagicMock()
614+
connect_module.pq.__build_version__ = 123
615+
connect_module.apilevel = 123
616+
connect_module.threadsafety = 123
617+
connect_module.paramstyle = "test"
618+
619+
db_integration = dbapi.DatabaseApiIntegration(
620+
"testname",
621+
"postgresql",
622+
enable_commenter=True,
623+
commenter_options={"db_driver": True, "dbapi_level": False},
624+
connect_module=connect_module,
625+
enable_attribute_commenter=True,
626+
)
627+
mock_connection = db_integration.wrapped_connection(
628+
mock_connect, {}, {}
629+
)
630+
cursor = mock_connection.cursor()
631+
cursor.executemany("Select 1;")
632+
self.assertRegex(
633+
cursor.query,
634+
r"Select 1 /\*db_driver='psycopg%%3A1.2.3',dbapi_threadsafety=123,driver_paramstyle='test',libpq_version=123,traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
635+
)
636+
spans_list = self.memory_exporter.get_finished_spans()
637+
self.assertEqual(len(spans_list), 1)
638+
span = spans_list[0]
639+
self.assertRegex(
640+
span.attributes[SpanAttributes.DB_STATEMENT],
641+
r"Select 1 /\*db_driver='psycopg%%3A1.2.3',dbapi_threadsafety=123,driver_paramstyle='test',libpq_version=123,traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
642+
)
427643

428644
def test_executemany_mysqlconnector_integration_comment(self):
429645
connect_module = mock.MagicMock()
@@ -450,6 +666,47 @@ def test_executemany_mysqlconnector_integration_comment(self):
450666
cursor.query,
451667
r"Select 1 /\*db_driver='mysql.connector%%3A1.2.3',dbapi_threadsafety=123,driver_paramstyle='test',mysql_client_version='1.2.3',traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
452668
)
669+
spans_list = self.memory_exporter.get_finished_spans()
670+
self.assertEqual(len(spans_list), 1)
671+
span = spans_list[0]
672+
self.assertEqual(
673+
span.attributes[SpanAttributes.DB_STATEMENT],
674+
"Select 1;",
675+
)
676+
677+
def test_executemany_mysqlconnector_integration_comment_stmt_enabled(self):
678+
connect_module = mock.MagicMock()
679+
connect_module.__name__ = "mysql.connector"
680+
connect_module.__version__ = "1.2.3"
681+
connect_module.apilevel = 123
682+
connect_module.threadsafety = 123
683+
connect_module.paramstyle = "test"
684+
685+
db_integration = dbapi.DatabaseApiIntegration(
686+
"testname",
687+
"mysql",
688+
enable_commenter=True,
689+
commenter_options={"db_driver": True, "dbapi_level": False},
690+
connect_module=connect_module,
691+
enable_attribute_commenter=True,
692+
)
693+
694+
mock_connection = db_integration.wrapped_connection(
695+
mock_connect, {}, {}
696+
)
697+
cursor = mock_connection.cursor()
698+
cursor.executemany("Select 1;")
699+
self.assertRegex(
700+
cursor.query,
701+
r"Select 1 /\*db_driver='mysql.connector%%3A1.2.3',dbapi_threadsafety=123,driver_paramstyle='test',mysql_client_version='1.2.3',traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
702+
)
703+
spans_list = self.memory_exporter.get_finished_spans()
704+
self.assertEqual(len(spans_list), 1)
705+
span = spans_list[0]
706+
self.assertRegex(
707+
span.attributes[SpanAttributes.DB_STATEMENT],
708+
r"Select 1 /\*db_driver='mysql.connector%%3A1.2.3',dbapi_threadsafety=123,driver_paramstyle='test',mysql_client_version='1.2.3',traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
709+
)
453710

454711
@mock.patch("opentelemetry.instrumentation.dbapi.util_version")
455712
def test_executemany_mysqlclient_integration_comment(
@@ -485,6 +742,56 @@ def test_executemany_mysqlclient_integration_comment(
485742
cursor.query,
486743
r"Select 1 /\*db_driver='MySQLdb%%3A1.2.3',dbapi_threadsafety=123,driver_paramstyle='test',mysql_client_version='123',traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
487744
)
745+
spans_list = self.memory_exporter.get_finished_spans()
746+
self.assertEqual(len(spans_list), 1)
747+
span = spans_list[0]
748+
self.assertEqual(
749+
span.attributes[SpanAttributes.DB_STATEMENT],
750+
"Select 1;",
751+
)
752+
753+
@mock.patch("opentelemetry.instrumentation.dbapi.util_version")
754+
def test_executemany_mysqlclient_integration_comment_stmt_enabled(
755+
self,
756+
mock_dbapi_util_version,
757+
):
758+
mock_dbapi_util_version.return_value = "1.2.3"
759+
connect_module = mock.MagicMock()
760+
connect_module.__name__ = "MySQLdb"
761+
connect_module.__version__ = "1.2.3"
762+
connect_module.apilevel = 123
763+
connect_module.threadsafety = 123
764+
connect_module.paramstyle = "test"
765+
connect_module._mysql = mock.MagicMock()
766+
connect_module._mysql.get_client_info = mock.MagicMock(
767+
return_value="123"
768+
)
769+
770+
db_integration = dbapi.DatabaseApiIntegration(
771+
"testname",
772+
"mysql",
773+
enable_commenter=True,
774+
commenter_options={"db_driver": True, "dbapi_level": False},
775+
connect_module=connect_module,
776+
enable_attribute_commenter=True,
777+
)
778+
779+
mock_connection = db_integration.wrapped_connection(
780+
mock_connect, {}, {}
781+
)
782+
cursor = mock_connection.cursor()
783+
cursor.executemany("Select 1;")
784+
self.assertRegex(
785+
cursor.query,
786+
r"Select 1 /\*db_driver='MySQLdb%%3A1.2.3',dbapi_threadsafety=123,driver_paramstyle='test',mysql_client_version='123',traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
787+
)
788+
spans_list = self.memory_exporter.get_finished_spans()
789+
self.assertEqual(len(spans_list), 1)
790+
span = spans_list[0]
791+
self.assertRegex(
792+
span.attributes[SpanAttributes.DB_STATEMENT],
793+
r"Select 1 /\*db_driver='MySQLdb%%3A1.2.3',dbapi_threadsafety=123,driver_paramstyle='test',mysql_client_version='123',traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
794+
)
488795

489796
def test_executemany_pymysql_integration_comment(self):
490797
connect_module = mock.MagicMock()
@@ -512,6 +819,48 @@ def test_executemany_pymysql_integration_comment(self):
512819
cursor.query,
513820
r"Select 1 /\*db_driver='pymysql%%3A1.2.3',dbapi_threadsafety=123,driver_paramstyle='test',mysql_client_version='123',traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
514821
)
822+
spans_list = self.memory_exporter.get_finished_spans()
823+
self.assertEqual(len(spans_list), 1)
824+
span = spans_list[0]
825+
self.assertEqual(
826+
span.attributes[SpanAttributes.DB_STATEMENT],
827+
"Select 1;",
828+
)
829+
830+
def test_executemany_pymysql_integration_comment_stmt_enabled(self):
831+
connect_module = mock.MagicMock()
832+
connect_module.__name__ = "pymysql"
833+
connect_module.__version__ = "1.2.3"
834+
connect_module.apilevel = 123
835+
connect_module.threadsafety = 123
836+
connect_module.paramstyle = "test"
837+
connect_module.get_client_info = mock.MagicMock(return_value="123")
838+
839+
db_integration = dbapi.DatabaseApiIntegration(
840+
"testname",
841+
"mysql",
842+
enable_commenter=True,
843+
commenter_options={"db_driver": True, "dbapi_level": False},
844+
connect_module=connect_module,
845+
enable_attribute_commenter=True,
846+
)
847+
848+
mock_connection = db_integration.wrapped_connection(
849+
mock_connect, {}, {}
850+
)
851+
cursor = mock_connection.cursor()
852+
cursor.executemany("Select 1;")
853+
self.assertRegex(
854+
cursor.query,
855+
r"Select 1 /\*db_driver='pymysql%%3A1.2.3',dbapi_threadsafety=123,driver_paramstyle='test',mysql_client_version='123',traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
856+
)
857+
spans_list = self.memory_exporter.get_finished_spans()
858+
self.assertEqual(len(spans_list), 1)
859+
span = spans_list[0]
860+
self.assertRegex(
861+
span.attributes[SpanAttributes.DB_STATEMENT],
862+
r"Select 1 /\*db_driver='pymysql%%3A1.2.3',dbapi_threadsafety=123,driver_paramstyle='test',mysql_client_version='123',traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
863+
)
515864

516865
def test_executemany_flask_integration_comment(self):
517866
connect_module = mock.MagicMock()
@@ -544,6 +893,58 @@ def test_executemany_flask_integration_comment(self):
544893
cursor.query,
545894
r"Select 1 /\*dbapi_threadsafety=123,driver_paramstyle='test',flask=1,libpq_version=123,traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
546895
)
896+
spans_list = self.memory_exporter.get_finished_spans()
897+
self.assertEqual(len(spans_list), 1)
898+
span = spans_list[0]
899+
self.assertEqual(
900+
span.attributes[SpanAttributes.DB_STATEMENT],
901+
"Select 1;",
902+
)
903+
904+
clear_context = context.set_value(
905+
"SQLCOMMENTER_ORM_TAGS_AND_VALUES", {}, current_context
906+
)
907+
context.attach(clear_context)
908+
909+
def test_executemany_flask_integration_comment_stmt_enabled(self):
910+
connect_module = mock.MagicMock()
911+
connect_module.__name__ = "test"
912+
connect_module.__version__ = mock.MagicMock()
913+
connect_module.__libpq_version__ = 123
914+
connect_module.apilevel = 123
915+
connect_module.threadsafety = 123
916+
connect_module.paramstyle = "test"
917+
918+
db_integration = dbapi.DatabaseApiIntegration(
919+
"testname",
920+
"postgresql",
921+
enable_commenter=True,
922+
commenter_options={"db_driver": False, "dbapi_level": False},
923+
connect_module=connect_module,
924+
enable_attribute_commenter=True,
925+
)
926+
current_context = context.get_current()
927+
sqlcommenter_context = context.set_value(
928+
"SQLCOMMENTER_ORM_TAGS_AND_VALUES", {"flask": 1}, current_context
929+
)
930+
context.attach(sqlcommenter_context)
931+
932+
mock_connection = db_integration.wrapped_connection(
933+
mock_connect, {}, {}
934+
)
935+
cursor = mock_connection.cursor()
936+
cursor.executemany("Select 1;")
937+
self.assertRegex(
938+
cursor.query,
939+
r"Select 1 /\*dbapi_threadsafety=123,driver_paramstyle='test',flask=1,libpq_version=123,traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
940+
)
941+
spans_list = self.memory_exporter.get_finished_spans()
942+
self.assertEqual(len(spans_list), 1)
943+
span = spans_list[0]
944+
self.assertRegex(
945+
span.attributes[SpanAttributes.DB_STATEMENT],
946+
r"Select 1 /\*dbapi_threadsafety=123,driver_paramstyle='test',flask=1,libpq_version=123,traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
947+
)
547948

548949
clear_context = context.set_value(
549950
"SQLCOMMENTER_ORM_TAGS_AND_VALUES", {}, current_context
@@ -603,6 +1004,7 @@ def test_instrument_connection_kwargs_defaults(self, mock_dbapiint):
6031004
self.assertEqual(kwargs["enable_commenter"], False)
6041005
self.assertEqual(kwargs["commenter_options"], None)
6051006
self.assertEqual(kwargs["connect_module"], None)
1007+
self.assertEqual(kwargs["enable_attribute_commenter"], False)
6061008

6071009
@mock.patch("opentelemetry.instrumentation.dbapi.DatabaseApiIntegration")
6081010
def test_instrument_connection_kwargs_provided(self, mock_dbapiint):
@@ -619,6 +1021,7 @@ def test_instrument_connection_kwargs_provided(self, mock_dbapiint):
6191021
enable_commenter=True,
6201022
commenter_options={"foo": "bar"},
6211023
connect_module=mock_connect_module,
1024+
enable_attribute_commenter=True,
6221025
)
6231026
kwargs = mock_dbapiint.call_args[1]
6241027
self.assertEqual(kwargs["connection_attributes"], {"foo": "bar"})
@@ -628,6 +1031,7 @@ def test_instrument_connection_kwargs_provided(self, mock_dbapiint):
6281031
self.assertEqual(kwargs["enable_commenter"], True)
6291032
self.assertEqual(kwargs["commenter_options"], {"foo": "bar"})
6301033
self.assertIs(kwargs["connect_module"], mock_connect_module)
1034+
self.assertEqual(kwargs["enable_attribute_commenter"], True)
6311035

6321036
def test_uninstrument_connection(self):
6331037
connection = mock.Mock()

0 commit comments

Comments
 (0)
Please sign in to comment.