diff --git a/logfire/_internal/integrations/httpx.py b/logfire/_internal/integrations/httpx.py index 44f5ea176..6b53e8271 100644 --- a/logfire/_internal/integrations/httpx.py +++ b/logfire/_internal/integrations/httpx.py @@ -148,11 +148,11 @@ def instrument_httpx( ) tracer_provider = final_kwargs['tracer_provider'] - instrumentor.instrument_client(client, tracer_provider, request_hook, response_hook) # type: ignore[reportArgumentType] + instrumentor.instrument_client(client, tracer_provider, request_hook, response_hook) class LogfireHttpxInfoMixin: - headers: httpx.Headers + headers: httpx.Headers | None @property def content_type_header_object(self) -> ContentTypeHeader: @@ -160,14 +160,18 @@ def content_type_header_object(self) -> ContentTypeHeader: @property def content_type_header_string(self) -> str: - return self.headers.get('content-type', '') + if self.headers: + return self.headers.get('content-type', '') + + raise ValueError(f"'headers' is None in {repr(self)}") class LogfireHttpxRequestInfo(RequestInfo, LogfireHttpxInfoMixin): span: Span def capture_headers(self): - capture_request_or_response_headers(self.span, self.headers, 'request') + if self.headers: + capture_request_or_response_headers(self.span, self.headers, 'request') def capture_body(self): captured_form = self.capture_body_if_form() @@ -234,7 +238,8 @@ class LogfireHttpxResponseInfo(ResponseInfo, LogfireHttpxInfoMixin): is_async: bool def capture_headers(self): - capture_request_or_response_headers(self.span, self.headers, 'response') + if self.headers: + capture_request_or_response_headers(self.span, self.headers, 'response') def capture_body_if_text(self, attr_name: str = 'http.response.body.text'): def hook(span: LogfireSpan): diff --git a/logfire/integrations/httpx.py b/logfire/integrations/httpx.py index bbfc7c589..76f64f60b 100644 --- a/logfire/integrations/httpx.py +++ b/logfire/integrations/httpx.py @@ -1,44 +1,10 @@ from __future__ import annotations -from typing import Any, Awaitable, Callable, NamedTuple - -import httpx -from opentelemetry.trace import Span - -# TODO(Marcelo): When https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3098/ gets merged, -# and the next version of `opentelemetry-instrumentation-httpx` is released, we can just do a reimport: -# from opentelemetry.instrumentation.httpx import RequestInfo as RequestInfo -# from opentelemetry.instrumentation.httpx import ResponseInfo as ResponseInfo -# from opentelemetry.instrumentation.httpx import RequestHook as RequestHook -# from opentelemetry.instrumentation.httpx import ResponseHook as ResponseHook - - -class RequestInfo(NamedTuple): - """Information about an HTTP request. - - This is the second parameter passed to the `RequestHook` function. - """ - - method: bytes - url: httpx.URL - headers: httpx.Headers - stream: httpx.SyncByteStream | httpx.AsyncByteStream | None - extensions: dict[str, Any] | None - - -class ResponseInfo(NamedTuple): - """Information about an HTTP response. - - This is the second parameter passed to the `ResponseHook` function. - """ - - status_code: int - headers: httpx.Headers - stream: httpx.SyncByteStream | httpx.AsyncByteStream | None - extensions: dict[str, Any] | None - - -RequestHook = Callable[[Span, RequestInfo], None] -ResponseHook = Callable[[Span, RequestInfo, ResponseInfo], None] -AsyncRequestHook = Callable[[Span, RequestInfo], Awaitable[None]] -AsyncResponseHook = Callable[[Span, RequestInfo, ResponseInfo], Awaitable[None]] +from opentelemetry.instrumentation.httpx import ( + AsyncRequestHook as AsyncRequestHook, + AsyncResponseHook as AsyncResponseHook, + RequestHook as RequestHook, + RequestInfo as RequestInfo, + ResponseHook as ResponseHook, + ResponseInfo as ResponseInfo, +)