Skip to content

Commit

Permalink
Merge branch 'main' of github.com:pydantic/logfire into alex/instrume…
Browse files Browse the repository at this point in the history
…nt-pydantic
  • Loading branch information
alexmojaki committed Sep 27, 2024
2 parents 5e981da + a996819 commit 889b86a
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 36 deletions.
4 changes: 2 additions & 2 deletions logfire-api/logfire_api/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
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 AdvancedOptions as AdvancedOptions, ConsoleOptions as ConsoleOptions, METRICS_PREFERRED_TEMPORALITY as METRICS_PREFERRED_TEMPORALITY, MetricsOptions as MetricsOptions, PydanticPlugin as PydanticPlugin, configure as configure
from ._internal.config import AdvancedOptions as AdvancedOptions, ConsoleOptions as ConsoleOptions, MetricsOptions as MetricsOptions, 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.main import Logfire as Logfire, LogfireSpan as LogfireSpan
Expand All @@ -11,7 +11,7 @@ from .integrations.structlog import LogfireProcessor as StructlogProcessor
from .version import VERSION as VERSION
from logfire.sampling import SamplingOptions as SamplingOptions

__all__ = ['Logfire', 'LogfireSpan', 'LevelName', 'AdvancedOptions', 'ConsoleOptions', 'PydanticPlugin', 'configure', 'span', 'instrument', 'log', 'trace', 'debug', 'notice', 'info', 'warn', 'error', 'exception', 'fatal', 'force_flush', 'log_slow_async_callbacks', 'install_auto_tracing', 'instrument_pydantic', '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', 'instrument_mysql', 'instrument_system_metrics', 'AutoTraceModule', 'with_tags', 'with_settings', 'shutdown', 'load_spans_from_file', 'no_auto_trace', 'METRICS_PREFERRED_TEMPORALITY', 'ScrubMatch', 'ScrubbingOptions', 'VERSION', 'suppress_instrumentation', 'StructlogProcessor', 'LogfireLoggingHandler', 'SamplingOptions', 'MetricsOptions']
__all__ = ['Logfire', 'LogfireSpan', 'LevelName', 'AdvancedOptions', 'ConsoleOptions', 'PydanticPlugin', 'configure', 'span', 'instrument', 'log', 'trace', 'debug', 'notice', 'info', 'warn', 'error', 'exception', 'fatal', 'force_flush', 'log_slow_async_callbacks', 'install_auto_tracing', 'instrument_pydantic', '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', 'instrument_mysql', 'instrument_system_metrics', 'AutoTraceModule', 'with_tags', 'with_settings', 'shutdown', 'load_spans_from_file', 'no_auto_trace', 'ScrubMatch', 'ScrubbingOptions', 'VERSION', 'suppress_instrumentation', 'StructlogProcessor', 'LogfireLoggingHandler', 'SamplingOptions', 'MetricsOptions']

DEFAULT_LOGFIRE_INSTANCE = Logfire()
span = DEFAULT_LOGFIRE_INSTANCE.span
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 @@ -17,7 +17,7 @@ from .metrics import ProxyMeterProvider as ProxyMeterProvider
from .scrubbing import BaseScrubber as BaseScrubber, NOOP_SCRUBBER as NOOP_SCRUBBER, Scrubber as Scrubber, ScrubbingOptions as ScrubbingOptions
from .stack_info import warn_at_user_stacklevel as warn_at_user_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, read_toml_file as read_toml_file, suppress_instrumentation as suppress_instrumentation
from .utils import SeededRandomIdGenerator as SeededRandomIdGenerator, UnexpectedResponse as UnexpectedResponse, ensure_data_dir_exists as ensure_data_dir_exists, read_toml_file as read_toml_file, suppress_instrumentation as suppress_instrumentation
from _typeshed import Incomplete
from dataclasses import dataclass
from functools import cached_property
Expand Down
19 changes: 19 additions & 0 deletions logfire-api/logfire_api/_internal/utils.pyi
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from _typeshed import Incomplete
from collections.abc import Generator
from dataclasses import dataclass
from logfire._internal.stack_info import is_user_code as is_user_code
from opentelemetry import trace as trace_api
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import Event as Event, ReadableSpan
from opentelemetry.sdk.trace.id_generator import IdGenerator
from opentelemetry.sdk.util.instrumentation import InstrumentationScope
from opentelemetry.trace.status import Status
from opentelemetry.util import types as otel_types
Expand Down Expand Up @@ -88,3 +90,20 @@ def log_internal_error() -> None: ...
def handle_internal_errors() -> Generator[None, None, None]: ...
def maybe_capture_server_headers(capture: bool): ...
def is_asgi_send_receive_span_name(name: str) -> bool: ...

