Skip to content

Commit 26bcc93

Browse files
authoredJan 9, 2025··
SQLAlchemy: db.statement inclusion of sqlcomment as opt-in (#3112)
1 parent 908437d commit 26bcc93

File tree

5 files changed

+497
-29
lines changed

5 files changed

+497
-29
lines changed
 

‎CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3737
- `opentelemetry-instrumentation` Fix `get_dist_dependency_conflicts` if no distribution requires
3838
([#3168](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3168))
3939

40+
### Breaking changes
41+
42+
- `opentelemetry-instrumentation-sqlalchemy` including sqlcomment in `db.statement` span attribute value is now opt-in
43+
([#3112](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3112))
4044

4145
## Version 1.29.0/0.50b0 (2024-12-11)
4246

‎instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/__init__.py

+42-2
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,30 @@
6565
::
6666
Enabling this flag will add traceparent values /*traceparent='00-03afa25236b8cd948fa853d67038ac79-405ff022e8247c46-01'*/
6767
68+
SQLComment in span attribute
69+
****************************
70+
If sqlcommenter is enabled, you can further configure SQLAlchemy instrumentation to append sqlcomment to the `db.statement` span attribute for convenience of your platform.
71+
72+
.. code:: python
73+
74+
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
75+
76+
SQLAlchemyInstrumentor().instrument(
77+
enable_commenter=True,
78+
commenter_options={},
79+
enable_attribute_commenter=True,
80+
)
81+
82+
83+
For example,
84+
::
85+
86+
Invoking `engine.execute("select * from auth_users")` will lead to sql query "select * from auth_users" but when SQLCommenter and `attribute_commenter` is enabled
87+
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.
88+
89+
Warning: capture of sqlcomment in ``db.statement`` may have high cardinality without platform normalization. See `Semantic Conventions for database spans <https://opentelemetry.io/docs/specs/semconv/database/database-spans/#generating-a-summary-of-the-query-text>`_ for more information.
90+
91+
6892
Usage
6993
-----
7094
.. code:: python
@@ -138,6 +162,7 @@ def _instrument(self, **kwargs):
138162
``meter_provider``: a MeterProvider, defaults to global
139163
``enable_commenter``: bool to enable sqlcommenter, defaults to False
140164
``commenter_options``: dict of sqlcommenter config, defaults to {}
165+
``enable_attribute_commenter``: bool to enable sqlcomment addition to span attribute, defaults to False. Must also set `enable_commenter`.
141166
142167
Returns:
143168
An instrumented engine if passed in as an argument or list of instrumented engines, None otherwise.
@@ -166,19 +191,30 @@ def _instrument(self, **kwargs):
166191

167192
enable_commenter = kwargs.get("enable_commenter", False)
168193
commenter_options = kwargs.get("commenter_options", {})
194+
enable_attribute_commenter = kwargs.get(
195+
"enable_attribute_commenter", False
196+
)
169197

170198
_w(
171199
"sqlalchemy",
172200
"create_engine",
173201
_wrap_create_engine(
174-
tracer, connections_usage, enable_commenter, commenter_options
202+
tracer,
203+
connections_usage,
204+
enable_commenter,
205+
commenter_options,
206+
enable_attribute_commenter,
175207
),
176208
)
177209
_w(
178210
"sqlalchemy.engine",
179211
"create_engine",
180212
_wrap_create_engine(
181-
tracer, connections_usage, enable_commenter, commenter_options
213+
tracer,
214+
connections_usage,
215+
enable_commenter,
216+
commenter_options,
217+
enable_attribute_commenter,
182218
),
183219
)
184220
# sqlalchemy.engine.create is not present in earlier versions of sqlalchemy (which we support)
@@ -191,6 +227,7 @@ def _instrument(self, **kwargs):
191227
connections_usage,
192228
enable_commenter,
193229
commenter_options,
230+
enable_attribute_commenter,
194231
),
195232
)
196233
_w(
@@ -207,6 +244,7 @@ def _instrument(self, **kwargs):
207244
connections_usage,
208245
enable_commenter,
209246
commenter_options,
247+
enable_attribute_commenter,
210248
),
211249
)
212250
if kwargs.get("engine") is not None:
@@ -216,6 +254,7 @@ def _instrument(self, **kwargs):
216254
connections_usage,
217255
kwargs.get("enable_commenter", False),
218256
kwargs.get("commenter_options", {}),
257+
kwargs.get("enable_attribute_commenter", False),
219258
)
220259
if kwargs.get("engines") is not None and isinstance(
221260
kwargs.get("engines"), Sequence
@@ -227,6 +266,7 @@ def _instrument(self, **kwargs):
227266
connections_usage,
228267
kwargs.get("enable_commenter", False),
229268
kwargs.get("commenter_options", {}),
269+
kwargs.get("enable_attribute_commenter", False),
230270
)
231271
for engine in kwargs.get("engines")
232272
]

‎instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py

+64-26
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ def _normalize_vendor(vendor):
4343

4444

