Skip to content

Commit

Permalink
Release 0.48.0 (#334)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexmojaki authored Jul 24, 2024
1 parent b3ca715 commit f3c50a2
Show file tree
Hide file tree
Showing 17 changed files with 130 additions and 48 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Release Notes

## [v0.48.0] (2024-07-24)

* Add `instrument_celery` method by @Kludex in https://github.com/pydantic/logfire/pull/322
* `capture_headers` by @alexmojaki in https://github.com/pydantic/logfire/pull/318
* Handle message formatting errors by @alexmojaki in https://github.com/pydantic/logfire/pull/329
* Handle logging `None` with `loguru` by @alexmojaki in https://github.com/pydantic/logfire/pull/331

## [v0.47.0] (2024-07-20)

* Fix recursive logging from OTEL's `BatchSpanProcessor` by @alexmojaki in https://github.com/pydantic/logfire/pull/306
Expand Down Expand Up @@ -214,6 +221,7 @@ First release from new repo!
* Ensure `logfire.testing` doesn't depend on pydantic and eval_type_backport by @alexmojaki in https://github.com/pydantic/logfire/pull/40
* Allow using pydantic plugin with models defined before calling logfire.configure by @alexmojaki in https://github.com/pydantic/logfire/pull/36

[v0.48.0]: https://github.com/pydantic/logfire/compare/v0.47.0...v0.48.0
[v0.47.0]: https://github.com/pydantic/logfire/compare/v0.46.1...v0.47.0
[v0.46.1]: https://github.com/pydantic/logfire/compare/v0.46.0...v0.46.1
[v0.46.0]: https://github.com/pydantic/logfire/compare/v0.45.1...v0.46.0
Expand Down
3 changes: 2 additions & 1 deletion logfire-api/logfire_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def with_settings(self, *args, **kwargs) -> Logfire:

def force_flush(self, *args, **kwargs) -> None: ...

def log_slow_async_callbacks(self, *args, **kwargs) -> None: # pragma: no branch
def log_slow_async_callbacks(self, *args, **kwargs) -> None: # pragma: no branch
return nullcontext()

def install_auto_tracing(self, *args, **kwargs) -> None: ...
Expand Down Expand Up @@ -143,6 +143,7 @@ def shutdown(self, *args, **kwargs) -> None: ...
instrument_openai = DEFAULT_LOGFIRE_INSTANCE.instrument_openai
instrument_anthropic = DEFAULT_LOGFIRE_INSTANCE.instrument_anthropic
instrument_asyncpg = DEFAULT_LOGFIRE_INSTANCE.instrument_asyncpg
instrument_celery = DEFAULT_LOGFIRE_INSTANCE.instrument_celery
instrument_httpx = DEFAULT_LOGFIRE_INSTANCE.instrument_httpx
instrument_requests = DEFAULT_LOGFIRE_INSTANCE.instrument_requests
instrument_psycopg = DEFAULT_LOGFIRE_INSTANCE.instrument_psycopg
Expand Down
59 changes: 57 additions & 2 deletions logfire-api/logfire_api/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
from ._internal.auto_trace import AutoTraceModule as AutoTraceModule
from ._internal.auto_trace.rewrite_ast import no_auto_trace as no_auto_trace
from ._internal.config import ConsoleOptions as ConsoleOptions, METRICS_PREFERRED_TEMPORALITY as METRICS_PREFERRED_TEMPORALITY, PydanticPlugin as PydanticPlugin, configure as configure
from ._internal.config import (
ConsoleOptions as ConsoleOptions,
METRICS_PREFERRED_TEMPORALITY as METRICS_PREFERRED_TEMPORALITY,
PydanticPlugin as PydanticPlugin,
configure as configure,
)
from ._internal.constants import LevelName as LevelName
from ._internal.exporters.file import load_file as load_spans_from_file
from ._internal.exporters.tail_sampling import TailSamplingOptions as TailSamplingOptions
Expand All @@ -11,7 +16,56 @@ from .integrations.logging import LogfireLoggingHandler as LogfireLoggingHandler
from .integrations.structlog import LogfireProcessor as StructlogProcessor
from .version import VERSION as VERSION

__all__ = ['Logfire', 'LogfireSpan', 'LevelName', 'ConsoleOptions', 'PydanticPlugin', 'configure', 'span', 'instrument', 'log', 'trace', 'debug', 'notice', 'info', 'warn', 'error', 'fatal', 'force_flush', 'log_slow_async_callbacks', 'install_auto_tracing', 'instrument_fastapi', 'instrument_openai', 'instrument_anthropic', 'instrument_asyncpg', 'instrument_httpx', 'instrument_requests', 'instrument_psycopg', 'instrument_django', 'instrument_flask', 'instrument_starlette', 'instrument_aiohttp_client', 'instrument_sqlalchemy', 'instrument_redis', 'instrument_pymongo', 'AutoTraceModule', 'with_tags', 'with_settings', 'shutdown', 'load_spans_from_file', 'no_auto_trace', 'METRICS_PREFERRED_TEMPORALITY', 'ScrubMatch', 'ScrubbingOptions', 'VERSION', 'suppress_instrumentation', 'StructlogProcessor', 'LogfireLoggingHandler', 'TailSamplingOptions']
__all__ = [
'Logfire',
'LogfireSpan',
'LevelName',
'ConsoleOptions',
'PydanticPlugin',
'configure',
'span',
'instrument',
'log',
'trace',
'debug',
'notice',
'info',
'warn',
'error',
'fatal',
'force_flush',
'log_slow_async_callbacks',
'install_auto_tracing',
'instrument_fastapi',
'instrument_openai',
'instrument_anthropic',
'instrument_asyncpg',
'instrument_httpx',
'instrument_celery',
'instrument_requests',
'instrument_psycopg',
'instrument_django',
'instrument_flask',
'instrument_starlette',
'instrument_aiohttp_client',
'instrument_sqlalchemy',
'instrument_redis',
'instrument_pymongo',
'AutoTraceModule',
'with_tags',
'with_settings',
'shutdown',
'load_spans_from_file',
'no_auto_trace',
'METRICS_PREFERRED_TEMPORALITY',
'ScrubMatch',
'ScrubbingOptions',
'VERSION',
'suppress_instrumentation',
'StructlogProcessor',
'LogfireLoggingHandler',
'TailSamplingOptions',
]

DEFAULT_LOGFIRE_INSTANCE = Logfire()
span = DEFAULT_LOGFIRE_INSTANCE.span
Expand All @@ -24,6 +78,7 @@ instrument_openai = DEFAULT_LOGFIRE_INSTANCE.instrument_openai
instrument_anthropic = DEFAULT_LOGFIRE_INSTANCE.instrument_anthropic
instrument_asyncpg = DEFAULT_LOGFIRE_INSTANCE.instrument_asyncpg
instrument_httpx = DEFAULT_LOGFIRE_INSTANCE.instrument_httpx
instrument_celery = DEFAULT_LOGFIRE_INSTANCE.instrument_celery
instrument_requests = DEFAULT_LOGFIRE_INSTANCE.instrument_requests
instrument_psycopg = DEFAULT_LOGFIRE_INSTANCE.instrument_psycopg
instrument_django = DEFAULT_LOGFIRE_INSTANCE.instrument_django
Expand Down
2 changes: 1 addition & 1 deletion logfire-api/logfire_api/_internal/config.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ from .exporters.remove_pending import RemovePendingSpansExporter as RemovePendin
from .exporters.tail_sampling import TailSamplingOptions as TailSamplingOptions, TailSamplingProcessor as TailSamplingProcessor
from .integrations.executors import instrument_executors as instrument_executors
from .metrics import ProxyMeterProvider as ProxyMeterProvider, configure_metrics as configure_metrics
from .scrubbing import BaseScrubber as BaseScrubber, NoopScrubber as NoopScrubber, ScrubCallback as ScrubCallback, Scrubber as Scrubber, ScrubbingOptions as ScrubbingOptions
from .scrubbing import BaseScrubber as BaseScrubber, NOOP_SCRUBBER as NOOP_SCRUBBER, ScrubCallback as ScrubCallback, Scrubber as Scrubber, ScrubbingOptions as ScrubbingOptions
from .stack_info import get_user_frame_and_stacklevel as get_user_frame_and_stacklevel
from .tracer import PendingSpanProcessor as PendingSpanProcessor, ProxyTracerProvider as ProxyTracerProvider
from .utils import UnexpectedResponse as UnexpectedResponse, ensure_data_dir_exists as ensure_data_dir_exists, get_version as get_version, read_toml_file as read_toml_file, suppress_instrumentation as suppress_instrumentation
Expand Down
18 changes: 14 additions & 4 deletions logfire-api/logfire_api/_internal/formatter.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import ast
import executing
import types
from .constants import ATTRIBUTES_SCRUBBED_KEY as ATTRIBUTES_SCRUBBED_KEY, MESSAGE_FORMATTED_VALUE_LENGTH_LIMIT as MESSAGE_FORMATTED_VALUE_LENGTH_LIMIT
from .scrubbing import BaseScrubber as BaseScrubber, ScrubbedNote as ScrubbedNote
from .utils import truncate_string as truncate_string
from .scrubbing import BaseScrubber as BaseScrubber, NOOP_SCRUBBER as NOOP_SCRUBBER, ScrubbedNote as ScrubbedNote
from .utils import log_internal_error as log_internal_error, truncate_string as truncate_string
from _typeshed import Incomplete
from logfire._internal.stack_info import get_user_frame_and_stacklevel as get_user_frame_and_stacklevel
from string import Formatter
from types import CodeType as CodeType
from typing import Any, Final, Literal, Mapping
from typing import Any, Final, Literal
from typing_extensions import NotRequired, TypedDict

class LiteralChunk(TypedDict):
Expand All @@ -22,7 +22,7 @@ class ArgChunk(TypedDict):

class ChunksFormatter(Formatter):
NONE_REPR: Final[str]
def chunks(self, format_string: str, kwargs: Mapping[str, Any], *, scrubber: BaseScrubber, fstring_frame: types.FrameType | None = None) -> tuple[list[LiteralChunk | ArgChunk], dict[str, Any], str]: ...
def chunks(self, format_string: str, kwargs: dict[str, Any], *, scrubber: BaseScrubber, fstring_frame: types.FrameType | None = None) -> tuple[list[LiteralChunk | ArgChunk], dict[str, Any], str]: ...

chunks_formatter: Incomplete

Expand All @@ -48,3 +48,13 @@ def get_stacklevel(frame: types.FrameType): ...
class InspectArgumentsFailedWarning(Warning): ...

def warn_inspect_arguments(msg: str, stacklevel: int): ...

class KnownFormattingError(Exception):
"""An error raised when there's something wrong with a format string or the field values.
In other words this should correspond to errors that would be raised when using `str.format`,
and generally indicate a user error, most likely that they weren't trying to pass a template string at all.
"""
class FormattingFailedWarning(UserWarning): ...

def warn_formatting(msg: str): ...
10 changes: 10 additions & 0 deletions logfire-api/logfire_api/_internal/integrations/celery.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from typing_extensions import TypedDict, Unpack

class CeleryInstrumentKwargs(TypedDict, total=False):
skip_dep_check: bool

def instrument_celery(**kwargs: Unpack[CeleryInstrumentKwargs]) -> None:
"""Instrument the `celery` module so that spans are automatically created for each task.
See the `Logfire.instrument_celery` method for details.
"""
3 changes: 2 additions & 1 deletion logfire-api/logfire_api/_internal/integrations/django.pyi
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from logfire._internal.utils import maybe_capture_server_headers as maybe_capture_server_headers
from typing import Any

def instrument_django(**kwargs: Any):
def instrument_django(*, capture_headers: bool = False, **kwargs: Any):
"""Instrument the `django` module so that spans are automatically created for each web request.
See the `Logfire.instrument_django` method for details.
Expand Down
3 changes: 2 additions & 1 deletion logfire-api/logfire_api/_internal/integrations/fastapi.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from ..main import Logfire as Logfire
from ..stack_info import StackInfo as StackInfo, get_code_object_info as get_code_object_info
from ..utils import maybe_capture_server_headers as maybe_capture_server_headers
from _typeshed import Incomplete
from fastapi import FastAPI
from starlette.requests import Request
Expand All @@ -8,7 +9,7 @@ from typing import Any, Awaitable, Callable, ContextManager, Iterable

def find_mounted_apps(app: FastAPI) -> list[FastAPI]:
"""Fetch all sub-apps mounted to a FastAPI app, including nested sub-apps."""
def instrument_fastapi(logfire_instance: Logfire, app: FastAPI, *, request_attributes_mapper: Callable[[Request | WebSocket, dict[str, Any]], dict[str, Any] | None] | None = None, use_opentelemetry_instrumentation: bool = True, excluded_urls: str | Iterable[str] | None = None, **opentelemetry_kwargs: Any) -> ContextManager[None]:
def instrument_fastapi(logfire_instance: Logfire, app: FastAPI, *, capture_headers: bool = False, request_attributes_mapper: Callable[[Request | WebSocket, dict[str, Any]], dict[str, Any] | None] | None = None, use_opentelemetry_instrumentation: bool = True, excluded_urls: str | Iterable[str] | None = None, **opentelemetry_kwargs: Any) -> ContextManager[None]:
"""Instrument a FastAPI app so that spans and logs are automatically created for each request.
See `Logfire.instrument_fastapi` for more details.
Expand Down
3 changes: 2 additions & 1 deletion logfire-api/logfire_api/_internal/integrations/flask.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from flask.app import Flask
from logfire._internal.utils import maybe_capture_server_headers as maybe_capture_server_headers
from opentelemetry.trace import Span
from typing_extensions import Protocol, TypedDict, Unpack
from wsgiref.types import WSGIEnvironment
Expand All @@ -16,7 +17,7 @@ class FlaskInstrumentKwargs(TypedDict, total=False):
enable_commenter: bool | None
commenter_options: dict[str, str] | None

def instrument_flask(app: Flask, **kwargs: Unpack[FlaskInstrumentKwargs]):
def instrument_flask(app: Flask, capture_headers: bool = False, **kwargs: Unpack[FlaskInstrumentKwargs]):
"""Instrument `app` so that spans are automatically created for each request.
See the `Logfire.instrument_flask` method for details.
Expand Down
3 changes: 2 additions & 1 deletion logfire-api/logfire_api/_internal/integrations/starlette.pyi
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from logfire._internal.utils import maybe_capture_server_headers as maybe_capture_server_headers
from opentelemetry.trace import Span
from starlette.applications import Starlette
from typing import Any
Expand All @@ -17,7 +18,7 @@ class StarletteInstrumentKwargs(TypedDict, total=False):
client_request_hook: ClientRequestHook | None
client_response_hook: ClientResponseHook | None

def instrument_starlette(app: Starlette, **kwargs: Unpack[StarletteInstrumentKwargs]):
def instrument_starlette(app: Starlette, *, capture_headers: bool = False, **kwargs: Unpack[StarletteInstrumentKwargs]):
"""Instrument `app` so that spans are automatically created for each request.
See the `Logfire.instrument_starlette` method for details.
Expand Down
22 changes: 18 additions & 4 deletions logfire-api/logfire_api/_internal/main.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ from .config import GLOBAL_CONFIG as GLOBAL_CONFIG, LogfireConfig as LogfireConf
from .constants import ATTRIBUTES_JSON_SCHEMA_KEY as ATTRIBUTES_JSON_SCHEMA_KEY, ATTRIBUTES_MESSAGE_KEY as ATTRIBUTES_MESSAGE_KEY, ATTRIBUTES_MESSAGE_TEMPLATE_KEY as ATTRIBUTES_MESSAGE_TEMPLATE_KEY, ATTRIBUTES_SAMPLE_RATE_KEY as ATTRIBUTES_SAMPLE_RATE_KEY, ATTRIBUTES_SPAN_TYPE_KEY as ATTRIBUTES_SPAN_TYPE_KEY, ATTRIBUTES_TAGS_KEY as ATTRIBUTES_TAGS_KEY, ATTRIBUTES_VALIDATION_ERROR_KEY as ATTRIBUTES_VALIDATION_ERROR_KEY, DISABLE_CONSOLE_KEY as DISABLE_CONSOLE_KEY, LevelName as LevelName, NULL_ARGS_KEY as NULL_ARGS_KEY, OTLP_MAX_INT_SIZE as OTLP_MAX_INT_SIZE, log_level_attributes as log_level_attributes
from .formatter import logfire_format as logfire_format, logfire_format_with_magic as logfire_format_with_magic
from .instrument import LogfireArgs as LogfireArgs, instrument as instrument
from .integrations.celery import CeleryInstrumentKwargs as CeleryInstrumentKwargs
from .integrations.flask import FlaskInstrumentKwargs as FlaskInstrumentKwargs
from .integrations.psycopg import PsycopgInstrumentKwargs as PsycopgInstrumentKwargs
from .integrations.pymongo import PymongoInstrumentKwargs as PymongoInstrumentKwargs
Expand Down Expand Up @@ -365,11 +366,12 @@ class Logfire:
Otherwise, the first time(s) each function is called, it will be timed but not traced.
Only after the function has run for at least `min_duration` will it be traced in subsequent calls.
"""
def instrument_fastapi(self, app: FastAPI, *, request_attributes_mapper: Callable[[Request | WebSocket, dict[str, Any]], dict[str, Any] | None] | None = None, use_opentelemetry_instrumentation: bool = True, excluded_urls: str | Iterable[str] | None = None, **opentelemetry_kwargs: Any) -> ContextManager[None]:
def instrument_fastapi(self, app: FastAPI, *, capture_headers: bool = False, request_attributes_mapper: Callable[[Request | WebSocket, dict[str, Any]], dict[str, Any] | None] | None = None, use_opentelemetry_instrumentation: bool = True, excluded_urls: str | Iterable[str] | None = None, **opentelemetry_kwargs: Any) -> ContextManager[None]:
"""Instrument a FastAPI app so that spans and logs are automatically created for each request.
Args:
app: The FastAPI app to instrument.
capture_headers: Set to `True` to capture all request and response headers.
request_attributes_mapper: A function that takes a [`Request`][fastapi.Request] or [`WebSocket`][fastapi.WebSocket]
and a dictionary of attributes and returns a new dictionary of attributes.
The input dictionary will contain:
Expand Down Expand Up @@ -510,14 +512,22 @@ class Logfire:
[OpenTelemetry HTTPX Instrumentation](https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/httpx/httpx.html)
library, specifically `HTTPXClientInstrumentor().instrument()`, to which it passes `**kwargs`.
"""
def instrument_django(self, is_sql_commentor_enabled: bool | None = None, request_hook: Callable[[Span, HttpRequest], None] | None = None, response_hook: Callable[[Span, HttpRequest, HttpResponse], None] | None = None, excluded_urls: str | None = None, **kwargs: Any) -> None:
def instrument_celery(self, **kwargs: Unpack[CeleryInstrumentKwargs]) -> None:
"""Instrument `celery` so that spans are automatically created for each task.
Uses the
[OpenTelemetry Celery Instrumentation](https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/celery/celery.html)
library.
"""
def instrument_django(self, capture_headers: bool = False, is_sql_commentor_enabled: bool | None = None, request_hook: Callable[[Span, HttpRequest], None] | None = None, response_hook: Callable[[Span, HttpRequest, HttpResponse], None] | None = None, excluded_urls: str | None = None, **kwargs: Any) -> None:
"""Instrument `django` so that spans are automatically created for each web request.
Uses the
[OpenTelemetry Django Instrumentation](https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/django/django.html)
library.
Args:
capture_headers: Set to `True` to capture all request and response headers.
is_sql_commentor_enabled: Adds comments to SQL queries performed by Django,
so that database logs have additional context.
Expand Down Expand Up @@ -567,16 +577,20 @@ class Logfire:
**kwargs: Additional keyword arguments to pass to the OpenTelemetry `instrument` methods,
particularly `enable_commenter` and `commenter_options`.
"""
def instrument_flask(self, app: Flask, **kwargs: Unpack[FlaskInstrumentKwargs]) -> None:
def instrument_flask(self, app: Flask, *, capture_headers: bool = False, **kwargs: Unpack[FlaskInstrumentKwargs]) -> None:
"""Instrument `app` so that spans are automatically created for each request.
Set `capture_headers` to `True` to capture all request and response headers.
Uses the
[OpenTelemetry Flask Instrumentation](https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/flask/flask.html)
library, specifically `FlaskInstrumentor().instrument_app()`, to which it passes `**kwargs`.
"""
def instrument_starlette(self, app: Starlette, **kwargs: Unpack[StarletteInstrumentKwargs]) -> None:
def instrument_starlette(self, app: Starlette, *, capture_headers: bool = False, **kwargs: Unpack[StarletteInstrumentKwargs]) -> None:
"""Instrument `app` so that spans are automatically created for each request.
Set `capture_headers` to `True` to capture all request and response headers.
Uses the
[OpenTelemetry Starlette Instrumentation](https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/starlette/starlette.html)
library, specifically `StarletteInstrumentor.instrument_app()`, to which it passes `**kwargs`.
Expand Down
Loading

0 comments on commit f3c50a2

Please sign in to comment.