Skip to content

Commit b82dd71

Browse files
authored
Merge branch 'main' into feature/genai-genai-spec-updates
2 parents 48f5e3a + 613d33a commit b82dd71

File tree

5 files changed

+221
-5
lines changed

5 files changed

+221
-5
lines changed

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2020

2121
### Added
2222

23-
- `opentelemetry-instrumentation`: botocore: Add support for AWS Secrets Manager semantic convention attribute
23+
- `opentelemetry-instrumentation-botocore`: Add support for AWS Secrets Manager semantic convention attribute
2424
([#3765](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3765))
25+
- `opentelemetry-instrumentation-dbapi`: Add support for `commenter_options` in `trace_integration` function to control SQLCommenter behavior
26+
([#3743](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3743))
2527
- Add `rstcheck` to pre-commit to stop introducing invalid RST
2628
([#3777](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3777))
2729
- `opentelemetry-exporter-credential-provider-gcp`: create this package which provides support for supplying your machine's Application Default
2830
Credentials (https://cloud.google.com/docs/authentication/application-default-credentials) to the OTLP Exporters created automatically by OpenTelemetry Python's auto instrumentation. These credentials authorize OTLP traces to be sent to `telemetry.googleapis.com`. [#3766](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3766).
31+
- `opentelemetry-instrumentation-psycopg`: Add missing parameter `capture_parameters` to instrumentor.
32+
([#3676](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3676))
33+
- `opentelemetry-instrumentation-dbapi`: Adds sqlcommenter to documentation.
34+
([#3720](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3720))
2935

3036
## Version 1.37.0/0.58b0 (2025-09-11)
3137

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

Lines changed: 136 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,148 @@
2020
Usage
2121
-----
2222
23+
The DB-API instrumentor and its utilities provide common, core functionality for
24+
database framework or object relation mapper (ORM) instrumentations. Users will
25+
typically instrument database client code with those framework/ORM-specific
26+
instrumentations, instead of directly using this DB-API integration. Features
27+
such as sqlcommenter can be configured at framework/ORM level as well. See full
28+
list at `instrumentation`_.
29+
30+
.. _instrumentation: https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation
31+
32+
If an instrumentation for your needs does not exist, then DB-API integration can
33+
be used directly as follows.
34+
35+
2336
.. code-block:: python
2437
2538
import mysql.connector
2639
import pyodbc
2740
28-
from opentelemetry.instrumentation.dbapi import trace_integration
29-
41+
from opentelemetry.instrumentation.dbapi import (
42+
trace_integration,
43+
wrap_connect,
44+
)
3045
31-
# Ex: mysql.connector
46+
# Example: mysql.connector
3247
trace_integration(mysql.connector, "connect", "mysql")
33-
# Ex: pyodbc
48+
# Example: pyodbc
3449
trace_integration(pyodbc, "Connection", "odbc")
3550
51+
# Or, directly call wrap_connect for more configurability.
52+
wrap_connect(__name__, mysql.connector, "connect", "mysql")
53+
wrap_connect(__name__, pyodbc, "Connection", "odbc")
54+
55+
56+
Configuration
57+
-------------
58+
59+
SQLCommenter
60+
************
61+
You can optionally enable sqlcommenter which enriches the query with contextual
62+
information. Queries made after setting up trace integration with sqlcommenter
63+
enabled will have configurable key-value pairs appended to them, e.g.
64+
``"select * from auth_users; /*traceparent=00-01234567-abcd-01*/"``. This
65+
supports context propagation between database client and server when database log
66+
records are enabled. For more information, see:
67+
68+
* `Semantic Conventions - Database Spans <https://github.com/open-telemetry/semantic-conventions/blob/main/docs/database/database-spans.md#sql-commenter>`_
69+
* `sqlcommenter <https://google.github.io/sqlcommenter/>`_
70+
71+
.. code:: python
72+
73+
import mysql.connector
74+
75+
from opentelemetry.instrumentation.dbapi import wrap_connect
76+
77+
78+
# Opts into sqlcomment for MySQL trace integration.
79+
wrap_connect(
80+
__name__,
81+
mysql.connector,
82+
"connect",
83+
"mysql",
84+
enable_commenter=True,
85+
)
86+
87+
88+
SQLCommenter with commenter_options
89+
***********************************
90+
The key-value pairs appended to the query can be configured using
91+
``commenter_options``. When sqlcommenter is enabled, all available KVs/tags
92+
are calculated by default. ``commenter_options`` supports *opting out*
93+
of specific KVs.
94+
95+
.. code:: python
96+
97+
import mysql.connector
98+
99+
from opentelemetry.instrumentation.dbapi import wrap_connect
100+
101+
102+
# Opts into sqlcomment for MySQL trace integration.
103+
# Opts out of tags for libpq_version, db_driver.
104+
wrap_connect(
105+
__name__,
106+
mysql.connector,
107+
"connect",
108+
"mysql",
109+
enable_commenter=True,
110+
commenter_options={
111+
"libpq_version": False,
112+
"db_driver": False,
113+
}
114+
)
115+
116+
Available commenter_options
117+
###########################
118+
119+
The following sqlcomment key-values can be opted out of through ``commenter_options``:
120+
121+
+---------------------------+-----------------------------------------------------------+---------------------------------------------------------------------------+
122+
| Commenter Option | Description | Example |
123+
+===========================+===========================================================+===========================================================================+
124+
| ``db_driver`` | Database driver name with version. | ``mysql.connector=2.2.9`` |
125+
+---------------------------+-----------------------------------------------------------+---------------------------------------------------------------------------+
126+
| ``dbapi_threadsafety`` | DB-API threadsafety value: 0-3 or unknown. | ``dbapi_threadsafety=2`` |
127+
+---------------------------+-----------------------------------------------------------+---------------------------------------------------------------------------+
128+
| ``dbapi_level`` | DB-API API level: 1.0, 2.0, or unknown. | ``dbapi_level=2.0`` |
129+
+---------------------------+-----------------------------------------------------------+---------------------------------------------------------------------------+
130+
| ``driver_paramstyle`` | DB-API paramstyle for SQL statement parameter. | ``driver_paramstyle='pyformat'`` |
131+
+---------------------------+-----------------------------------------------------------+---------------------------------------------------------------------------+
132+
| ``libpq_version`` | PostgreSQL libpq version (checked for PostgreSQL only). | ``libpq_version=140001`` |
133+
+---------------------------+-----------------------------------------------------------+---------------------------------------------------------------------------+
134+
| ``mysql_client_version`` | MySQL client version (checked for MySQL only). | ``mysql_client_version='123'`` |
135+
+---------------------------+-----------------------------------------------------------+---------------------------------------------------------------------------+
136+
| ``opentelemetry_values`` | OpenTelemetry context as traceparent at time of query. | ``traceparent='00-03afa25236b8cd948fa853d67038ac79-405ff022e8247c46-01'`` |
137+
+---------------------------+-----------------------------------------------------------+---------------------------------------------------------------------------+
138+
139+
SQLComment in span attribute
140+
****************************
141+
If sqlcommenter is enabled, you can opt into the inclusion of sqlcomment in
142+
the query span ``db.statement`` attribute for your needs. If ``commenter_options``
143+
have been set, the span attribute comment will also be configured by this
144+
setting.
145+
146+
.. code:: python
147+
148+
import mysql.connector
149+
150+
from opentelemetry.instrumentation.dbapi import wrap_connect
151+
152+
153+
# Opts into sqlcomment for MySQL trace integration.
154+
# Opts into sqlcomment for `db.statement` span attribute.
155+
wrap_connect(
156+
__name__,
157+
mysql.connector,
158+
"connect",
159+
"mysql",
160+
enable_commenter=True,
161+
enable_attribute_commenter=True,
162+
)
163+
164+
36165
API
37166
---
38167
"""
@@ -79,6 +208,7 @@ def trace_integration(
79208
enable_commenter: bool = False,
80209
db_api_integration_factory: type[DatabaseApiIntegration] | None = None,
81210
enable_attribute_commenter: bool = False,
211+
commenter_options: dict[str, Any] | None = None,
82212
):
83213
"""Integrate with DB API library.
84214
https://www.python.org/dev/peps/pep-0249/
@@ -97,6 +227,7 @@ def trace_integration(
97227
db_api_integration_factory: The `DatabaseApiIntegration` to use. If none is passed the
98228
default one is used.
99229
enable_attribute_commenter: Flag to enable/disable sqlcomment inclusion in `db.statement` span attribute. Only available if enable_commenter=True.
230+
commenter_options: Configurations for tags to be appended at the sql query.
100231
"""
101232
wrap_connect(
102233
__name__,
@@ -110,6 +241,7 @@ def trace_integration(
110241
enable_commenter=enable_commenter,
111242
db_api_integration_factory=db_api_integration_factory,
112243
enable_attribute_commenter=enable_attribute_commenter,
244+
commenter_options=commenter_options,
113245
)
114246

115247

instrumentation/opentelemetry-instrumentation-dbapi/tests/test_dbapi_integration.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,45 @@ def test_suppress_instrumentation(self):
259259
spans_list = self.memory_exporter.get_finished_spans()
260260
self.assertEqual(len(spans_list), 0)
261261

262+
def test_commenter_options_propagation(self):
263+
db_integration = dbapi.DatabaseApiIntegration(
264+
"instrumenting_module_test_name",
265+
"testcomponent",
266+
commenter_options={"opentelemetry_values": False},
267+
)
268+
mock_connection = db_integration.wrapped_connection(
269+
mock_connect, {}, {}
270+
)
271+
cursor = mock_connection.cursor()
272+
with self.assertRaises(Exception):
273+
cursor.execute("SELECT 1", throw_exception=True)
274+
spans_list = self.memory_exporter.get_finished_spans()
275+
self.assertEqual(len(spans_list), 1)
276+
span = spans_list[0]
277+
self.assertEqual(span.attributes.get("db.system"), "testcomponent")
278+
self.assertIn("opentelemetry_values", db_integration.commenter_options)
279+
self.assertFalse(
280+
db_integration.commenter_options["opentelemetry_values"]
281+
)
282+
283+
@mock.patch("opentelemetry.instrumentation.dbapi.wrap_connect")
284+
def test_trace_integration_passes_commenter_options(
285+
self, mock_wrap_connect
286+
):
287+
fake_connect_module = mock.Mock()
288+
fake_options = {"opentelemetry_values": False, "foo": "bar"}
289+
dbapi.trace_integration(
290+
connect_module=fake_connect_module,
291+
connect_method_name="connect",
292+
database_system="testdb",
293+
commenter_options=fake_options,
294+
)
295+
mock_wrap_connect.assert_called_once()
296+
_, _, kwargs = mock_wrap_connect.mock_calls[0]
297+
298+
self.assertIn("commenter_options", kwargs)
299+
self.assertEqual(kwargs["commenter_options"], fake_options)
300+
262301
def test_executemany(self):
263302
db_integration = dbapi.DatabaseApiIntegration(
264303
"instrumenting_module_test_name", "testcomponent"

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ def _instrument(self, **kwargs: Any):
184184
enable_attribute_commenter = kwargs.get(
185185
"enable_attribute_commenter", False
186186
)
187+
capture_parameters = kwargs.get("capture_parameters", False)
187188
dbapi.wrap_connect(
188189
__name__,
189190
psycopg,
@@ -196,6 +197,7 @@ def _instrument(self, **kwargs: Any):
196197
enable_commenter=enable_sqlcommenter,
197198
commenter_options=commenter_options,
198199
enable_attribute_commenter=enable_attribute_commenter,
200+
capture_parameters=capture_parameters,
199201
)
200202

201203
dbapi.wrap_connect(
@@ -210,6 +212,7 @@ def _instrument(self, **kwargs: Any):
210212
enable_commenter=enable_sqlcommenter,
211213
commenter_options=commenter_options,
212214
enable_attribute_commenter=enable_attribute_commenter,
215+
capture_parameters=capture_parameters,
213216
)
214217
dbapi.wrap_connect(
215218
__name__,
@@ -223,6 +226,7 @@ def _instrument(self, **kwargs: Any):
223226
enable_commenter=enable_sqlcommenter,
224227
commenter_options=commenter_options,
225228
enable_attribute_commenter=enable_attribute_commenter,
229+
capture_parameters=capture_parameters,
226230
)
227231

228232
def _uninstrument(self, **kwargs: Any):

instrumentation/opentelemetry-instrumentation-psycopg/tests/test_psycopg_integration.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,24 @@ def test_span_name(self):
264264
self.assertEqual(spans_list[6].name, "postgresql")
265265
self.assertEqual(spans_list[7].name, "--")
266266

267+
def test_span_params_attribute(self):
268+
PsycopgInstrumentor().instrument(capture_parameters=True)
269+
cnx = psycopg.connect(database="test")
270+
query = "SELECT * FROM mytable WHERE myparam1 = %s AND myparam2 = %s"
271+
params = ("test", 42)
272+
273+
cursor = cnx.cursor()
274+
275+
cursor.execute(query, params)
276+
spans_list = self.memory_exporter.get_finished_spans()
277+
self.assertEqual(len(spans_list), 1)
278+
self.assertEqual(spans_list[0].name, "SELECT")
279+
assert spans_list[0].attributes is not None
280+
self.assertEqual(spans_list[0].attributes["db.statement"], query)
281+
self.assertEqual(
282+
spans_list[0].attributes["db.statement.parameters"], str(params)
283+
)
284+
267285
# pylint: disable=unused-argument
268286
def test_not_recording(self):
269287
mock_tracer = mock.Mock()
@@ -479,6 +497,23 @@ async def test_span_name_async(self):
479497
self.assertEqual(spans_list[4].name, "query")
480498
self.assertEqual(spans_list[5].name, "query")
481499

500+
async def test_span_params_attribute(self):
501+
PsycopgInstrumentor().instrument(capture_parameters=True)
502+
cnx = await psycopg.AsyncConnection.connect("test")
503+
query = "SELECT * FROM mytable WHERE myparam1 = %s AND myparam2 = %s"
504+
params = ("test", 42)
505+
async with cnx.cursor() as cursor:
506+
await cursor.execute(query, params)
507+
508+
spans_list = self.memory_exporter.get_finished_spans()
509+
self.assertEqual(len(spans_list), 1)
510+
self.assertEqual(spans_list[0].name, "SELECT")
511+
assert spans_list[0].attributes is not None
512+
self.assertEqual(spans_list[0].attributes["db.statement"], query)
513+
self.assertEqual(
514+
spans_list[0].attributes["db.statement.parameters"], str(params)
515+
)
516+
482517
# pylint: disable=unused-argument
483518
async def test_not_recording_async(self):
484519
mock_tracer = mock.Mock()

0 commit comments

Comments
 (0)