Skip to content

Commit fbac424

Browse files
authored
Add psycopg2 native tags to sqlcommenter (#1203)
1 parent 7c75b38 commit fbac424

File tree

5 files changed

+123
-7
lines changed

5 files changed

+123
-7
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
([#1187](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1187))
1111
- SQLCommenter semicolon bug fix
1212
([#1200](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1200/files))
13+
- Add psycopg2 native tags to sqlcommenter
14+
([#1203](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1203))
1315

1416
### Added
1517
- `opentelemetry-instrumentation-redis` add support to instrument RedisCluster clients

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

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ def wrap_connect(
105105
capture_parameters: bool = False,
106106
enable_commenter: bool = False,
107107
db_api_integration_factory=None,
108+
commenter_options: dict = None,
108109
):
109110
"""Integrate with DB API library.
110111
https://www.python.org/dev/peps/pep-0249/
@@ -119,6 +120,8 @@ def wrap_connect(
119120
tracer_provider: The :class:`opentelemetry.trace.TracerProvider` to
120121
use. If omitted the current configured one is used.
121122
capture_parameters: Configure if db.statement.parameters should be captured.
123+
enable_commenter: Flag to enable/disable sqlcommenter.
124+
commenter_options: Configurations for tags to be appended at the sql query.
122125
123126
"""
124127
db_api_integration_factory = (
@@ -140,6 +143,8 @@ def wrap_connect_(
140143
tracer_provider=tracer_provider,
141144
capture_parameters=capture_parameters,
142145
enable_commenter=enable_commenter,
146+
commenter_options=commenter_options,
147+
connect_module=connect_module,
143148
)
144149
return db_integration.wrapped_connection(wrapped, args, kwargs)
145150

@@ -173,6 +178,7 @@ def instrument_connection(
173178
tracer_provider: typing.Optional[TracerProvider] = None,
174179
capture_parameters: bool = False,
175180
enable_commenter: bool = False,
181+
commenter_options: dict = None,
176182
):
177183
"""Enable instrumentation in a database connection.
178184
@@ -185,6 +191,9 @@ def instrument_connection(
185191
tracer_provider: The :class:`opentelemetry.trace.TracerProvider` to
186192
use. If omitted the current configured one is used.
187193
capture_parameters: Configure if db.statement.parameters should be captured.
194+
enable_commenter: Flag to enable/disable sqlcommenter.
195+
commenter_options: Configurations for tags to be appended at the sql query.
196+
188197
Returns:
189198
An instrumented connection.
190199
"""
@@ -200,6 +209,7 @@ def instrument_connection(
200209
tracer_provider=tracer_provider,
201210
capture_parameters=capture_parameters,
202211
enable_commenter=enable_commenter,
212+
commenter_options=commenter_options,
203213
)
204214
db_integration.get_connection_attributes(connection)
205215
return get_traced_connection_proxy(connection, db_integration)
@@ -231,6 +241,8 @@ def __init__(
231241
tracer_provider: typing.Optional[TracerProvider] = None,
232242
capture_parameters: bool = False,
233243
enable_commenter: bool = False,
244+
commenter_options: dict = None,
245+
connect_module: typing.Callable[..., typing.Any] = None,
234246
):
235247
self.connection_attributes = connection_attributes
236248
if self.connection_attributes is None:
@@ -249,11 +261,13 @@ def __init__(
249261
)
250262
self.capture_parameters = capture_parameters
251263
self.enable_commenter = enable_commenter
264+
self.commenter_options = commenter_options
252265
self.database_system = database_system
253266
self.connection_props = {}
254267
self.span_attributes = {}
255268
self.name = ""
256269
self.database = ""
270+
self.connect_module = connect_module
257271

258272
def wrapped_connection(
259273
self,
@@ -335,12 +349,18 @@ class CursorTracer:
335349
def __init__(self, db_api_integration: DatabaseApiIntegration) -> None:
336350
self._db_api_integration = db_api_integration
337351
self._commenter_enabled = self._db_api_integration.enable_commenter
352+
self._commenter_options = (
353+
self._db_api_integration.commenter_options
354+
if self._db_api_integration.commenter_options
355+
else {}
356+
)
357+
self._connect_module = self._db_api_integration.connect_module
338358

339359
def _populate_span(
340360
self,
341361
span: trace_api.Span,
342362
cursor,
343-
*args: typing.Tuple[typing.Any, typing.Any]
363+
*args: typing.Tuple[typing.Any, typing.Any],
344364
):
345365
if not span.is_recording():
346366
return
@@ -380,7 +400,7 @@ def traced_execution(
380400
cursor,
381401
query_method: typing.Callable[..., typing.Any],
382402
*args: typing.Tuple[typing.Any, typing.Any],
383-
**kwargs: typing.Dict[typing.Any, typing.Any]
403+
**kwargs: typing.Dict[typing.Any, typing.Any],
384404
):
385405
name = self.get_operation_name(cursor, args)
386406
if not name:
@@ -397,14 +417,32 @@ def traced_execution(
397417
if args and self._commenter_enabled:
398418
try:
399419
args_list = list(args)
400-
commenter_data = {}
401-
commenter_data.update(_get_opentelemetry_values())
420+
commenter_data = dict(
421+
# Psycopg2/framework information
422+
db_driver=f"psycopg2:{self._connect_module.__version__.split(' ')[0]}",
423+
dbapi_threadsafety=self._connect_module.threadsafety,
424+
dbapi_level=self._connect_module.apilevel,
425+
libpq_version=self._connect_module.__libpq_version__,
426+
driver_paramstyle=self._connect_module.paramstyle,
427+
)
428+
if self._commenter_options.get(
429+
"opentelemetry_values", True
430+
):
431+
commenter_data.update(**_get_opentelemetry_values())
432+
433+
# Filter down to just the requested attributes.
434+
commenter_data = {
435+
k: v
436+
for k, v in commenter_data.items()
437+
if self._commenter_options.get(k, True)
438+
}
402439
statement = _add_sql_comment(
403440
args_list[0], **commenter_data
404441
)
405442

406443
args_list[0] = statement
407444
args = tuple(args_list)
445+
408446
except Exception as exc: # pylint: disable=broad-except
409447
_logger.exception(
410448
"Exception while generating sql comment: %s", exc

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,20 @@ def test_executemany(self):
229229
)
230230

231231
def test_executemany_comment(self):
232+
233+
connect_module = mock.MagicMock()
234+
connect_module.__version__ = mock.MagicMock()
235+
connect_module.__libpq_version__ = 123
236+
connect_module.apilevel = 123
237+
connect_module.threadsafety = 123
238+
connect_module.paramstyle = "test"
239+
232240
db_integration = dbapi.DatabaseApiIntegration(
233-
"testname", "testcomponent", enable_commenter=True
241+
"testname",
242+
"testcomponent",
243+
enable_commenter=True,
244+
commenter_options={"db_driver": False, "dbapi_level": False},
245+
connect_module=connect_module,
234246
)
235247
mock_connection = db_integration.wrapped_connection(
236248
mock_connect, {}, {}
@@ -239,7 +251,7 @@ def test_executemany_comment(self):
239251
cursor.executemany("Select 1;")
240252
self.assertRegex(
241253
cursor.query,
242-
r"Select 1 /\*traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
254+
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}'\*/;",
243255
)
244256

245257
def test_callproc(self):

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

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,68 @@
1818
1919
.. _Psycopg: http://initd.org/psycopg/
2020
21+
SQLCOMMENTER
22+
*****************************************
23+
You can optionally configure Psycopg2 instrumentation to enable sqlcommenter which enriches
24+
the query with contextual information.
25+
26+
Usage
27+
-----
28+
29+
.. code:: python
30+
31+
from opentelemetry.instrumentation.psycopg2 import Psycopg2Instrumentor
32+
33+
Psycopg2Instrumentor().instrument(enable_commenter=True, commenter_options={})
34+
35+
36+
For example,
37+
::
38+
39+
Invoking cursor.execute("select * from auth_users") will lead to sql query "select * from auth_users" but when SQLCommenter is enabled
40+
the query will get appended with some configurable tags like "select * from auth_users /*tag=value*/;"
41+
42+
43+
SQLCommenter Configurations
44+
***************************
45+
We can configure the tags to be appended to the sqlquery log by adding configuration inside commenter_options(default:{}) keyword
46+
47+
db_driver = True(Default) or False
48+
49+
For example,
50+
::
51+
Enabling this flag will add psycopg2 and it's version which is /*psycopg2%%3A2.9.3*/
52+
53+
dbapi_threadsafety = True(Default) or False
54+
55+
For example,
56+
::
57+
Enabling this flag will add threadsafety /*dbapi_threadsafety=2*/
58+
59+
dbapi_level = True(Default) or False
60+
61+
For example,
62+
::
63+
Enabling this flag will add dbapi_level /*dbapi_level='2.0'*/
64+
65+
libpq_version = True(Default) or False
66+
67+
For example,
68+
::
69+
Enabling this flag will add libpq_version /*libpq_version=140001*/
70+
71+
driver_paramstyle = True(Default) or False
72+
73+
For example,
74+
::
75+
Enabling this flag will add driver_paramstyle /*driver_paramstyle='pyformat'*/
76+
77+
opentelemetry_values = True(Default) or False
78+
79+
For example,
80+
::
81+
Enabling this flag will add traceparent values /*traceparent='00-03afa25236b8cd948fa853d67038ac79-405ff022e8247c46-01'*/
82+
2183
Usage
2284
-----
2385
@@ -77,6 +139,7 @@ def _instrument(self, **kwargs):
77139
"""
78140
tracer_provider = kwargs.get("tracer_provider")
79141
enable_sqlcommenter = kwargs.get("enable_commenter", False)
142+
commenter_options = kwargs.get("commenter_options", {})
80143
dbapi.wrap_connect(
81144
__name__,
82145
psycopg2,
@@ -87,6 +150,7 @@ def _instrument(self, **kwargs):
87150
tracer_provider=tracer_provider,
88151
db_api_integration_factory=DatabaseApiIntegration,
89152
enable_commenter=enable_sqlcommenter,
153+
commenter_options=commenter_options,
90154
)
91155

92156
def _uninstrument(self, **kwargs):

tests/opentelemetry-docker-tests/tests/postgres/test_psycopg_sqlcommenter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,5 @@ def test_commenter_enabled(self):
5050
self._cursor.execute("SELECT 1;")
5151
self.assertRegex(
5252
self._cursor.query.decode("ascii"),
53-
r"SELECT 1 /\*traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
53+
r"SELECT 1 /\*db_driver='psycopg2(.*)',dbapi_level='\d.\d',dbapi_threadsafety=\d,driver_paramstyle=(.*),libpq_version=\d*,traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
5454
)

0 commit comments

Comments
 (0)