mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-07-31 22:23:12 +08:00
[opentelemetry-instrumentation-httpx] fix mixing of sync and non async hooks (#1920)
* fix: sync response hooks being used on httpx.AsyncClient * docs: add changelog * docs: improved docs a bit more * docs: fix variable name --------- Co-authored-by: Diego Hurtado <ocelotl@users.noreply.github.com> Co-authored-by: Shalev Roda <65566801+shalevr@users.noreply.github.com>
This commit is contained in:
@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
- `opentelemetry-instrumentation` Added Otel semantic convention opt-in mechanism
|
- `opentelemetry-instrumentation` Added Otel semantic convention opt-in mechanism
|
||||||
([#1987](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1987))
|
([#1987](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1987))
|
||||||
|
- `opentelemetry-instrumentation-httpx` Fix mixing async and non async hooks
|
||||||
|
([#1920](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1920))
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
@ -136,7 +136,21 @@ The hooks can be configured as follows:
|
|||||||
# status_code, headers, stream, extensions = response
|
# status_code, headers, stream, extensions = response
|
||||||
pass
|
pass
|
||||||
|
|
||||||
HTTPXClientInstrumentor().instrument(request_hook=request_hook, response_hook=response_hook)
|
async def async_request_hook(span, request):
|
||||||
|
# method, url, headers, stream, extensions = request
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def async_response_hook(span, request, response):
|
||||||
|
# method, url, headers, stream, extensions = request
|
||||||
|
# status_code, headers, stream, extensions = response
|
||||||
|
pass
|
||||||
|
|
||||||
|
HTTPXClientInstrumentor().instrument(
|
||||||
|
request_hook=request_hook,
|
||||||
|
response_hook=response_hook,
|
||||||
|
async_request_hook=async_request_hook,
|
||||||
|
async_response_hook=async_response_hook
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
Or if you are using the transport classes directly:
|
Or if you are using the transport classes directly:
|
||||||
@ -144,7 +158,7 @@ Or if you are using the transport classes directly:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from opentelemetry.instrumentation.httpx import SyncOpenTelemetryTransport
|
from opentelemetry.instrumentation.httpx import SyncOpenTelemetryTransport, AsyncOpenTelemetryTransport
|
||||||
|
|
||||||
def request_hook(span, request):
|
def request_hook(span, request):
|
||||||
# method, url, headers, stream, extensions = request
|
# method, url, headers, stream, extensions = request
|
||||||
@ -155,6 +169,15 @@ Or if you are using the transport classes directly:
|
|||||||
# status_code, headers, stream, extensions = response
|
# status_code, headers, stream, extensions = response
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
async def async_request_hook(span, request):
|
||||||
|
# method, url, headers, stream, extensions = request
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def async_response_hook(span, request, response):
|
||||||
|
# method, url, headers, stream, extensions = request
|
||||||
|
# status_code, headers, stream, extensions = response
|
||||||
|
pass
|
||||||
|
|
||||||
transport = httpx.HTTPTransport()
|
transport = httpx.HTTPTransport()
|
||||||
telemetry_transport = SyncOpenTelemetryTransport(
|
telemetry_transport = SyncOpenTelemetryTransport(
|
||||||
transport,
|
transport,
|
||||||
@ -162,6 +185,13 @@ Or if you are using the transport classes directly:
|
|||||||
response_hook=response_hook
|
response_hook=response_hook
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async_transport = httpx.AsyncHTTPTransport()
|
||||||
|
async_telemetry_transport = AsyncOpenTelemetryTransport(
|
||||||
|
async_transport,
|
||||||
|
request_hook=async_request_hook,
|
||||||
|
response_hook=async_response_hook
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
References
|
References
|
||||||
----------
|
----------
|
||||||
|
@ -131,7 +131,21 @@ The hooks can be configured as follows:
|
|||||||
# status_code, headers, stream, extensions = response
|
# status_code, headers, stream, extensions = response
|
||||||
pass
|
pass
|
||||||
|
|
||||||
HTTPXClientInstrumentor().instrument(request_hook=request_hook, response_hook=response_hook)
|
async def async_request_hook(span, request):
|
||||||
|
# method, url, headers, stream, extensions = request
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def async_response_hook(span, request, response):
|
||||||
|
# method, url, headers, stream, extensions = request
|
||||||
|
# status_code, headers, stream, extensions = response
|
||||||
|
pass
|
||||||
|
|
||||||
|
HTTPXClientInstrumentor().instrument(
|
||||||
|
request_hook=request_hook,
|
||||||
|
response_hook=response_hook,
|
||||||
|
async_request_hook=async_request_hook,
|
||||||
|
async_response_hook=async_response_hook
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
Or if you are using the transport classes directly:
|
Or if you are using the transport classes directly:
|
||||||
@ -139,7 +153,7 @@ Or if you are using the transport classes directly:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from opentelemetry.instrumentation.httpx import SyncOpenTelemetryTransport
|
from opentelemetry.instrumentation.httpx import SyncOpenTelemetryTransport, AsyncOpenTelemetryTransport
|
||||||
|
|
||||||
def request_hook(span, request):
|
def request_hook(span, request):
|
||||||
# method, url, headers, stream, extensions = request
|
# method, url, headers, stream, extensions = request
|
||||||
@ -150,6 +164,15 @@ Or if you are using the transport classes directly:
|
|||||||
# status_code, headers, stream, extensions = response
|
# status_code, headers, stream, extensions = response
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
async def async_request_hook(span, request):
|
||||||
|
# method, url, headers, stream, extensions = request
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def async_response_hook(span, request, response):
|
||||||
|
# method, url, headers, stream, extensions = request
|
||||||
|
# status_code, headers, stream, extensions = response
|
||||||
|
pass
|
||||||
|
|
||||||
transport = httpx.HTTPTransport()
|
transport = httpx.HTTPTransport()
|
||||||
telemetry_transport = SyncOpenTelemetryTransport(
|
telemetry_transport = SyncOpenTelemetryTransport(
|
||||||
transport,
|
transport,
|
||||||
@ -157,6 +180,13 @@ Or if you are using the transport classes directly:
|
|||||||
response_hook=response_hook
|
response_hook=response_hook
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async_transport = httpx.AsyncHTTPTransport()
|
||||||
|
async_telemetry_transport = AsyncOpenTelemetryTransport(
|
||||||
|
async_transport,
|
||||||
|
request_hook=async_request_hook,
|
||||||
|
response_hook=async_response_hook
|
||||||
|
)
|
||||||
|
|
||||||
API
|
API
|
||||||
---
|
---
|
||||||
"""
|
"""
|
||||||
@ -377,8 +407,8 @@ class AsyncOpenTelemetryTransport(httpx.AsyncBaseTransport):
|
|||||||
self,
|
self,
|
||||||
transport: httpx.AsyncBaseTransport,
|
transport: httpx.AsyncBaseTransport,
|
||||||
tracer_provider: typing.Optional[TracerProvider] = None,
|
tracer_provider: typing.Optional[TracerProvider] = None,
|
||||||
request_hook: typing.Optional[RequestHook] = None,
|
request_hook: typing.Optional[AsyncRequestHook] = None,
|
||||||
response_hook: typing.Optional[ResponseHook] = None,
|
response_hook: typing.Optional[AsyncResponseHook] = None,
|
||||||
):
|
):
|
||||||
self._transport = transport
|
self._transport = transport
|
||||||
self._tracer = get_tracer(
|
self._tracer = get_tracer(
|
||||||
@ -511,21 +541,27 @@ class HTTPXClientInstrumentor(BaseInstrumentor):
|
|||||||
Args:
|
Args:
|
||||||
**kwargs: Optional arguments
|
**kwargs: Optional arguments
|
||||||
``tracer_provider``: a TracerProvider, defaults to global
|
``tracer_provider``: a TracerProvider, defaults to global
|
||||||
``request_hook``: A hook that receives the span and request that is called
|
``request_hook``: A ``httpx.Client`` hook that receives the span and request
|
||||||
right after the span is created
|
that is called right after the span is created
|
||||||
``response_hook``: A hook that receives the span, request, and response
|
``response_hook``: A ``httpx.Client`` hook that receives the span, request,
|
||||||
that is called right before the span ends
|
and response that is called right before the span ends
|
||||||
|
``async_request_hook``: Async ``request_hook`` for ``httpx.AsyncClient``
|
||||||
|
``async_response_hook``: Async``response_hook`` for ``httpx.AsyncClient``
|
||||||
"""
|
"""
|
||||||
self._original_client = httpx.Client
|
self._original_client = httpx.Client
|
||||||
self._original_async_client = httpx.AsyncClient
|
self._original_async_client = httpx.AsyncClient
|
||||||
request_hook = kwargs.get("request_hook")
|
request_hook = kwargs.get("request_hook")
|
||||||
response_hook = kwargs.get("response_hook")
|
response_hook = kwargs.get("response_hook")
|
||||||
|
async_request_hook = kwargs.get("async_request_hook", request_hook)
|
||||||
|
async_response_hook = kwargs.get("async_response_hook", response_hook)
|
||||||
if callable(request_hook):
|
if callable(request_hook):
|
||||||
_InstrumentedClient._request_hook = request_hook
|
_InstrumentedClient._request_hook = request_hook
|
||||||
_InstrumentedAsyncClient._request_hook = request_hook
|
if callable(async_request_hook):
|
||||||
|
_InstrumentedAsyncClient._request_hook = async_request_hook
|
||||||
if callable(response_hook):
|
if callable(response_hook):
|
||||||
_InstrumentedClient._response_hook = response_hook
|
_InstrumentedClient._response_hook = response_hook
|
||||||
_InstrumentedAsyncClient._response_hook = response_hook
|
if callable(async_response_hook):
|
||||||
|
_InstrumentedAsyncClient._response_hook = async_response_hook
|
||||||
tracer_provider = kwargs.get("tracer_provider")
|
tracer_provider = kwargs.get("tracer_provider")
|
||||||
_InstrumentedClient._tracer_provider = tracer_provider
|
_InstrumentedClient._tracer_provider = tracer_provider
|
||||||
_InstrumentedAsyncClient._tracer_provider = tracer_provider
|
_InstrumentedAsyncClient._tracer_provider = tracer_provider
|
||||||
@ -546,8 +582,12 @@ class HTTPXClientInstrumentor(BaseInstrumentor):
|
|||||||
def instrument_client(
|
def instrument_client(
|
||||||
client: typing.Union[httpx.Client, httpx.AsyncClient],
|
client: typing.Union[httpx.Client, httpx.AsyncClient],
|
||||||
tracer_provider: TracerProvider = None,
|
tracer_provider: TracerProvider = None,
|
||||||
request_hook: typing.Optional[RequestHook] = None,
|
request_hook: typing.Union[
|
||||||
response_hook: typing.Optional[ResponseHook] = None,
|
typing.Optional[RequestHook], typing.Optional[AsyncRequestHook]
|
||||||
|
] = None,
|
||||||
|
response_hook: typing.Union[
|
||||||
|
typing.Optional[ResponseHook], typing.Optional[AsyncResponseHook]
|
||||||
|
] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Instrument httpx Client or AsyncClient
|
"""Instrument httpx Client or AsyncClient
|
||||||
|
|
||||||
|
@ -421,6 +421,28 @@ class BaseTestCases:
|
|||||||
)
|
)
|
||||||
HTTPXClientInstrumentor().uninstrument()
|
HTTPXClientInstrumentor().uninstrument()
|
||||||
|
|
||||||
|
def test_response_hook_sync_async_kwargs(self):
|
||||||
|
HTTPXClientInstrumentor().instrument(
|
||||||
|
tracer_provider=self.tracer_provider,
|
||||||
|
response_hook=_response_hook,
|
||||||
|
async_response_hook=_async_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.attributes,
|
||||||
|
{
|
||||||
|
SpanAttributes.HTTP_METHOD: "GET",
|
||||||
|
SpanAttributes.HTTP_URL: self.URL,
|
||||||
|
SpanAttributes.HTTP_STATUS_CODE: 200,
|
||||||
|
HTTP_RESPONSE_BODY: "Hello!",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
HTTPXClientInstrumentor().uninstrument()
|
||||||
|
|
||||||
def test_request_hook(self):
|
def test_request_hook(self):
|
||||||
HTTPXClientInstrumentor().instrument(
|
HTTPXClientInstrumentor().instrument(
|
||||||
tracer_provider=self.tracer_provider,
|
tracer_provider=self.tracer_provider,
|
||||||
@ -434,6 +456,20 @@ class BaseTestCases:
|
|||||||
self.assertEqual(span.name, "GET" + self.URL)
|
self.assertEqual(span.name, "GET" + self.URL)
|
||||||
HTTPXClientInstrumentor().uninstrument()
|
HTTPXClientInstrumentor().uninstrument()
|
||||||
|
|
||||||
|
def test_request_hook_sync_async_kwargs(self):
|
||||||
|
HTTPXClientInstrumentor().instrument(
|
||||||
|
tracer_provider=self.tracer_provider,
|
||||||
|
request_hook=_request_hook,
|
||||||
|
async_request_hook=_async_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" + self.URL)
|
||||||
|
HTTPXClientInstrumentor().uninstrument()
|
||||||
|
|
||||||
def test_request_hook_no_span_update(self):
|
def test_request_hook_no_span_update(self):
|
||||||
HTTPXClientInstrumentor().instrument(
|
HTTPXClientInstrumentor().instrument(
|
||||||
tracer_provider=self.tracer_provider,
|
tracer_provider=self.tracer_provider,
|
||||||
|
Reference in New Issue
Block a user