@dataclass(repr=True)
class SeededRandomIdGenerator(IdGenerator):
"""Generate random span/trace IDs from a seed for deterministic tests.
Similar to RandomIdGenerator from OpenTelemetry, but with a seed.
Set the seed to None for non-deterministic randomness.
In that case the difference from RandomIdGenerator is that it's not affected by `random.seed(...)`.
Trace IDs are 128-bit integers.
Span IDs are 64-bit integers.
"""
seed: int | None = ...
random = ...
def __post_init__(self) -> None: ...
def generate_span_id(self) -> int: ...
def generate_trace_id(self) -> int: ...
2 changes: 0 additions & 2 deletions logfire/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from ._internal.auto_trace import AutoTraceModule
from ._internal.auto_trace.rewrite_ast import no_auto_trace
from ._internal.config import (
METRICS_PREFERRED_TEMPORALITY,
AdvancedOptions,
ConsoleOptions,
MetricsOptions,
Expand Down Expand Up @@ -135,7 +134,6 @@ def loguru_handler() -> dict[str, Any]:
'shutdown',
'load_spans_from_file',
'no_auto_trace',
'METRICS_PREFERRED_TEMPORALITY',
'ScrubMatch',
'ScrubbingOptions',
'VERSION',
Expand Down
22 changes: 17 additions & 5 deletions logfire/_internal/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import SpanProcessor, TracerProvider as SDKTracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor, SimpleSpanProcessor
from opentelemetry.sdk.trace.id_generator import IdGenerator, RandomIdGenerator
from opentelemetry.sdk.trace.id_generator import IdGenerator
from opentelemetry.sdk.trace.sampling import ParentBasedTraceIdRatio, Sampler
from opentelemetry.semconv.resource import ResourceAttributes
from rich.console import Console
Expand Down Expand Up @@ -83,7 +83,13 @@
from .scrubbing import NOOP_SCRUBBER, BaseScrubber, Scrubber, ScrubbingOptions
from .stack_info import warn_at_user_stacklevel
from .tracer import PendingSpanProcessor, ProxyTracerProvider
from .utils import UnexpectedResponse, ensure_data_dir_exists, read_toml_file, suppress_instrumentation
from .utils import (
SeededRandomIdGenerator,
UnexpectedResponse,
ensure_data_dir_exists,
read_toml_file,
suppress_instrumentation,
)

if TYPE_CHECKING:
from .main import FastLogfireSpan, LogfireSpan
Expand All @@ -105,7 +111,10 @@
ObservableUpDownCounter: AggregationTemporality.CUMULATIVE,
ObservableGauge: AggregationTemporality.CUMULATIVE,
}
"""This should be passed as the `preferred_temporality` argument of metric readers and exporters."""
"""
This should be passed as the `preferred_temporality` argument of metric readers and exporters
which send to the Logfire backend.
"""


@dataclass
Expand Down Expand Up @@ -136,8 +145,11 @@ class AdvancedOptions:
base_url: str = 'https://logfire-api.pydantic.dev'
"""Root URL for the Logfire API."""

id_generator: IdGenerator = dataclasses.field(default_factory=RandomIdGenerator)
"""Generator for trace and span IDs."""
id_generator: IdGenerator = dataclasses.field(default_factory=lambda: SeededRandomIdGenerator(None))
"""Generator for trace and span IDs.
The default generates random IDs and is unaffected by calls to `random.seed()`.
"""

