From 7102d94f39c5f9516a8a86ac07e4b4480fcac2b9 Mon Sep 17 00:00:00 2001 From: samypr100 <3933065+samypr100@users.noreply.github.com> Date: Wed, 20 Mar 2024 11:33:21 -0400 Subject: [PATCH 1/5] fix: retain httpx.URL type on request.url This fixes a regression caused in #2020 where request.url stopped being of `httpx.URL` type, causing issues with request/response hooks. --- .../src/opentelemetry/instrumentation/httpx/__init__.py | 2 +- 1 file changed, 1 insertion(+), 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 53542e7ef3..e6609157c4 100644 --- a/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py @@ -270,7 +270,7 @@ def _extract_parameters(args, kwargs): # In httpx >= 0.20.0, handle_request receives a Request object request: httpx.Request = args[0] method = request.method.encode() - url = remove_url_credentials(str(request.url)) + url = httpx.URL(remove_url_credentials(str(request.url))) headers = request.headers stream = request.stream extensions = request.extensions From 509a7bcde34787d09665eee31c64874bc13fb776 Mon Sep 17 00:00:00 2001 From: samypr100 <3933065+samypr100@users.noreply.github.com> Date: Wed, 20 Mar 2024 11:45:40 -0400 Subject: [PATCH 2/5] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fae8763b7..39938e12a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#2297](https://github.com/open-telemetry/opentelemetry-python-contrib/issues/2297)) - Ensure all http.server.duration metrics have the same description ([#2151](https://github.com/open-telemetry/opentelemetry-python-contrib/issues/2298)) +- Fix regression in httpx `request.url` not being of type `httpx.URL` after `0.44b0` + ([#2359](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2359)) ## Version 1.23.0/0.44b0 (2024-02-23) From 2cc4f874e3455c741a6acb6cc3965da9ba2b145d Mon Sep 17 00:00:00 2001 From: samypr100 <3933065+samypr100@users.noreply.github.com> Date: Wed, 20 Mar 2024 14:33:51 -0400 Subject: [PATCH 3/5] tests: adding assertions to verify request.url on hooks is a httpx.URL --- .../tests/test_httpx_integration.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py b/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py index b8d7fbb6b6..7bb212139d 100644 --- a/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py +++ b/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py @@ -57,6 +57,7 @@ def _async_call(coro: typing.Coroutine) -> asyncio.Task: def _response_hook(span, request: "RequestInfo", response: "ResponseInfo"): + assert isinstance(request.url, httpx.URL) span.set_attribute( HTTP_RESPONSE_BODY, b"".join(response[2]), @@ -66,6 +67,7 @@ def _response_hook(span, request: "RequestInfo", response: "ResponseInfo"): async def _async_response_hook( span: "Span", request: "RequestInfo", response: "ResponseInfo" ): + assert isinstance(request.url, httpx.URL) span.set_attribute( HTTP_RESPONSE_BODY, b"".join([part async for part in response[2]]), @@ -73,11 +75,13 @@ async def _async_response_hook( def _request_hook(span: "Span", request: "RequestInfo"): + assert isinstance(request.url, httpx.URL) url = httpx.URL(request[1]) span.update_name("GET" + str(url)) async def _async_request_hook(span: "Span", request: "RequestInfo"): + assert isinstance(request.url, httpx.URL) url = httpx.URL(request[1]) span.update_name("GET" + str(url)) From 73b60149ac850b11e7be43c23a0cd9bb73f37e8e Mon Sep 17 00:00:00 2001 From: samypr100 <3933065+samypr100@users.noreply.github.com> Date: Wed, 20 Mar 2024 20:45:32 -0400 Subject: [PATCH 4/5] fixup: adjust check to consider httpx < 0.20.0 --- .../tests/test_httpx_integration.py | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py b/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py index 7bb212139d..1fce6cd02a 100644 --- a/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py +++ b/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py @@ -57,7 +57,10 @@ def _async_call(coro: typing.Coroutine) -> asyncio.Task: def _response_hook(span, request: "RequestInfo", response: "ResponseInfo"): - assert isinstance(request.url, httpx.URL) + # httpx < 0.20.0 url is a tuple of len 4 and not a httpx.URL + is_url_tuple = isinstance(request[1], tuple) and len(request[1]) == 4 + if not is_url_tuple: + assert isinstance(request.url, httpx.URL) span.set_attribute( HTTP_RESPONSE_BODY, b"".join(response[2]), @@ -67,7 +70,10 @@ def _response_hook(span, request: "RequestInfo", response: "ResponseInfo"): async def _async_response_hook( span: "Span", request: "RequestInfo", response: "ResponseInfo" ): - assert isinstance(request.url, httpx.URL) + # httpx < 0.20.0 url is a tuple of len 4 and not a httpx.URL + is_url_tuple = isinstance(request[1], tuple) and len(request[1]) == 4 + if not is_url_tuple: + assert isinstance(request.url, httpx.URL) span.set_attribute( HTTP_RESPONSE_BODY, b"".join([part async for part in response[2]]), @@ -75,13 +81,19 @@ async def _async_response_hook( def _request_hook(span: "Span", request: "RequestInfo"): - assert isinstance(request.url, httpx.URL) + # httpx < 0.20.0 url is a tuple of len 4 and not a httpx.URL + is_url_tuple = isinstance(request[1], tuple) and len(request[1]) == 4 + if not is_url_tuple: + assert isinstance(request.url, httpx.URL) url = httpx.URL(request[1]) span.update_name("GET" + str(url)) async def _async_request_hook(span: "Span", request: "RequestInfo"): - assert isinstance(request.url, httpx.URL) + # httpx < 0.20.0 url is a tuple of len 4 and not a httpx.URL + is_url_tuple = isinstance(request[1], tuple) and len(request[1]) == 4 + if not is_url_tuple: + assert isinstance(request.url, httpx.URL) url = httpx.URL(request[1]) span.update_name("GET" + str(url)) From 6da35cbe78e21c1a686cc4a1ecb3b4f032ab8079 Mon Sep 17 00:00:00 2001 From: samypr100 <3933065+samypr100@users.noreply.github.com> Date: Fri, 22 Mar 2024 12:58:00 -0400 Subject: [PATCH 5/5] fixup: keep code dry --- .../tests/test_httpx_integration.py | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py b/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py index 1fce6cd02a..c3f668cafe 100644 --- a/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py +++ b/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py @@ -51,16 +51,18 @@ HTTP_RESPONSE_BODY = "http.response.body" +def _is_url_tuple(request: "RequestInfo"): + """Determine if request url format is for httpx versions < 0.20.0.""" + return isinstance(request[1], tuple) and len(request[1]) == 4 + + def _async_call(coro: typing.Coroutine) -> asyncio.Task: loop = asyncio.get_event_loop() return loop.run_until_complete(coro) def _response_hook(span, request: "RequestInfo", response: "ResponseInfo"): - # httpx < 0.20.0 url is a tuple of len 4 and not a httpx.URL - is_url_tuple = isinstance(request[1], tuple) and len(request[1]) == 4 - if not is_url_tuple: - assert isinstance(request.url, httpx.URL) + assert _is_url_tuple(request) or isinstance(request.url, httpx.URL) span.set_attribute( HTTP_RESPONSE_BODY, b"".join(response[2]), @@ -70,10 +72,7 @@ def _response_hook(span, request: "RequestInfo", response: "ResponseInfo"): async def _async_response_hook( span: "Span", request: "RequestInfo", response: "ResponseInfo" ): - # httpx < 0.20.0 url is a tuple of len 4 and not a httpx.URL - is_url_tuple = isinstance(request[1], tuple) and len(request[1]) == 4 - if not is_url_tuple: - assert isinstance(request.url, httpx.URL) + assert _is_url_tuple(request) or isinstance(request.url, httpx.URL) span.set_attribute( HTTP_RESPONSE_BODY, b"".join([part async for part in response[2]]), @@ -81,19 +80,13 @@ async def _async_response_hook( def _request_hook(span: "Span", request: "RequestInfo"): - # httpx < 0.20.0 url is a tuple of len 4 and not a httpx.URL - is_url_tuple = isinstance(request[1], tuple) and len(request[1]) == 4 - if not is_url_tuple: - assert isinstance(request.url, httpx.URL) + assert _is_url_tuple(request) or isinstance(request.url, httpx.URL) url = httpx.URL(request[1]) span.update_name("GET" + str(url)) async def _async_request_hook(span: "Span", request: "RequestInfo"): - # httpx < 0.20.0 url is a tuple of len 4 and not a httpx.URL - is_url_tuple = isinstance(request[1], tuple) and len(request[1]) == 4 - if not is_url_tuple: - assert isinstance(request.url, httpx.URL) + assert _is_url_tuple(request) or isinstance(request.url, httpx.URL) url = httpx.URL(request[1]) span.update_name("GET" + str(url))