Skip to content

Commit

Permalink
[opentelemetry-instrumentation-httpx] fix mixing of sync and non asyn…
Browse files Browse the repository at this point in the history
…c 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 <[email protected]>
Co-authored-by: Shalev Roda <[email protected]>
  • Loading branch information
3 people committed Nov 21, 2023
1 parent 773e431 commit 1b1c38d
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 14 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
([#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

Expand Down
34 changes: 32 additions & 2 deletions instrumentation/opentelemetry-instrumentation-httpx/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -136,15 +136,29 @@ The hooks can be configured as follows:
# status_code, headers, stream, extensions = response
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:


.. code-block:: python
from opentelemetry.instrumentation.httpx import SyncOpenTelemetryTransport
from opentelemetry.instrumentation.httpx import SyncOpenTelemetryTransport, AsyncOpenTelemetryTransport
def request_hook(span, request):
# method, url, headers, stream, extensions = request
Expand All @@ -155,13 +169,29 @@ Or if you are using the transport classes directly:
# status_code, headers, stream, extensions = response
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()
telemetry_transport = SyncOpenTelemetryTransport(
transport,
request_hook=request_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
----------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,15 +131,29 @@ def response_hook(span, request, response):
# status_code, headers, stream, extensions = response
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:
.. code-block:: python
from opentelemetry.instrumentation.httpx import SyncOpenTelemetryTransport
from opentelemetry.instrumentation.httpx import SyncOpenTelemetryTransport, AsyncOpenTelemetryTransport
def request_hook(span, request):
# method, url, headers, stream, extensions = request
Expand All @@ -150,13 +164,29 @@ def response_hook(span, request, response):
# status_code, headers, stream, extensions = response
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()
telemetry_transport = SyncOpenTelemetryTransport(
transport,
request_hook=request_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
---
"""
Expand Down Expand Up @@ -377,8 +407,8 @@ def __init__(
self,
transport: httpx.AsyncBaseTransport,
tracer_provider: typing.Optional[TracerProvider] = None,
request_hook: typing.Optional[RequestHook] = None,
response_hook: typing.Optional[ResponseHook] = None,
request_hook: typing.Optional[AsyncRequestHook] = None,
response_hook: typing.Optional[AsyncResponseHook] = None,
):
self._transport = transport
self._tracer = get_tracer(
Expand Down Expand Up @@ -511,21 +541,27 @@ def _instrument(self, **kwargs):
Args:
**kwargs: Optional arguments
``tracer_provider``: a TracerProvider, defaults to global
``request_hook``: A hook that receives the span and request that is called
right after the span is created
``response_hook``: A hook that receives the span, request, and response
that is called right before the span ends
``request_hook``: A ``httpx.Client`` hook that receives the span and request
that is called right after the span is created
``response_hook``: A ``httpx.Client`` hook that receives the span, request,
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_async_client = httpx.AsyncClient
request_hook = kwargs.get("request_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):
_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):
_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")
_InstrumentedClient._tracer_provider = tracer_provider
_InstrumentedAsyncClient._tracer_provider = tracer_provider
Expand All @@ -546,8 +582,12 @@ def _uninstrument(self, **kwargs):
def instrument_client(
client: typing.Union[httpx.Client, httpx.AsyncClient],
tracer_provider: TracerProvider = None,
request_hook: typing.Optional[RequestHook] = None,
response_hook: typing.Optional[ResponseHook] = None,
request_hook: typing.Union[
typing.Optional[RequestHook], typing.Optional[AsyncRequestHook]
] = None,
response_hook: typing.Union[
typing.Optional[ResponseHook], typing.Optional[AsyncResponseHook]
] = None,
) -> None:
"""Instrument httpx Client or AsyncClient
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,28 @@ def test_response_hook(self):
)
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):
HTTPXClientInstrumentor().instrument(
tracer_provider=self.tracer_provider,
Expand All @@ -434,6 +456,20 @@ def test_request_hook(self):
self.assertEqual(span.name, "GET" + self.URL)
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):
HTTPXClientInstrumentor().instrument(
tracer_provider=self.tracer_provider,
Expand Down

0 comments on commit 1b1c38d

Please sign in to comment.