Skip to content

Commit d3bd5ee

Browse files
Merge branch 'main' into instrument-connection-optional-connect-module
2 parents bd6be9c + 19a59e4 commit d3bd5ee

File tree

8 files changed

+222
-89
lines changed

8 files changed

+222
-89
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717
([#2976](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2976))
1818
- Add `opentelemetry-instrumentation-openai-v2` to `opentelemetry-bootstrap`
1919
([#2996](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2996))
20+
- `opentelemetry-instrumentation-sqlalchemy` Add sqlcomment to `db.statement` attribute
21+
([#2937](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2937))
22+
- `opentelemetry-instrumentation-dbapi` Add sqlcomment to `db.statement` attribute
23+
([#2935](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2935))
2024

2125
### Fixed
2226

2327
- `opentelemetry-instrumentation-httpx`: instrument_client is a static method again
2428
([#3003](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3003))
29+
- `opentelemetry-instrumentation-httpx`: Check if mount transport is none before wrap it
30+
([#3022](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3022))
2531

2632
### Breaking changes
2733

instrumentation/opentelemetry-instrumentation-confluent-kafka/README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,5 @@ Installation
1919
References
2020
----------
2121

22-
* `OpenTelemetry confluent-kafka/ Tracing <https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/confluent-kafka/confluent-kafka.html>`_
22+
* `OpenTelemetry confluent-kafka/ Tracing <https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/confluent_kafka/confluent_kafka.html>`_
2323
* `OpenTelemetry Project <https://opentelemetry.io/>`_

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

Lines changed: 48 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -495,49 +495,54 @@ def traced_execution(
495495
with self._db_api_integration._tracer.start_as_current_span(
496496
name, kind=SpanKind.CLIENT
497497
) as span:
498-
self._populate_span(span, cursor, *args)
499-
if args and self._commenter_enabled:
500-
try:
501-
args_list = list(args)
502-
503-
# lazy capture of mysql-connector client version using cursor
504-
if (
505-
self._db_api_integration.database_system == "mysql"
506-
and self._db_api_integration.connect_module.__name__
507-
== "mysql.connector"
508-
and not self._db_api_integration.commenter_data[
509-
"mysql_client_version"
510-
]
511-
):
512-
self._db_api_integration.commenter_data[
513-
"mysql_client_version"
514-
] = cursor._cnx._cmysql.get_client_info()
515-
516-
commenter_data = dict(
517-
self._db_api_integration.commenter_data
518-
)
519-
if self._commenter_options.get(
520-
"opentelemetry_values", True
521-
):
522-
commenter_data.update(**_get_opentelemetry_values())
523-
524-
# Filter down to just the requested attributes.
525-
commenter_data = {
526-
k: v
527-
for k, v in commenter_data.items()
528-
if self._commenter_options.get(k, True)
529-
}
530-
statement = _add_sql_comment(
531-
args_list[0], **commenter_data
532-
)
533-
534-
args_list[0] = statement
535-
args = tuple(args_list)
536-
537-
except Exception as exc: # pylint: disable=broad-except
538-
_logger.exception(
539-
"Exception while generating sql comment: %s", exc
540-
)
498+
if span.is_recording():
499+
if args and self._commenter_enabled:
500+
try:
501+
args_list = list(args)
502+
503+
# lazy capture of mysql-connector client version using cursor
504+
if (
505+
self._db_api_integration.database_system == "mysql"
506+
and self._db_api_integration.connect_module.__name__
507+
== "mysql.connector"
508+
and not self._db_api_integration.commenter_data[
509+
"mysql_client_version"
510+
]
511+
):
512+
self._db_api_integration.commenter_data[
513+
"mysql_client_version"
514+
] = cursor._cnx._cmysql.get_client_info()
515+
516+
commenter_data = dict(
517+
self._db_api_integration.commenter_data
518+
)
519+
if self._commenter_options.get(
520+
"opentelemetry_values", True
521+
):
522+
commenter_data.update(
523+
**_get_opentelemetry_values()
524+
)
525+
526+
# Filter down to just the requested attributes.
527+
commenter_data = {
528+
k: v
529+
for k, v in commenter_data.items()
530+
if self._commenter_options.get(k, True)
531+
}
532+
statement = _add_sql_comment(
533+
args_list[0], **commenter_data
534+
)
535+
536+
args_list[0] = statement
537+
args = tuple(args_list)
538+
539+
except Exception as exc: # pylint: disable=broad-except
540+
_logger.exception(
541+
"Exception while generating sql comment: %s", exc
542+
)
543+
544+
self._populate_span(span, cursor, *args)
545+
541546
return query_method(*args, **kwargs)
542547

543548

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515

1616
import logging
17+
import re
1718
from unittest import mock
1819

1920
from opentelemetry import context
@@ -306,6 +307,44 @@ def __getattr__(self, name):
306307
r"Select 1 /\*dbapi_level='1.0',dbapi_threadsafety='unknown',driver_paramstyle='unknown',libpq_version=123,traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;",
307308
)
308309

310+
def test_executemany_comment_matches_db_statement_attribute(self):
311+
connect_module = mock.MagicMock()
312+
connect_module.__version__ = mock.MagicMock()
313+
connect_module.__libpq_version__ = 123
314+
connect_module.apilevel = 123
315+
connect_module.threadsafety = 123
316+
connect_module.paramstyle = "test"
317+
318+
db_integration = dbapi.DatabaseApiIntegration(
319+
"testname",
320+
"postgresql",
321+
enable_commenter=True,
322+
commenter_options={"db_driver": False, "dbapi_level": False},
323+
connect_module=connect_module,
324+
)
325+
mock_connection = db_integration.wrapped_connection(
326+
mock_connect, {}, {}
327+
)
328+
cursor = mock_connection.cursor()
329+
cursor.executemany("Select 1;")
330+
self.assertRegex(
331+
cursor.query,
332+
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}'\*/;",
333+
)
334+
spans_list = self.memory_exporter.get_finished_spans()
335+
self.assertEqual(len(spans_list), 1)
336+
span = spans_list[0]
337+
self.assertRegex(
338+
span.attributes[SpanAttributes.DB_STATEMENT],
339+
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}'\*/",
340+
)
341+
342+
cursor_span_id = re.search(r"[a-zA-Z0-9_]{16}", cursor.query).group()
343+
db_statement_span_id = re.search(
344+
r"[a-zA-Z0-9_]{16}", span.attributes[SpanAttributes.DB_STATEMENT]
345+
).group()
346+
self.assertEqual(cursor_span_id, db_statement_span_id)
347+
309348
def test_compatible_build_version_psycopg_psycopg2_libpq(self):
310349
connect_module = mock.MagicMock()
311350
connect_module.__name__ = "test"

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

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,17 +1005,18 @@ def instrument_client(
10051005
),
10061006
)
10071007
for transport in client._mounts.values():
1008-
wrap_function_wrapper(
1009-
transport,
1010-
"handle_request",
1011-
partial(
1012-
cls._handle_request_wrapper,
1013-
tracer=tracer,
1014-
sem_conv_opt_in_mode=sem_conv_opt_in_mode,
1015-
request_hook=request_hook,
1016-
response_hook=response_hook,
1017-
),
1018-
)
1008+
if hasattr(transport, "handle_request"):
1009+
wrap_function_wrapper(
1010+
transport,
1011+
"handle_request",
1012+
partial(
1013+
cls._handle_request_wrapper,
1014+
tracer=tracer,
1015+
sem_conv_opt_in_mode=sem_conv_opt_in_mode,
1016+
request_hook=request_hook,
1017+
response_hook=response_hook,
1018+
),
1019+
)
10191020
client._is_instrumented_by_opentelemetry = True
10201021
if hasattr(client._transport, "handle_async_request"):
10211022
wrap_function_wrapper(
@@ -1030,17 +1031,18 @@ def instrument_client(
10301031
),
10311032
)
10321033
for transport in client._mounts.values():
1033-
wrap_function_wrapper(
1034-
transport,
1035-
"handle_async_request",
1036-
partial(
1037-
cls._handle_async_request_wrapper,
1038-
tracer=tracer,
1039-
sem_conv_opt_in_mode=sem_conv_opt_in_mode,
1040-
async_request_hook=async_request_hook,
1041-
async_response_hook=async_response_hook,
1042-
),
1043-
)
1034+
if hasattr(transport, "handle_async_request"):
1035+
wrap_function_wrapper(
1036+
transport,
1037+
"handle_async_request",
1038+
partial(
1039+
cls._handle_async_request_wrapper,
1040+
tracer=tracer,
1041+
sem_conv_opt_in_mode=sem_conv_opt_in_mode,
1042+
async_request_hook=async_request_hook,
1043+
async_response_hook=async_response_hook,
1044+
),
1045+
)
10441046
client._is_instrumented_by_opentelemetry = True
10451047

10461048
@staticmethod

instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,10 @@ def create_client(
741741
def create_proxy_transport(self, url: str):
742742
pass
743743

744+
@abc.abstractmethod
745+
def get_transport_handler(self, transport):
746+
pass
747+
744748
def setUp(self):
745749
super().setUp()
746750
self.client = self.create_client()
@@ -763,17 +767,15 @@ def assert_proxy_mounts(self, mounts, num_mounts, transport_type=None):
763767
self.assertEqual(len(mounts), num_mounts)
764768
for transport in mounts:
765769
with self.subTest(transport):
770+
if transport is None:
771+
continue
766772
if transport_type:
767773
self.assertIsInstance(
768774
transport,
769775
transport_type,
770776
)
771777
else:
772-
handler = getattr(transport, "handle_request", None)
773-
if not handler:
774-
handler = getattr(
775-
transport, "handle_async_request"
776-
)
778+
handler = self.get_transport_handler(transport)
777779
self.assertTrue(
778780
isinstance(handler, ObjectProxy)
779781
and getattr(handler, "__wrapped__")
@@ -983,6 +985,21 @@ def test_uninstrument_new_client(self):
983985
self.assertEqual(result.text, "Hello!")
984986
self.assert_span()
985987

988+
@mock.patch.dict(
989+
"os.environ", {"NO_PROXY": "http://mock/status/200"}, clear=True
990+
)
991+
def test_instrument_with_no_proxy(self):
992+
proxy_mounts = self.create_proxy_mounts()
993+
HTTPXClientInstrumentor().instrument()
994+
client = self.create_client(mounts=proxy_mounts)
995+
result = self.perform_request(self.URL, client=client)
996+
self.assert_span(num_spans=1)
997+
self.assertEqual(result.text, "Hello!")
998+
self.assert_proxy_mounts(
999+
client._mounts.values(),
1000+
3,
1001+
)
1002+
9861003
def test_instrument_proxy(self):
9871004
proxy_mounts = self.create_proxy_mounts()
9881005
HTTPXClientInstrumentor().instrument()
@@ -994,6 +1011,27 @@ def test_instrument_proxy(self):
9941011
2,
9951012
)
9961013

1014+
@mock.patch.dict(
1015+
"os.environ", {"NO_PROXY": "http://mock/status/200"}, clear=True
1016+
)
1017+
def test_instrument_client_with_no_proxy(self):
1018+
proxy_mounts = self.create_proxy_mounts()
1019+
client = self.create_client(mounts=proxy_mounts)
1020+
self.assert_proxy_mounts(
1021+
client._mounts.values(),
1022+
3,
1023+
(httpx.HTTPTransport, httpx.AsyncHTTPTransport),
1024+
)
1025+
HTTPXClientInstrumentor.instrument_client(client)
1026+
result = self.perform_request(self.URL, client=client)
1027+
self.assertEqual(result.text, "Hello!")
1028+
self.assert_span(num_spans=1)
1029+
self.assert_proxy_mounts(
1030+
client._mounts.values(),
1031+
3,
1032+
)
1033+
HTTPXClientInstrumentor.uninstrument_client(client)
1034+
9971035
def test_instrument_client_with_proxy(self):
9981036
proxy_mounts = self.create_proxy_mounts()
9991037
client = self.create_client(mounts=proxy_mounts)
@@ -1188,6 +1226,9 @@ def perform_request(
11881226
def create_proxy_transport(self, url):
11891227
return httpx.HTTPTransport(proxy=httpx.Proxy(url))
11901228

1229+
def get_transport_handler(self, transport):
1230+
return getattr(transport, "handle_request", None)
1231+
11911232
def test_can_instrument_subclassed_client(self):
11921233
class CustomClient(httpx.Client):
11931234
pass
@@ -1241,6 +1282,9 @@ async def _perform_request():
12411282
def create_proxy_transport(self, url):
12421283
return httpx.AsyncHTTPTransport(proxy=httpx.Proxy(url))
12431284

1285+
def get_transport_handler(self, transport):
1286+
return getattr(transport, "handle_async_request", None)
1287+
12441288
def test_basic_multiple(self):
12451289
# We need to create separate clients because in httpx >= 0.19,
12461290
# closing the client after "with" means the second http call fails

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

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -219,28 +219,31 @@ def _before_cur_exec(
219219
)
220220
with trace.use_span(span, end_on_exit=False):
221221
if span.is_recording():
222+
if self.enable_commenter:
223+
commenter_data = {
224+
"db_driver": conn.engine.driver,
225+
# Driver/framework centric information.
226+
"db_framework": f"sqlalchemy:{sqlalchemy.__version__}",
227+
}
228+
229+
if self.commenter_options.get(
230+
"opentelemetry_values", True
231+
):
232+
commenter_data.update(**_get_opentelemetry_values())
233+
234+
# Filter down to just the requested attributes.
235+
commenter_data = {
236+
k: v
237+
for k, v in commenter_data.items()
238+
if self.commenter_options.get(k, True)
239+
}
240+
241+
statement = _add_sql_comment(statement, **commenter_data)
242+
222243
span.set_attribute(SpanAttributes.DB_STATEMENT, statement)
223244
span.set_attribute(SpanAttributes.DB_SYSTEM, self.vendor)
224245
for key, value in attrs.items():
225246
span.set_attribute(key, value)
226-
if self.enable_commenter:
227-
commenter_data = {
228-
"db_driver": conn.engine.driver,
229-
# Driver/framework centric information.
230-
"db_framework": f"sqlalchemy:{sqlalchemy.__version__}",
231-
}
232-
233-
if self.commenter_options.get("opentelemetry_values", True):
234-
commenter_data.update(**_get_opentelemetry_values())
235-
236-
# Filter down to just the requested attributes.
237-
commenter_data = {
238-
k: v
239-
for k, v in commenter_data.items()
240-
if self.commenter_options.get(k, True)
241-
}
242-
243-
statement = _add_sql_comment(statement, **commenter_data)
244247

245248
context._otel_span = span
246249

0 commit comments

Comments
 (0)