Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions logfire/_internal/integrations/httpx.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,26 +148,30 @@
)

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:
return content_type_header_from_string(self.content_type_header_string)

@property
def content_type_header_string(self) -> str:
return self.headers.get('content-type', '')
if self.headers is not None:
return self.headers.get('content-type', '')

raise TypeError('Content Type Header String returned None')

Check warning on line 166 in logfire/_internal/integrations/httpx.py

View check run for this annotation

Codecov / codecov/patch

logfire/_internal/integrations/httpx.py#L166

Added line #L166 was not covered by tests


class LogfireHttpxRequestInfo(RequestInfo, LogfireHttpxInfoMixin):
span: Span

def capture_headers(self):
capture_request_or_response_headers(self.span, self.headers, 'request')
if self.headers is not None:
capture_request_or_response_headers(self.span, self.headers, 'request')

def capture_body(self):
captured_form = self.capture_body_if_form()
Expand All @@ -189,7 +193,7 @@
return False

data = self.form_data
if not (data and isinstance(data, Mapping)): # pragma: no cover # type: ignore
if not data:
return False
self.set_complex_span_attributes({attr_name: data})
return True
Expand Down Expand Up @@ -234,7 +238,8 @@
is_async: bool

def capture_headers(self):
capture_request_or_response_headers(self.span, self.headers, 'response')
if self.headers is not None:
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):
Expand Down
48 changes: 8 additions & 40 deletions logfire/integrations/httpx.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,12 @@
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,
)
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ dependencies = [
"typing-extensions >= 4.1.0",
"tomli >= 2.0.1; python_version < '3.11'",
"executing >= 2.0.1",
"opentelemetry-instrumentation-urllib3>=0.54b1",
]

[project.optional-dependencies]
Expand Down
18 changes: 18 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading