From dc711e870ec086c1e598c9f9b6191b8a7eeba42e Mon Sep 17 00:00:00 2001 From: Rytis Bagdziunas Date: Fri, 31 May 2024 00:48:22 +0200 Subject: [PATCH] Ensure httpx non-client methods are instrumented (#2538) * Ensure httpx non-client methods are instrumented * Update changelog * Added subTest to distinguish tests inside a loop * Updated changelog * Add a comment explaining private attribute usage --------- Co-authored-by: Diego Hurtado --- CHANGELOG.md | 5 +++- .../instrumentation/httpx/__init__.py | 6 +++-- .../tests/test_httpx_integration.py | 24 +++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17152f3fa7..72ff7ac52c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,7 +42,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- `opentelemetry-instrumentation-dbapi` Fix compatibility with Psycopg3 to extract libpq build version (#2500)[https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2500] +- `opentelemetry-instrumentation-dbapi` Fix compatibility with Psycopg3 to extract libpq build version + ([#2500](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2500)) +- `opentelemetry-instrumentation-httpx` Ensure httpx.get or httpx.request like methods are instrumented + ([#2538](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2538)) - `opentelemetry-instrumentation-grpc` AioClientInterceptor should propagate with a Metadata object ([#2363](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2363)) - `opentelemetry-instrumentation-boto3sqs` Instrument Session and resource 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 7fcc7128be..850e76eea3 100644 --- a/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py @@ -564,11 +564,13 @@ def _instrument(self, **kwargs): tracer_provider = kwargs.get("tracer_provider") _InstrumentedClient._tracer_provider = tracer_provider _InstrumentedAsyncClient._tracer_provider = tracer_provider - httpx.Client = _InstrumentedClient + # Intentionally using a private attribute here, see: + # https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2538#discussion_r1610603719 + httpx.Client = httpx._api.Client = _InstrumentedClient httpx.AsyncClient = _InstrumentedAsyncClient def _uninstrument(self, **kwargs): - httpx.Client = self._original_client + httpx.Client = httpx._api.Client = self._original_client httpx.AsyncClient = self._original_async_client _InstrumentedClient._tracer_provider = None _InstrumentedClient._request_hook = None diff --git a/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py b/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py index c3f668cafe..d64db1a8f5 100644 --- a/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py +++ b/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py @@ -532,12 +532,36 @@ def test_instrument_client(self): self.assertEqual(result.text, "Hello!") self.assert_span(num_spans=1) + def test_instrumentation_without_client(self): + + HTTPXClientInstrumentor().instrument() + results = [ + httpx.get(self.URL), + httpx.request("GET", self.URL), + ] + with httpx.stream("GET", self.URL) as stream: + stream.read() + results.append(stream) + + spans = self.assert_span(num_spans=len(results)) + for idx, res in enumerate(results): + with self.subTest(idx=idx, res=res): + self.assertEqual(res.text, "Hello!") + self.assertEqual( + spans[idx].attributes[SpanAttributes.HTTP_URL], + self.URL, + ) + + HTTPXClientInstrumentor().uninstrument() + def test_uninstrument(self): HTTPXClientInstrumentor().instrument() HTTPXClientInstrumentor().uninstrument() client = self.create_client() result = self.perform_request(self.URL, client=client) + result_no_client = httpx.get(self.URL) self.assertEqual(result.text, "Hello!") + self.assertEqual(result_no_client.text, "Hello!") self.assert_span(num_spans=0) def test_uninstrument_client(self):