Skip to content

Commit df44de3

Browse files
(WIP) check prepared statement cursor at get_traced_cursor_proxy, not per-trace
1 parent 2c75bc0 commit df44de3

File tree

1 file changed

+94
-87
lines changed
  • instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi

1 file changed

+94
-87
lines changed

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

Lines changed: 94 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -408,10 +408,62 @@ def __getattribute__(self, name):
408408
)
409409

410410
def cursor(self, *args, **kwargs):
411-
return get_traced_cursor_proxy(
412-
self.__wrapped__.cursor(*args, **kwargs), db_api_integration
411+
wrapped_cursor = self.__wrapped__.cursor(*args, **kwargs)
412+
413+
# If a mysql-connector cursor was created with prepared=True
414+
# then MySQL statements will be prepared and executed natively.
415+
# 1:1 sqlcomment and span correlation in instrumentation will
416+
# break, so sqlcomment is not supported for this use case.
417+
# This is here because a client app can use multiple cursors
418+
# and to not check cursor with every traced request.
419+
is_prepared = False
420+
if (
421+
db_api_integration.database_system == "mysql"
422+
and db_api_integration.connect_module.__name__
423+
== "mysql.connector"
424+
):
425+
is_prepared = self.is_mysql_connector_cursor_prepared(
426+
wrapped_cursor
427+
)
428+
429+
if is_prepared and db_api_integration.enable_commenter:
430+
_logger.warning(
431+
"sqlcomment is not supported for query statements executed with native prepared statement support. Disabling."
432+
)
433+
db_api_integration.enable_commenter = False
434+
435+
return get_traced_connection_proxy(
436+
wrapped_cursor, db_api_integration
413437
)
414438

439+
def is_mysql_connector_cursor_prepared(self, cursor):
440+
try:
441+
from mysql.connector.cursor_cext import ( # pylint: disable=import-outside-toplevel
442+
CMySQLCursorPrepared,
443+
CMySQLCursorPreparedDict,
444+
CMySQLCursorPreparedNamedTuple,
445+
CMySQLCursorPreparedRaw,
446+
)
447+
448+
if type(cursor) in [
449+
CMySQLCursorPrepared,
450+
CMySQLCursorPreparedDict,
451+
CMySQLCursorPreparedNamedTuple,
452+
CMySQLCursorPreparedRaw,
453+
]:
454+
_logger.warning(
455+
"Adding sqlcomment to prepared MySQL statements is not supported. Please check OpenTelemetry configuration. Skipping."
456+
)
457+
return True
458+
459+
except ImportError as exc:
460+
_logger.warning(
461+
"Could not verify mysql.connector cursor, skipping prepared statement check: %s",
462+
exc,
463+
)
464+
465+
return False
466+
415467
def __enter__(self):
416468
self.__wrapped__.__enter__()
417469
return self
@@ -460,34 +512,6 @@ def _populate_span(
460512
if self._db_api_integration.capture_parameters and len(args) > 1:
461513
span.set_attribute("db.statement.parameters", str(args[1]))
462514

463-
def _is_mysql_connector_cursor_prepared(self, cursor): # pylint: disable=no-self-use
464-
try:
465-
from mysql.connector.cursor_cext import ( # pylint: disable=import-outside-toplevel
466-
CMySQLCursorPrepared,
467-
CMySQLCursorPreparedDict,
468-
CMySQLCursorPreparedNamedTuple,
469-
CMySQLCursorPreparedRaw,
470-
)
471-
472-
if type(cursor) in [
473-
CMySQLCursorPrepared,
474-
CMySQLCursorPreparedDict,
475-
CMySQLCursorPreparedNamedTuple,
476-
CMySQLCursorPreparedRaw,
477-
]:
478-
_logger.warning(
479-
"Adding sqlcomment to prepared MySQL statements is not supported. Please check OpenTelemetry configuration. Skipping."
480-
)
481-
return True
482-
483-
except ImportError as exc:
484-
_logger.warning(
485-
"Could not verify mysql.connector cursor, skipping sqlcomment: %s",
486-
exc,
487-
)
488-
489-
return False
490-
491515
def get_operation_name(self, cursor, args): # pylint: disable=no-self-use
492516
if args and isinstance(args[0], str):
493517
# Strip leading comments so we get the operation name.
@@ -522,67 +546,50 @@ def traced_execution(
522546
) as span:
523547
if span.is_recording():
524548
if args and self._commenter_enabled:
525-
# If a mysql-connector cursor was created with prepared=True
526-
# then MySQL statements will be prepared and executed natively.
527-
# 1:1 sqlcomment and span correlation in instrumentation will
528-
# break, so sqlcomment is not supported for this use case.
529-
# This is here because a client app can use multiple cursors.
530-
is_prepared = False
531-
if (
532-
self._db_api_integration.database_system == "mysql"
533-
and self._db_api_integration.connect_module.__name__
534-
== "mysql.connector"
535-
):
536-
is_prepared = self._is_mysql_connector_cursor_prepared(
537-
cursor
549+
try:
550+
args_list = list(args)
551+
552+
# lazy capture of mysql-connector client version using cursor
553+
if (
554+
self._db_api_integration.database_system == "mysql"
555+
and self._db_api_integration.connect_module.__name__
556+
== "mysql.connector"
557+
and not self._db_api_integration.commenter_data[
558+
"mysql_client_version"
559+
]
560+
):
561+
self._db_api_integration.commenter_data[
562+
"mysql_client_version"
563+
] = cursor._cnx._cmysql.get_client_info()
564+
565+
commenter_data = dict(
566+
self._db_api_integration.commenter_data
538567
)
539-
540-
if not is_prepared:
541-
try:
542-
args_list = list(args)
543-
544-
# lazy capture of mysql-connector client version using cursor
545-
if (
546-
self._db_api_integration.database_system
547-
== "mysql"
548-
and self._db_api_integration.connect_module.__name__
549-
== "mysql.connector"
550-
and not self._db_api_integration.commenter_data[
551-
"mysql_client_version"
552-
]
553-
):
554-
self._db_api_integration.commenter_data[
555-
"mysql_client_version"
556-
] = cursor._cnx._cmysql.get_client_info()
557-
558-
commenter_data = dict(
559-
self._db_api_integration.commenter_data
560-
)
561-
if self._commenter_options.get(
562-
"opentelemetry_values", True
563-
):
564-
commenter_data.update(
565-
**_get_opentelemetry_values()
566-
)
567-
568-
# Filter down to just the requested attributes.
569-
commenter_data = {
570-
k: v
571-
for k, v in commenter_data.items()
572-
if self._commenter_options.get(k, True)
573-
}
574-
statement = _add_sql_comment(
575-
args_list[0], **commenter_data
568+
if self._commenter_options.get(
569+
"opentelemetry_values", True
570+
):
571+
commenter_data.update(
572+
**_get_opentelemetry_values()
576573
)
577574

578-
args_list[0] = statement
579-
args = tuple(args_list)
575+
# Filter down to just the requested attributes.
576+
commenter_data = {
577+
k: v
578+
for k, v in commenter_data.items()
579+
if self._commenter_options.get(k, True)
580+
}
581+
statement = _add_sql_comment(
582+
args_list[0], **commenter_data
583+
)
584+
585+
args_list[0] = statement
586+
args = tuple(args_list)
580587

581-
except Exception as exc: # pylint: disable=broad-except
582-
_logger.exception(
583-
"Exception while generating sql comment: %s",
584-
exc,
585-
)
588+
except Exception as exc: # pylint: disable=broad-except
589+
_logger.exception(
590+
"Exception while generating sql comment: %s",
591+
exc,
592+
)
586593

587594
self._populate_span(span, cursor, *args)
588595

0 commit comments

Comments
 (0)