From 3ff1113cfcaaac8296367b3fca94f95c48bf1e89 Mon Sep 17 00:00:00 2001 From: "Shi, Stone" Date: Fri, 26 Jul 2024 20:41:09 +0800 Subject: [PATCH 1/3] fix Sync hook used as async hook Signed-off-by: Shi, Stone --- .../instrumentation/httpx/__init__.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py b/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py index f2a18a2770..37b1559bb7 100644 --- a/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py @@ -731,7 +731,9 @@ def _instrument(self, **kwargs): self._original_async_client = httpx.AsyncClient request_hook = kwargs.get("request_hook") response_hook = kwargs.get("response_hook") - async_request_hook = kwargs.get("async_request_hook", request_hook) + async_request_hook = kwargs.get( + "async_request_hook", self._wrap_async_request_hook(request_hook) + ) async_response_hook = kwargs.get("async_response_hook", response_hook) if callable(request_hook): _InstrumentedClient._request_hook = request_hook @@ -749,6 +751,16 @@ def _instrument(self, **kwargs): httpx.Client = httpx._api.Client = _InstrumentedClient httpx.AsyncClient = _InstrumentedAsyncClient + # Wrap a given request hook function and ensure it is asynchronous + def _wrap_async_request_hook(self, request_hook_func): + if request_hook_func is None: + return None + + async def async_request_hook(span, req): + return request_hook_func(span, req) + + return async_request_hook + def _uninstrument(self, **kwargs): httpx.Client = httpx._api.Client = self._original_client httpx.AsyncClient = self._original_async_client From f3347b640ca723b0b9f8ca2d66b2634865dc47c3 Mon Sep 17 00:00:00 2001 From: "Shi, Stone" Date: Mon, 19 Aug 2024 17:31:26 +0800 Subject: [PATCH 2/3] fix sync request as async request co-authored-by: yao.yao@fmr.com Signed-off-by: Shi, Stone --- CHANGELOG.md | 5 ++++ .../instrumentation/httpx/__init__.py | 25 ++++++++----------- .../tests/test_httpx_integration.py | 24 ++++++++++++++++++ 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7924d9211e..67ed406c2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## Fixed + +- Sync hook used as async hook in `opentelemetry-instrumentation-httpx` causing `TypeError` + ([#2794](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2794)) + ## Version 1.26.0/0.47b0 (2024-07-23) ### Added diff --git a/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py b/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py index 37b1559bb7..4ae60a2515 100644 --- a/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py @@ -192,6 +192,7 @@ async def async_response_hook(span, request, response): """ import logging import typing +from inspect import iscoroutinefunction from types import TracebackType import httpx @@ -731,10 +732,16 @@ def _instrument(self, **kwargs): self._original_async_client = httpx.AsyncClient request_hook = kwargs.get("request_hook") response_hook = kwargs.get("response_hook") - async_request_hook = kwargs.get( - "async_request_hook", self._wrap_async_request_hook(request_hook) - ) - async_response_hook = kwargs.get("async_response_hook", response_hook) + if iscoroutinefunction(request_hook): + async_request_hook = kwargs.get("async_request_hook", request_hook) + else: + async_request_hook = kwargs.get("async_request_hook") + if iscoroutinefunction(response_hook): + async_response_hook = kwargs.get( + "async_response_hook", response_hook + ) + else: + async_response_hook = kwargs.get("async_response_hook") if callable(request_hook): _InstrumentedClient._request_hook = request_hook if callable(async_request_hook): @@ -751,16 +758,6 @@ def _instrument(self, **kwargs): httpx.Client = httpx._api.Client = _InstrumentedClient httpx.AsyncClient = _InstrumentedAsyncClient - # Wrap a given request hook function and ensure it is asynchronous - def _wrap_async_request_hook(self, request_hook_func): - if request_hook_func is None: - return None - - async def async_request_hook(span, req): - return request_hook_func(span, req) - - return async_request_hook - def _uninstrument(self, **kwargs): httpx.Client = httpx._api.Client = self._original_client httpx.AsyncClient = self._original_async_client diff --git a/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py b/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py index 011b5e57d2..f6aad4e129 100644 --- a/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py +++ b/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py @@ -1214,3 +1214,27 @@ def test_basic_multiple(self): self.perform_request(self.URL, client=self.client) self.perform_request(self.URL, client=self.client2) self.assert_span(num_spans=2) + + def test_async_request_hook_with_sync_hook_value_provided(self): + HTTPXClientInstrumentor().instrument( + tracer_provider=self.tracer_provider, + request_hook=_request_hook, + ) + client = self.create_client() + result = self.perform_request(self.URL, client=client) + self.assertEqual(result.text, "Hello!") + span = self.assert_span() + self.assertEqual(span.name, "GET") + HTTPXClientInstrumentor().uninstrument() + + def test_async_response_hook_with_sync_hook_value_provided(self): + HTTPXClientInstrumentor().instrument( + tracer_provider=self.tracer_provider, + response_hook=_response_hook, + ) + client = self.create_client() + result = self.perform_request(self.URL, client=client) + self.assertEqual(result.text, "Hello!") + span = self.assert_span() + self.assertEqual(span.name, "GET") + HTTPXClientInstrumentor().uninstrument() From e5483025cfc03b5634f8493bf281a96b28ad1aad Mon Sep 17 00:00:00 2001 From: "Shi, Stone" Date: Mon, 19 Aug 2024 20:47:02 +0800 Subject: [PATCH 3/3] change format of changelog Co-authored by yao.yao@fmr.com Signed-off-by: Shi, Stone --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67ed406c2b..064ab87f93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased -## Fixed +## Added - Sync hook used as async hook in `opentelemetry-instrumentation-httpx` causing `TypeError` ([#2794](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2794))