DB-API instrumentor populates span after sqlcomment creation, not before (#2935)

This commit is contained in:
Tammy Baylis
2024-11-19 09:13:31 -08:00
committed by GitHub
parent a29ad8a6a3
commit 53b87145b3
3 changed files with 84 additions and 38 deletions

View File

@ -44,6 +44,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#2635](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2635)) ([#2635](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2635))
- `opentelemetry-instrumentation` Add support for string based dotted module paths in unwrap - `opentelemetry-instrumentation` Add support for string based dotted module paths in unwrap
([#2919](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2919)) ([#2919](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2919))
- `opentelemetry-instrumentation-dbapi` Add sqlcomment to `db.statement` attribute
([#2935](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2935))
### Fixed ### Fixed

View File

@ -492,49 +492,54 @@ class CursorTracer:
with self._db_api_integration._tracer.start_as_current_span( with self._db_api_integration._tracer.start_as_current_span(
name, kind=SpanKind.CLIENT name, kind=SpanKind.CLIENT
) as span: ) as span:
self._populate_span(span, cursor, *args) if span.is_recording():
if args and self._commenter_enabled: if args and self._commenter_enabled:
try: try:
args_list = list(args) args_list = list(args)
# lazy capture of mysql-connector client version using cursor # lazy capture of mysql-connector client version using cursor
if ( if (
self._db_api_integration.database_system == "mysql" self._db_api_integration.database_system == "mysql"
and self._db_api_integration.connect_module.__name__ and self._db_api_integration.connect_module.__name__
== "mysql.connector" == "mysql.connector"
and not self._db_api_integration.commenter_data[ and not self._db_api_integration.commenter_data[
"mysql_client_version" "mysql_client_version"
] ]
): ):
self._db_api_integration.commenter_data[ self._db_api_integration.commenter_data[
"mysql_client_version" "mysql_client_version"
] = cursor._cnx._cmysql.get_client_info() ] = cursor._cnx._cmysql.get_client_info()
commenter_data = dict( commenter_data = dict(
self._db_api_integration.commenter_data self._db_api_integration.commenter_data
) )
if self._commenter_options.get( if self._commenter_options.get(
"opentelemetry_values", True "opentelemetry_values", True
): ):
commenter_data.update(**_get_opentelemetry_values()) commenter_data.update(
**_get_opentelemetry_values()
)
# Filter down to just the requested attributes. # Filter down to just the requested attributes.
commenter_data = { commenter_data = {
k: v k: v
for k, v in commenter_data.items() for k, v in commenter_data.items()
if self._commenter_options.get(k, True) if self._commenter_options.get(k, True)
} }
statement = _add_sql_comment( statement = _add_sql_comment(
args_list[0], **commenter_data args_list[0], **commenter_data
) )
args_list[0] = statement args_list[0] = statement
args = tuple(args_list) args = tuple(args_list)
except Exception as exc: # pylint: disable=broad-except
_logger.exception(
"Exception while generating sql comment: %s", exc
)
self._populate_span(span, cursor, *args)
except Exception as exc: # pylint: disable=broad-except
_logger.exception(
"Exception while generating sql comment: %s", exc
)
return query_method(*args, **kwargs) return query_method(*args, **kwargs)

View File

@ -14,6 +14,7 @@
import logging import logging
import re
from unittest import mock from unittest import mock
from opentelemetry import context from opentelemetry import context
@ -306,6 +307,44 @@ class TestDBApiIntegration(TestBase):
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}'\*/;", 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}'\*/;",
) )
def test_executemany_comment_matches_db_statement_attribute(self):
connect_module = mock.MagicMock()
connect_module.__version__ = mock.MagicMock()
connect_module.__libpq_version__ = 123
connect_module.apilevel = 123
connect_module.threadsafety = 123
connect_module.paramstyle = "test"
db_integration = dbapi.DatabaseApiIntegration(
"testname",
"postgresql",
enable_commenter=True,
commenter_options={"db_driver": False, "dbapi_level": False},
connect_module=connect_module,
)
mock_connection = db_integration.wrapped_connection(
mock_connect, {}, {}
)
cursor = mock_connection.cursor()
cursor.executemany("Select 1;")
self.assertRegex(
cursor.query,
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}'\*/;",
)
spans_list = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans_list), 1)
span = spans_list[0]
self.assertRegex(
span.attributes[SpanAttributes.DB_STATEMENT],
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}'\*/",
)
cursor_span_id = re.search(r"[a-zA-Z0-9_]{16}", cursor.query).group()
db_statement_span_id = re.search(
r"[a-zA-Z0-9_]{16}", span.attributes[SpanAttributes.DB_STATEMENT]
).group()
self.assertEqual(cursor_span_id, db_statement_span_id)
def test_compatible_build_version_psycopg_psycopg2_libpq(self): def test_compatible_build_version_psycopg_psycopg2_libpq(self):
connect_module = mock.MagicMock() connect_module = mock.MagicMock()
connect_module.__name__ = "test" connect_module.__name__ = "test"