Skip to content

Commit 48f238c

Browse files
db.statement inclusion of sqlcomment as opt-in
1 parent 770003d commit 48f238c

File tree

3 files changed

+97
-6
lines changed

3 files changed

+97
-6
lines changed

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

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,28 @@
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 optionally configure SQLAlchemy instrumentation to append sqlcomment to query 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+
6890
Usage
6991
-----
7092
.. code:: python
@@ -138,6 +160,7 @@ def _instrument(self, **kwargs):
138160
``meter_provider``: a MeterProvider, defaults to global
139161
``enable_commenter``: bool to enable sqlcommenter, defaults to False
140162
``commenter_options``: dict of sqlcommenter config, defaults to {}
163+
``enable_attribute_commenter``: bool to enable sqlcomment addition to span attribute, defaults to False. Must also set `enable_commenter`.
141164
142165
Returns:
143166
An instrumented engine if passed in as an argument or list of instrumented engines, None otherwise.
@@ -166,19 +189,30 @@ def _instrument(self, **kwargs):
166189

167190
enable_commenter = kwargs.get("enable_commenter", False)
168191
commenter_options = kwargs.get("commenter_options", {})
192+
enable_attribute_commenter = kwargs.get(
193+
"enable_attribute_commenter", False
194+
)
169195

170196
_w(
171197
"sqlalchemy",
172198
"create_engine",
173199
_wrap_create_engine(
174-
tracer, connections_usage, enable_commenter, commenter_options
200+
tracer,
201+
connections_usage,
202+
enable_commenter,
203+
commenter_options,
204+
enable_attribute_commenter,
175205
),
176206
)
177207
_w(
178208
"sqlalchemy.engine",
179209
"create_engine",
180210
_wrap_create_engine(
181-
tracer, connections_usage, enable_commenter, commenter_options
211+
tracer,
212+
connections_usage,
213+
enable_commenter,
214+
commenter_options,
215+
enable_attribute_commenter,
182216
),
183217
)
184218
# sqlalchemy.engine.create is not present in earlier versions of sqlalchemy (which we support)
@@ -191,6 +225,7 @@ def _instrument(self, **kwargs):
191225
connections_usage,
192226
enable_commenter,
193227
commenter_options,
228+
enable_attribute_commenter,
194229
),
195230
)
196231
_w(
@@ -207,6 +242,7 @@ def _instrument(self, **kwargs):
207242
connections_usage,
208243
enable_commenter,
209244
commenter_options,
245+
enable_attribute_commenter,
210246
),
211247
)
212248
if kwargs.get("engine") is not None:
@@ -216,6 +252,7 @@ def _instrument(self, **kwargs):
216252
connections_usage,
217253
kwargs.get("enable_commenter", False),
218254
kwargs.get("commenter_options", {}),
255+
kwargs.get("enable_attribute_commenter", False),
219256
)
220257
if kwargs.get("engines") is not None and isinstance(
221258
kwargs.get("engines"), Sequence
@@ -227,6 +264,7 @@ def _instrument(self, **kwargs):
227264
connections_usage,
228265
kwargs.get("enable_commenter", False),
229266
kwargs.get("commenter_options", {}),
267+
kwargs.get("enable_attribute_commenter", False),
230268
)
231269
for engine in kwargs.get("engines")
232270
]

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

Lines changed: 24 additions & 3 deletions
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

@@ -251,13 +263,22 @@ def _before_cur_exec(
251263
if self.commenter_options.get(k, True)
252264
}
253265

254-
statement = _add_sql_comment(statement, **commenter_data)
266+
if self.enable_attribute_commenter:
267+
statement = _add_sql_comment(
268+
statement, **commenter_data
269+
)
255270

256271
span.set_attribute(SpanAttributes.DB_STATEMENT, statement)
257272
span.set_attribute(SpanAttributes.DB_SYSTEM, self.vendor)
258273
for key, value in attrs.items():
259274
span.set_attribute(key, value)
260275

276+
if (
277+
self.enable_commenter
278+
and not self.enable_attribute_commenter
279+
):
280+
statement = _add_sql_comment(statement, **commenter_data)
281+
261282
context._otel_span = span
262283

263284
return statement, params

instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlcommenter.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,45 @@ def test_sqlcommenter_enabled(self):
6161
r"SELECT 1 /\*db_driver='(.*)',traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
6262
)
6363

64-
def test_sqlcommenter_enabled_matches_db_statement_attribute(self):
64+
def test_sqlcommenter_enabled_stmt_disabled_default_matches_db_statement_attribute(
65+
self,
66+
):
6567
engine = create_engine("sqlite:///:memory:")
6668
SQLAlchemyInstrumentor().instrument(
6769
engine=engine,
6870
tracer_provider=self.tracer_provider,
6971
enable_commenter=True,
7072
commenter_options={"db_framework": False},
73+
# enable_attribute_commenter not set
74+
)
75+
cnx = engine.connect()
76+
cnx.execute(text("SELECT 1;")).fetchall()
77+
query_log = self.caplog.records[-2].getMessage()
78+
self.assertRegex(
79+
query_log,
80+
r"SELECT 1 /\*db_driver='(.*)',traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
81+
)
82+
spans = self.memory_exporter.get_finished_spans()
83+
self.assertEqual(len(spans), 2)
84+
# first span is connection to db
85+
self.assertEqual(spans[0].name, "connect")
86+
# second span is query itself
87+
query_span = spans[1]
88+
self.assertEqual(
89+
query_span.attributes[SpanAttributes.DB_STATEMENT],
90+
"SELECT 1;",
91+
)
92+
93+
def test_sqlcommenter_enabled_stmt_enabled_matches_db_statement_attribute(
94+
self,
95+
):
96+
engine = create_engine("sqlite:///:memory:")
97+
SQLAlchemyInstrumentor().instrument(
98+
engine=engine,
99+
tracer_provider=self.tracer_provider,
100+
enable_commenter=True,
101+
commenter_options={"db_framework": False},
102+
enable_attribute_commenter=True,
71103
)
72104
cnx = engine.connect()
73105
cnx.execute(text("SELECT 1;")).fetchall()

0 commit comments

Comments
 (0)