Skip to content
Open
1 change: 1 addition & 0 deletions .changelog/4561.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`opentelemetry-instrumentation-dbapi`: Add `comment_position` option to `_add_sql_comment()` to support prepending sqlcommenter comment to the beginning of the query
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@
import functools
import logging
import re
from typing import Any, Awaitable, Callable, Generic, TypeVar
from typing import Any, Awaitable, Callable, Generic, Literal, TypeVar

from wrapt import wrap_function_wrapper

Expand Down Expand Up @@ -200,13 +200,15 @@
"MySQLdb": "mysqlclient",
}

CommentPositionT = Literal["start", "end"]

_logger = logging.getLogger(__name__)

ConnectionT = TypeVar("ConnectionT")
CursorT = TypeVar("CursorT")


def trace_integration(
def trace_integration( # pylint: disable=too-many-positional-arguments
connect_module: Callable[..., Any],
connect_method_name: str,
database_system: str,
Expand All @@ -217,6 +219,7 @@ def trace_integration(
db_api_integration_factory: type[DatabaseApiIntegration] | None = None,
enable_attribute_commenter: bool = False,
commenter_options: dict[str, Any] | None = None,
comment_position: CommentPositionT = "end",
):
"""Integrate with DB API library.
https://www.python.org/dev/peps/pep-0249/
Expand Down Expand Up @@ -250,10 +253,11 @@ def trace_integration(
db_api_integration_factory=db_api_integration_factory,
enable_attribute_commenter=enable_attribute_commenter,
commenter_options=commenter_options,
comment_position=comment_position,
)


def wrap_connect(
def wrap_connect( # pylint: disable=too-many-positional-arguments
name: str,
connect_module: Callable[..., Any],
connect_method_name: str,
Expand All @@ -266,6 +270,7 @@ def wrap_connect(
db_api_integration_factory: type[DatabaseApiIntegration] | None = None,
commenter_options: dict[str, Any] | None = None,
enable_attribute_commenter: bool = False,
comment_position: CommentPositionT = "end",
):
"""Integrate with DB API library.
https://www.python.org/dev/peps/pep-0249/
Expand Down Expand Up @@ -309,6 +314,7 @@ def wrap_connect_(
commenter_options=commenter_options,
connect_module=connect_module,
enable_attribute_commenter=enable_attribute_commenter,
comment_position=comment_position,
)
return db_integration.wrapped_connection(wrapped, args, kwargs)

Expand All @@ -333,7 +339,7 @@ def unwrap_connect(
unwrap(connect_module, connect_method_name)


def instrument_connection(
def instrument_connection( # pylint: disable=too-many-positional-arguments
name: str,
connection: ConnectionT | TracedConnectionProxy[ConnectionT],
database_system: str,
Expand All @@ -346,6 +352,7 @@ def instrument_connection(
connect_module: Callable[..., Any] | None = None,
enable_attribute_commenter: bool = False,
db_api_integration_factory: type[DatabaseApiIntegration] | None = None,
comment_position: CommentPositionT = "end",
) -> TracedConnectionProxy[ConnectionT]:
"""Enable instrumentation in a database connection.

Expand Down Expand Up @@ -390,6 +397,7 @@ def instrument_connection(
commenter_options=commenter_options,
connect_module=connect_module,
enable_attribute_commenter=enable_attribute_commenter,
comment_position=comment_position,
)
db_integration.get_connection_attributes(connection)
return get_traced_connection_proxy(connection, db_integration)
Expand All @@ -414,7 +422,7 @@ def uninstrument_connection(


class DatabaseApiIntegration:
def __init__(
def __init__( # pylint: disable=too-many-positional-arguments
self,
name: str,
database_system: str,
Expand All @@ -426,6 +434,7 @@ def __init__(
commenter_options: dict[str, Any] | None = None,
connect_module: Callable[..., Any] | None = None,
enable_attribute_commenter: bool = False,
comment_position: CommentPositionT = "end",
):
# Initialize semantic conventions opt-in if needed
_OpenTelemetrySemanticConventionStability._initialize()
Expand Down Expand Up @@ -462,6 +471,7 @@ def __init__(
self.enable_commenter = enable_commenter
self.commenter_options = commenter_options
self.enable_attribute_commenter = enable_attribute_commenter
self.comment_position = comment_position
self.database_system = database_system
self.connection_props: dict[str, Any] = {}
self.span_attributes: dict[str, Any] = {}
Expand Down Expand Up @@ -693,7 +703,11 @@ def _update_args_with_added_sql_comment(self, args, cursor) -> tuple:
args_list[0] = args_list[0].as_string(cursor.connection)

args_list[0] = str(args_list[0])
statement = _add_sql_comment(args_list[0], **commenter_data)
statement = _add_sql_comment(
args_list[0],
comment_position=self._db_api_integration.comment_position,
**commenter_data,
)
args_list[0] = statement
args = tuple(args_list)
except Exception as exc: # pylint: disable=broad-except
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,59 @@ def test_executemany_comment(self):
"Select 1;",
)

def test_executemany_comment_position_start(self):
connect_module = mock.MagicMock()
connect_module.__name__ = "test"
connect_module.__version__ = mock.MagicMock()
connect_module.pq.version.return_value = 123
connect_module.apilevel = 123
connect_module.threadsafety = 123
connect_module.paramstyle = "test"

db_integration = dbapi.DatabaseApiIntegration(
"instrumenting_module_test_name",
"postgresql",
enable_commenter=True,
commenter_options={"db_driver": False, "dbapi_level": False},
connect_module=connect_module,
comment_position="start",
)
mock_connection = db_integration.wrapped_connection(
mock_connect, {}, {}
)
cursor = mock_connection.cursor()
cursor.executemany("Select 1;")
self.assertRegex(
cursor.query,
r"/\*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}'\*/ Select 1;",
)

def test_executemany_comment_position_end_default(self):
connect_module = mock.MagicMock()
connect_module.__name__ = "test"
connect_module.__version__ = mock.MagicMock()
connect_module.pq.version.return_value = 123
connect_module.apilevel = 123
connect_module.threadsafety = 123
connect_module.paramstyle = "test"

db_integration = dbapi.DatabaseApiIntegration(
"instrumenting_module_test_name",
"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}'\*/;",
)

def test_executemany_comment_stmt_enabled(self):
connect_module = mock.MagicMock()
connect_module.__name__ = "test"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,22 @@
from opentelemetry.instrumentation.utils import _url_quote


def _add_sql_comment(sql, **meta) -> str:
def _add_sql_comment(sql, comment_position="end", **meta) -> str:
"""
Appends comments to the sql statement and returns it
"""
meta.update(**_add_framework_tags())
comment = _generate_sql_comment(**meta)
sql = sql.rstrip()
if sql.endswith(";"):
sql = sql[:-1] + comment + ";"
sql = sql[:-1]
end = ";"
else:
sql = sql + comment
end = ""
if comment_position == "start":
sql = comment + " " + sql + end
else:
sql = sql + comment + end
return sql


Expand Down
Loading