ns_timestamp_generator: Callable[[], int] = time.time_ns
"""Generator for nanosecond start and end timestamps of spans."""
Expand Down
33 changes: 33 additions & 0 deletions logfire/_internal/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@
import json
import logging
import os
import random
import sys
from contextlib import contextmanager
from dataclasses import dataclass
from pathlib import Path
from types import TracebackType
from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Sequence, Tuple, TypedDict, TypeVar, Union

from opentelemetry import context, trace as trace_api
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import Event, ReadableSpan
from opentelemetry.sdk.trace.id_generator import IdGenerator
from opentelemetry.sdk.util.instrumentation import InstrumentationScope
from opentelemetry.trace.status import Status
from opentelemetry.util import types as otel_types
Expand Down Expand Up @@ -348,3 +351,33 @@ def maybe_capture_server_headers(capture: bool):

def is_asgi_send_receive_span_name(name: str) -> bool:
return name.endswith((' http send', ' http receive', ' websocket send', ' websocket receive'))


@dataclass(repr=True)
class SeededRandomIdGenerator(IdGenerator):
"""Generate random span/trace IDs from a seed for deterministic tests.
Similar to RandomIdGenerator from OpenTelemetry, but with a seed.
Set the seed to None for non-deterministic randomness.
In that case the difference from RandomIdGenerator is that it's not affected by `random.seed(...)`.
Trace IDs are 128-bit integers.
Span IDs are 64-bit integers.
"""

seed: int | None = 0

def __post_init__(self) -> None:
self.random = random.Random(self.seed)

def generate_span_id(self) -> int:
span_id = self.random.getrandbits(64)
while span_id == trace_api.INVALID_SPAN_ID: # pragma: no cover
span_id = self.random.getrandbits(64)
return span_id

def generate_trace_id(self) -> int:
trace_id = self.random.getrandbits(128)
while trace_id == trace_api.INVALID_TRACE_ID: # pragma: no cover
trace_id = self.random.getrandbits(128)
return trace_id
24 changes: 1 addition & 23 deletions logfire/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from __future__ import annotations

import random
from dataclasses import dataclass

import pytest
Expand All @@ -14,6 +13,7 @@

from ._internal.constants import ONE_SECOND_IN_NANOSECONDS
from ._internal.exporters.test import TestExporter
from ._internal.utils import SeededRandomIdGenerator

__all__ = [
'capfire',
Expand Down Expand Up @@ -56,28 +56,6 @@ def generate_trace_id(self) -> int:
return self.trace_id_counter


@dataclass(repr=True)
class SeededRandomIdGenerator(IdGenerator):
"""Generate random span/trace IDs from a random seed for deterministic tests.
Trace IDs are 64-bit integers.
Span IDs are 32-bit integers.
"""

seed: int = 0

def __post_init__(self) -> None:
self.random = random.Random(self.seed)

def generate_span_id(self) -> int:
"""Generates a random span id."""
return self.random.getrandbits(64)

def generate_trace_id(self) -> int:
"""Generates a random trace id."""
return self.random.getrandbits(128)


# Making this a dataclass causes errors in the process pool end-to-end tests
class TimeGenerator:
"""Generate incrementing timestamps for testing.
Expand Down
3 changes: 0 additions & 3 deletions tests/test_logfire_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,6 @@ def func() -> None: ...
logfire_api.MetricsOptions()
logfire__all__.remove('MetricsOptions')

assert hasattr(logfire_api, 'METRICS_PREFERRED_TEMPORALITY')
logfire__all__.remove('METRICS_PREFERRED_TEMPORALITY')

assert hasattr(logfire_api, 'load_spans_from_file')
logfire_api.load_spans_from_file(file_path='test')
logfire__all__.remove('load_spans_from_file')
Expand Down

0 comments on commit 889b86a

Please sign in to comment.