4545
def _wrap_create_async_engine(
46-
tracer, connections_usage, enable_commenter=False, commenter_options=None
46+
tracer,
47+
connections_usage,
48+
enable_commenter=False,
49+
commenter_options=None,
50+
enable_attribute_commenter=False,
4751
):
4852
# pylint: disable=unused-argument
4953
def _wrap_create_async_engine_internal(func, module, args, kwargs):
@@ -57,14 +61,19 @@ def _wrap_create_async_engine_internal(func, module, args, kwargs):
5761
connections_usage,
5862
enable_commenter,
5963
commenter_options,
64+
enable_attribute_commenter,
6065
)
6166
return engine
6267

6368
return _wrap_create_async_engine_internal
6469

6570

6671
def _wrap_create_engine(
67-
tracer, connections_usage, enable_commenter=False, commenter_options=None
72+
tracer,
73+
connections_usage,
74+
enable_commenter=False,
75+
commenter_options=None,
76+
enable_attribute_commenter=False,
6877
):
6978
def _wrap_create_engine_internal(func, _module, args, kwargs):
7079
"""Trace the SQLAlchemy engine, creating an `EngineTracer`
@@ -77,6 +86,7 @@ def _wrap_create_engine_internal(func, _module, args, kwargs):
7786
connections_usage,
7887
enable_commenter,
7988
commenter_options,
89+
enable_attribute_commenter,
8090
)
8191
return engine
8292

@@ -110,12 +120,14 @@ def __init__(
110120
connections_usage,
111121
enable_commenter=False,
112122
commenter_options=None,
123+
enable_attribute_commenter=False,
113124
):
114125
self.tracer = tracer
115126
self.connections_usage = connections_usage
116127
self.vendor = _normalize_vendor(engine.name)
117128
self.enable_commenter = enable_commenter
118129
self.commenter_options = commenter_options if commenter_options else {}
130+
self.enable_attribute_commenter = enable_attribute_commenter
119131
self._engine_attrs = _get_attributes_from_engine(engine)
120132
self._leading_comment_remover = re.compile(r"^/\*.*?\*/")
121133

@@ -218,6 +230,32 @@ def _operation_name(self, db_name, statement):
218230
return self.vendor
219231
return " ".join(parts)
220232

233+
def _get_commenter_data(self, conn) -> dict:
234+
"""Calculate sqlcomment contents from conn and configured options"""
235+
commenter_data = {
236+
"db_driver": conn.engine.driver,
237+
# Driver/framework centric information.
238+
"db_framework": f"sqlalchemy:{sqlalchemy.__version__}",
239+
}
240+
241+
if self.commenter_options.get("opentelemetry_values", True):
242+
commenter_data.update(**_get_opentelemetry_values())
243+
244+
# Filter down to just the requested attributes.
245+
commenter_data = {
246+
k: v
247+
for k, v in commenter_data.items()
248+
if self.commenter_options.get(k, True)
249+
}
250+
return commenter_data
251+
252+
def _set_db_client_span_attributes(self, span, statement, attrs) -> None:
253+
"""Uses statement and attrs to set attributes of provided Otel span"""
254+
span.set_attribute(SpanAttributes.DB_STATEMENT, statement)
255+
span.set_attribute(SpanAttributes.DB_SYSTEM, self.vendor)
256+
for key, value in attrs.items():
257+
span.set_attribute(key, value)
258+
221259
def _before_cur_exec(
222260
self, conn, cursor, statement, params, context, _executemany
223261
):
@@ -233,30 +271,30 @@ def _before_cur_exec(
233271
with trace.use_span(span, end_on_exit=False):
234272
if span.is_recording():
235273
if self.enable_commenter:
236-
commenter_data = {
237-
"db_driver": conn.engine.driver,
238-
# Driver/framework centric information.
239-
"db_framework": f"sqlalchemy:{sqlalchemy.__version__}",
240-
}
241-
242-
if self.commenter_options.get(
243-
"opentelemetry_values", True
244-
):
245-
commenter_data.update(**_get_opentelemetry_values())
246-
247-
# Filter down to just the requested attributes.
248-
commenter_data = {
249-
k: v
250-
for k, v in commenter_data.items()
251-
if self.commenter_options.get(k, True)
252-
}
253-
254-
statement = _add_sql_comment(statement, **commenter_data)
255-
256-
span.set_attribute(SpanAttributes.DB_STATEMENT, statement)
257-
span.set_attribute(SpanAttributes.DB_SYSTEM, self.vendor)
258-
for key, value in attrs.items():
259-
span.set_attribute(key, value)
274+
commenter_data = self._get_commenter_data(conn)
275+
276+
if self.enable_attribute_commenter:
277+
# sqlcomment is added to executed query and db.statement span attribute
278+
statement = _add_sql_comment(
279+
statement, **commenter_data
280+
)
281+
self._set_db_client_span_attributes(
282+
span, statement, attrs
283+
)
284+
285+
else:
286+
# sqlcomment is only added to executed query
287+
# so db.statement is set before add_sql_comment
288+
self._set_db_client_span_attributes(
289+
span, statement, attrs
290+
)
291+
statement = _add_sql_comment(
292+
statement, **commenter_data
293+
)
294+
295+
else:
296+
# no sqlcomment anywhere
297+
self._set_db_client_span_attributes(span, statement, attrs)
260298

261299
context._otel_span = span
262300

0 commit comments

Comments
 (0)
Please sign in to comment.