diff --git a/CHANGELOG.md b/CHANGELOG.md index a243091b1d..c3a0c1dabb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `opentelemetry-instrumentation-httpx` Fix `RequestInfo`/`ResponseInfo` type hints ([#3105](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3105)) +- `opentelemetry-instrumentation-httpx` Add the `is_instrumented` flag to the transport + ([#3106](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3106)) ## Version 1.29.0/0.50b0 (2024-12-11) 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 27bb3d639d..4eb4202c36 100644 --- a/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/__init__.py @@ -404,6 +404,8 @@ class SyncOpenTelemetryTransport(httpx.BaseTransport): that is called right before the span ends """ + _is_instrumented_by_opentelemetry = True + def __init__( self, transport: httpx.BaseTransport, @@ -529,6 +531,8 @@ class AsyncOpenTelemetryTransport(httpx.AsyncBaseTransport): that is called right before the span ends """ + _is_instrumented_by_opentelemetry = True + def __init__( self, transport: httpx.AsyncBaseTransport, @@ -897,6 +901,10 @@ def instrument_client( "Attempting to instrument Httpx client while already instrumented" ) return + if getattr( + client._transport, "is_instrumented_by_opentelemetry", False + ): + return _OpenTelemetrySemanticConventionStability._initialize() sem_conv_opt_in_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode( @@ -948,6 +956,7 @@ def instrument_client( response_hook=response_hook, ), ) + client._transport._is_instrumented_by_opentelemetry = True client._is_instrumented_by_opentelemetry = True if hasattr(client._transport, "handle_async_request"): wrap_function_wrapper( @@ -974,6 +983,7 @@ def instrument_client( async_response_hook=async_response_hook, ), ) + client._transport._is_instrumented_by_opentelemetry = True client._is_instrumented_by_opentelemetry = True @staticmethod @@ -989,9 +999,11 @@ def uninstrument_client( unwrap(client._transport, "handle_request") for transport in client._mounts.values(): unwrap(transport, "handle_request") + client._transport._is_instrumented_by_opentelemetry = False client._is_instrumented_by_opentelemetry = False elif hasattr(client._transport, "handle_async_request"): unwrap(client._transport, "handle_async_request") for transport in client._mounts.values(): unwrap(transport, "handle_async_request") + client._transport._is_instrumented_by_opentelemetry = False client._is_instrumented_by_opentelemetry = False diff --git a/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py b/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py index 148fe27893..9383bd269a 100644 --- a/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py +++ b/instrumentation/opentelemetry-instrumentation-httpx/tests/test_httpx_integration.py @@ -14,6 +14,8 @@ # pylint: disable=too-many-lines +from __future__ import annotations + import abc import asyncio import typing @@ -593,10 +595,10 @@ def create_transport( @abc.abstractmethod def create_client( self, - transport: typing.Union[ - SyncOpenTelemetryTransport, AsyncOpenTelemetryTransport, None - ] = None, - **kwargs, + transport: SyncOpenTelemetryTransport + | AsyncOpenTelemetryTransport + | None = None, + **kwargs: typing.Any, ): pass @@ -730,9 +732,9 @@ class BaseInstrumentorTest(BaseTest, metaclass=abc.ABCMeta): @abc.abstractmethod def create_client( self, - transport: typing.Union[ - SyncOpenTelemetryTransport, AsyncOpenTelemetryTransport, None - ] = None, + transport: SyncOpenTelemetryTransport + | AsyncOpenTelemetryTransport + | None = None, **kwargs, ): pass @@ -926,6 +928,17 @@ def test_instrument_client_called_on_the_class(self): self.assertEqual(result.text, "Hello!") self.assert_span(num_spans=1) + def test_instrument_multiple_clients_with_the_same_transport(self): + client1 = self.create_client() + client2 = self.create_client(transport=client1._transport) + + HTTPXClientInstrumentor().instrument_client(client1) + HTTPXClientInstrumentor().instrument_client(client2) + + result = self.perform_request(self.URL, client=client1) + self.assertEqual(result.text, "Hello!") + self.assert_span(num_spans=1) + def test_instrumentation_without_client(self): HTTPXClientInstrumentor().instrument() results = [ @@ -1210,7 +1223,7 @@ def create_client( transport: typing.Optional[SyncOpenTelemetryTransport] = None, **kwargs, ): - return httpx.Client(**kwargs) + return httpx.Client(transport=transport, **kwargs) def perform_request( self, @@ -1260,7 +1273,7 @@ def create_client( transport: typing.Optional[AsyncOpenTelemetryTransport] = None, **kwargs, ): - return httpx.AsyncClient(**kwargs) + return httpx.AsyncClient(transport=transport, **kwargs) def perform_request( self,