Skip to content

Commit

Permalink
Release v2.11.0 (#723)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexmojaki authored Dec 23, 2024
1 parent db1f57a commit 21c457a
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 24 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Release Notes

## [v2.11.0] (2024-12-23)

* Add `capture_request_text_body` param to `instrument_httpx` by @alexmojaki in [#722](https://github.com/pydantic/logfire/pull/722)
* Support for `AnthropicBedrock` client by @stephenhibbert in [#701](https://github.com/pydantic/logfire/pull/701)

## [v2.10.0] (2024-12-23)

* Add `capture_request_form_data` param to `instrument_httpx` by @alexmojaki in [#711](https://github.com/pydantic/logfire/pull/711)
Expand Down Expand Up @@ -493,3 +498,4 @@ First release from new repo!
[v2.8.0]: https://github.com/pydantic/logfire/compare/v2.7.1...v2.8.0
[v2.9.0]: https://github.com/pydantic/logfire/compare/v2.8.0...v2.9.0
[v2.10.0]: https://github.com/pydantic/logfire/compare/v2.9.0...v2.10.0
[v2.11.0]: https://github.com/pydantic/logfire/compare/v2.10.0...v2.11.0
43 changes: 35 additions & 8 deletions logfire-api/logfire_api/_internal/integrations/httpx.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ from logfire._internal.utils import handle_internal_errors as handle_internal_er
from logfire.integrations.httpx import AsyncRequestHook as AsyncRequestHook, AsyncResponseHook as AsyncResponseHook, RequestHook as RequestHook, RequestInfo as RequestInfo, ResponseHook as ResponseHook, ResponseInfo as ResponseInfo
from logfire.propagate import attach_context as attach_context, get_context as get_context
from opentelemetry.trace import Span
from typing import Any, Callable, Literal, ParamSpec, TypeVar, TypedDict
from typing import Any, Callable, Literal, Mapping, ParamSpec, TypeVar, TypedDict

class AsyncClientKwargs(TypedDict, total=False):
request_hook: RequestHook | AsyncRequestHook
Expand All @@ -32,28 +32,55 @@ Hook = TypeVar('Hook', RequestHook, ResponseHook)
AsyncHook = TypeVar('AsyncHook', AsyncRequestHook, AsyncResponseHook)
P = ParamSpec('P')

def instrument_httpx(logfire_instance: Logfire, client: httpx.Client | httpx.AsyncClient | None, capture_headers: bool, capture_request_json_body: bool, capture_response_json_body: bool, capture_request_form_data: bool, **kwargs: Any) -> None:
def instrument_httpx(logfire_instance: Logfire, client: httpx.Client | httpx.AsyncClient | None, capture_headers: bool, capture_request_json_body: bool, capture_request_text_body: bool, capture_response_json_body: bool, capture_request_form_data: bool, **kwargs: Any) -> None:
"""Instrument the `httpx` module so that spans are automatically created for each request.
See the `Logfire.instrument_httpx` method for details.
"""
def make_request_hook(hook: RequestHook | None, should_capture_headers: bool, should_capture_json: bool, should_capture_form_data: bool) -> RequestHook | None: ...
def make_async_request_hook(hook: AsyncRequestHook | RequestHook | None, should_capture_headers: bool, should_capture_json: bool, should_capture_form_data: bool) -> AsyncRequestHook | None: ...
def capture_request(request: RequestInfo, span: Span, should_capture_headers: bool, should_capture_json: bool, should_capture_form_data: bool) -> None: ...

class LogfireHttpxRequestInfo(RequestInfo):
span: Span
def capture_headers(self) -> None: ...
def capture_body_if_json(self, attr_name: str = 'http.request.body.json'): ...
def capture_body_if_text(self, attr_name: str = 'http.request.body.text'): ...
def capture_body_if_form(self, attr_name: str = 'http.request.body.form'): ...
def capture_text_as_json(self, attr_name: str = 'http.request.body.json', text: str | None = None): ...
@property
def body_is_streaming(self): ...
@property
def content_type_header_object(self) -> ContentTypeHeader: ...
@property
def content_type_header_string(self) -> str: ...
@property
def content_type_is_json(self): ...
@property
def content_type_is_text(self): ...
@property
def content_type_is_form(self): ...
@property
def content_type_charset(self): ...
@property
def content(self) -> bytes: ...
@property
def text(self) -> str: ...
@property
def form_data(self) -> Mapping[str, Any] | None: ...
def set_complex_span_attributes(self, attributes: dict[str, Any]): ...

def make_request_hook(hook: RequestHook | None, should_capture_headers: bool, should_capture_json: bool, should_capture_text: bool, should_capture_form_data: bool) -> RequestHook | None: ...
def make_async_request_hook(hook: AsyncRequestHook | RequestHook | None, should_capture_headers: bool, should_capture_json: bool, should_capture_text: bool, should_capture_form_data: bool) -> AsyncRequestHook | None: ...
def capture_request(request: LogfireHttpxRequestInfo, should_capture_headers: bool, should_capture_json: bool, should_capture_text: bool, should_capture_form_data: bool) -> None: ...
def make_response_hook(hook: ResponseHook | None, should_capture_headers: bool, should_capture_json: bool, logfire_instance: Logfire) -> ResponseHook | None: ...
def make_async_response_hook(hook: ResponseHook | AsyncResponseHook | None, should_capture_headers: bool, should_capture_json: bool, logfire_instance: Logfire) -> AsyncResponseHook | None: ...
def capture_response_json(logfire_instance: Logfire, response_info: ResponseInfo, is_async: bool) -> None: ...
async def run_async_hook(hook: Callable[P, Any] | None, *args: P.args, **kwargs: P.kwargs) -> None: ...
def run_hook(hook: Callable[P, Any] | None, *args: P.args, **kwargs: P.kwargs) -> None: ...
def capture_response_headers(span: Span, response: ResponseInfo) -> None: ...
def capture_request_headers(span: Span, request: RequestInfo) -> None: ...
def capture_headers(span: Span, headers: httpx.Headers, request_or_response: Literal['request', 'response']) -> None: ...
def decode_body(body: bytes, charset: str): ...
def capture_request_body(span: Span, request: RequestInfo) -> None: ...

CODES_FOR_METHODS_WITH_DATA_PARAM: Incomplete

def capture_request_form_data(span: Span, request: RequestInfo) -> None: ...
def content_type_header_from_string(content_type: str) -> ContentTypeHeader: ...
def content_type_subtypes(subtype: str) -> set[str]: ...
def is_json_type(content_type: str) -> bool: ...
Expand Down
22 changes: 10 additions & 12 deletions logfire-api/logfire_api/_internal/main.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -501,10 +501,10 @@ class Logfire:
A context manager that will revert the instrumentation when exited.
Use of this context manager is optional.
"""
def instrument_anthropic(self, anthropic_client: anthropic.Anthropic | anthropic.AsyncAnthropic | type[anthropic.Anthropic] | type[anthropic.AsyncAnthropic] | None = None, *, suppress_other_instrumentation: bool = True) -> ContextManager[None]:
def instrument_anthropic(self, anthropic_client: anthropic.Anthropic | anthropic.AsyncAnthropic | anthropic.AnthropicBedrock | anthropic.AsyncAnthropicBedrock | type[anthropic.Anthropic] | type[anthropic.AsyncAnthropic] | type[anthropic.AnthropicBedrock] | type[anthropic.AsyncAnthropicBedrock] | None = None, *, suppress_other_instrumentation: bool = True) -> ContextManager[None]:
"""Instrument an Anthropic client so that spans are automatically created for each request.
The following methods are instrumented for both the sync and the async clients:
The following methods are instrumented for both the sync and async clients:
- [`client.messages.create`](https://docs.anthropic.com/en/api/messages)
- [`client.messages.stream`](https://docs.anthropic.com/en/api/messages-streaming)
Expand All @@ -519,6 +519,7 @@ class Logfire:
import anthropic
client = anthropic.Anthropic()
logfire.configure()
logfire.instrument_anthropic(client)
Expand All @@ -534,13 +535,10 @@ class Logfire:
Args:
anthropic_client: The Anthropic client or class to instrument:
- `None` (the default) to instrument both the
`anthropic.Anthropic` and `anthropic.AsyncAnthropic` classes.
- The `anthropic.Anthropic` class or a subclass
- The `anthropic.AsyncAnthropic` class or a subclass
- An instance of `anthropic.Anthropic`
- An instance of `anthropic.AsyncAnthropic`
- `None` (the default) to instrument all Anthropic client types
- The `anthropic.Anthropic` or `anthropic.AnthropicBedrock` class or subclass
- The `anthropic.AsyncAnthropic` or `anthropic.AsyncAnthropicBedrock` class or subclass
- An instance of any of the above classes
suppress_other_instrumentation: If True, suppress any other OTEL instrumentation that may be otherwise
enabled. In reality, this means the HTTPX instrumentation, which could otherwise be called since
Expand All @@ -553,11 +551,11 @@ class Logfire:
def instrument_asyncpg(self, **kwargs: Unpack[AsyncPGInstrumentKwargs]) -> None:
"""Instrument the `asyncpg` module so that spans are automatically created for each query."""
@overload
def instrument_httpx(self, client: httpx.Client, *, capture_headers: bool = False, capture_request_json_body: bool = False, capture_response_json_body: bool = False, capture_request_form_data: bool = False, **kwargs: Unpack[ClientKwargs]) -> None: ...
def instrument_httpx(self, client: httpx.Client, *, capture_headers: bool = False, capture_request_text_body: bool = False, capture_request_json_body: bool = False, capture_response_json_body: bool = False, capture_request_form_data: bool = False, **kwargs: Unpack[ClientKwargs]) -> None: ...
@overload
def instrument_httpx(self, client: httpx.AsyncClient, *, capture_headers: bool = False, capture_request_json_body: bool = False, capture_response_json_body: bool = False, capture_request_form_data: bool = False, **kwargs: Unpack[AsyncClientKwargs]) -> None: ...
def instrument_httpx(self, client: httpx.AsyncClient, *, capture_headers: bool = False, capture_request_json_body: bool = False, capture_request_text_body: bool = False, capture_response_json_body: bool = False, capture_request_form_data: bool = False, **kwargs: Unpack[AsyncClientKwargs]) -> None: ...
@overload
def instrument_httpx(self, client: None = None, *, capture_headers: bool = False, capture_request_json_body: bool = False, capture_response_json_body: bool = False, capture_request_form_data: bool = False, **kwargs: Unpack[HTTPXInstrumentKwargs]) -> None: ...
def instrument_httpx(self, client: None = None, *, capture_headers: bool = False, capture_request_json_body: bool = False, capture_request_text_body: bool = False, capture_response_json_body: bool = False, capture_request_form_data: bool = False, **kwargs: Unpack[HTTPXInstrumentKwargs]) -> None: ...
def instrument_celery(self, **kwargs: Any) -> None:
"""Instrument `celery` so that spans are automatically created for each task.
Expand Down
2 changes: 1 addition & 1 deletion logfire-api/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "logfire-api"
version = "2.10.0"
version = "2.11.0"
description = "Shim for the Logfire SDK which does nothing unless Logfire is installed"
authors = [
{ name = "Pydantic Team", email = "engineering@pydantic.dev" },
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "logfire"
version = "2.10.0"
version = "2.11.0"
description = "The best Python observability tool! 🪵🔥"
requires-python = ">=3.8"
authors = [
Expand Down
4 changes: 2 additions & 2 deletions uv.lock

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

0 comments on commit 21c457a

Please sign in to comment.