Skip to content

Commit 830397e

Browse files
authored
httpx: fix handling of async hooks (#2823)
1 parent 19f8e77 commit 830397e

File tree

3 files changed

+63
-11
lines changed

3 files changed

+63
-11
lines changed

CHANGELOG.md

+7-5
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10-
## Added
10+
### Added
1111

1212
- `opentelemetry-instrumentation-kafka-python` Instrument temporary fork, kafka-python-ng
1313
inside kafka-python's instrumentation
1414
([#2537](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2537))
1515

16-
## Breaking changes
16+
### Breaking changes
1717

1818
- `opentelemetry-bootstrap` Remove `opentelemetry-instrumentation-aws-lambda` from the defaults instrumentations
1919
([#2786](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2786))
2020

21-
## Fixed
21+
### Fixed
2222

23+
- `opentelemetry-instrumentation-httpx` fix handling of async hooks
24+
([#2823](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2823))
2325
- `opentelemetry-instrumentation-system-metrics` fix `process.runtime.cpu.utilization` values to be shown in range of 0 to 1
24-
([2812](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2812))
26+
([#2812](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2812))
2527
- `opentelemetry-instrumentation-fastapi` fix `fastapi` auto-instrumentation by removing `fastapi-slim` support, `fastapi-slim` itself is discontinued from maintainers
26-
([2783](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2783))
28+
([#2783](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2783))
2729
- `opentelemetry-instrumentation-aws-lambda` Avoid exception when a handler is not present.
2830
([#2750](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2750))
2931
- `opentelemetry-instrumentation-django` Fix regression - `http.target` re-added back to old semconv duration metrics

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

+9-4
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ async def async_response_hook(span, request, response):
192192
"""
193193
import logging
194194
import typing
195+
from asyncio import iscoroutinefunction
195196
from types import TracebackType
196197

197198
import httpx
@@ -731,15 +732,19 @@ def _instrument(self, **kwargs):
731732
self._original_async_client = httpx.AsyncClient
732733
request_hook = kwargs.get("request_hook")
733734
response_hook = kwargs.get("response_hook")
734-
async_request_hook = kwargs.get("async_request_hook", request_hook)
735-
async_response_hook = kwargs.get("async_response_hook", response_hook)
735+
async_request_hook = kwargs.get("async_request_hook")
736+
async_response_hook = kwargs.get("async_response_hook")
736737
if callable(request_hook):
737738
_InstrumentedClient._request_hook = request_hook
738-
if callable(async_request_hook):
739+
if callable(async_request_hook) and iscoroutinefunction(
740+
async_request_hook
741+
):
739742
_InstrumentedAsyncClient._request_hook = async_request_hook
740743
if callable(response_hook):
741744
_InstrumentedClient._response_hook = response_hook
742-
if callable(async_response_hook):
745+
if callable(async_response_hook) and iscoroutinefunction(
746+
async_response_hook
747+
):
743748
_InstrumentedAsyncClient._response_hook = async_response_hook
744749
tracer_provider = kwargs.get("tracer_provider")
745750
_InstrumentedClient._tracer_provider = tracer_provider

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

+47-2
Original file line numberDiff line numberDiff line change
@@ -780,9 +780,15 @@ def test_custom_tracer_provider(self):
780780
HTTPXClientInstrumentor().uninstrument()
781781

782782
def test_response_hook(self):
783+
response_hook_key = (
784+
"async_response_hook"
785+
if asyncio.iscoroutinefunction(self.response_hook)
786+
else "response_hook"
787+
)
788+
response_hook_kwargs = {response_hook_key: self.response_hook}
783789
HTTPXClientInstrumentor().instrument(
784790
tracer_provider=self.tracer_provider,
785-
response_hook=self.response_hook,
791+
**response_hook_kwargs,
786792
)
787793
client = self.create_client()
788794
result = self.perform_request(self.URL, client=client)
@@ -823,9 +829,15 @@ def test_response_hook_sync_async_kwargs(self):
823829
HTTPXClientInstrumentor().uninstrument()
824830

825831
def test_request_hook(self):
832+
request_hook_key = (
833+
"async_request_hook"
834+
if asyncio.iscoroutinefunction(self.request_hook)
835+
else "request_hook"
836+
)
837+
request_hook_kwargs = {request_hook_key: self.request_hook}
826838
HTTPXClientInstrumentor().instrument(
827839
tracer_provider=self.tracer_provider,
828-
request_hook=self.request_hook,
840+
**request_hook_kwargs,
829841
)
830842
client = self.create_client()
831843
result = self.perform_request(self.URL, client=client)
@@ -1214,3 +1226,36 @@ def test_basic_multiple(self):
12141226
self.perform_request(self.URL, client=self.client)
12151227
self.perform_request(self.URL, client=self.client2)
12161228
self.assert_span(num_spans=2)
1229+
1230+
def test_async_response_hook_does_nothing_if_not_coroutine(self):
1231+
HTTPXClientInstrumentor().instrument(
1232+
tracer_provider=self.tracer_provider,
1233+
async_response_hook=_response_hook,
1234+
)
1235+
client = self.create_client()
1236+
result = self.perform_request(self.URL, client=client)
1237+
1238+
self.assertEqual(result.text, "Hello!")
1239+
span = self.assert_span()
1240+
self.assertEqual(
1241+
dict(span.attributes),
1242+
{
1243+
SpanAttributes.HTTP_METHOD: "GET",
1244+
SpanAttributes.HTTP_URL: self.URL,
1245+
SpanAttributes.HTTP_STATUS_CODE: 200,
1246+
},
1247+
)
1248+
HTTPXClientInstrumentor().uninstrument()
1249+
1250+
def test_async_request_hook_does_nothing_if_not_coroutine(self):
1251+
HTTPXClientInstrumentor().instrument(
1252+
tracer_provider=self.tracer_provider,
1253+
async_request_hook=_request_hook,
1254+
)
1255+
client = self.create_client()
1256+
result = self.perform_request(self.URL, client=client)
1257+
1258+
self.assertEqual(result.text, "Hello!")
1259+
span = self.assert_span()
1260+
self.assertEqual(span.name, "GET")
1261+
HTTPXClientInstrumentor().uninstrument()

0 commit comments

Comments
 (0)