Skip to content

Commit fdfe06d

Browse files
authored
PYD-1066: Remove default_span_processor parameter from configure (#400)
1 parent 5a7ddc4 commit fdfe06d

File tree

3 files changed

+84
-43
lines changed

3 files changed

+84
-43
lines changed

logfire-api/logfire_api/_internal/config.pyi

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ from logfire.version import VERSION as VERSION
2727
from opentelemetry import metrics
2828
from opentelemetry.sdk.metrics.export import MetricReader as MetricReader
2929
from opentelemetry.sdk.trace import SpanProcessor
30-
from opentelemetry.sdk.trace.export import SpanExporter
3130
from opentelemetry.sdk.trace.id_generator import IdGenerator
3231
from pathlib import Path
3332
from typing import Any, Callable, Literal, Sequence
@@ -56,7 +55,7 @@ class PydanticPlugin:
5655
include: set[str] = ...
5756
exclude: set[str] = ...
5857

59-
def configure(*, send_to_logfire: bool | Literal['if-token-present'] | None = None, token: str | None = None, project_name: str | None = None, service_name: str | None = None, service_version: str | None = None, trace_sample_rate: float | None = None, console: ConsoleOptions | Literal[False] | None = None, show_summary: bool | None = None, config_dir: Path | str | None = None, data_dir: Path | str | None = None, base_url: str | None = None, collect_system_metrics: None = None, id_generator: IdGenerator | None = None, ns_timestamp_generator: Callable[[], int] | None = None, processors: None = None, additional_span_processors: Sequence[SpanProcessor] | None = None, default_span_processor: Callable[[SpanExporter], SpanProcessor] | None = None, metric_readers: None = None, additional_metric_readers: Sequence[MetricReader] | None = None, pydantic_plugin: PydanticPlugin | None = None, fast_shutdown: bool = False, scrubbing_patterns: Sequence[str] | None = None, scrubbing_callback: ScrubCallback | None = None, scrubbing: ScrubbingOptions | Literal[False] | None = None, inspect_arguments: bool | None = None, tail_sampling: TailSamplingOptions | None = None) -> None:
58+
def configure(*, send_to_logfire: bool | Literal['if-token-present'] | None = None, token: str | None = None, project_name: str | None = None, service_name: str | None = None, service_version: str | None = None, trace_sample_rate: float | None = None, console: ConsoleOptions | Literal[False] | None = None, show_summary: bool | None = None, config_dir: Path | str | None = None, data_dir: Path | str | None = None, base_url: str | None = None, collect_system_metrics: None = None, id_generator: IdGenerator | None = None, ns_timestamp_generator: Callable[[], int] | None = None, processors: None = None, additional_span_processors: Sequence[SpanProcessor] | None = None, metric_readers: None = None, additional_metric_readers: Sequence[MetricReader] | None = None, pydantic_plugin: PydanticPlugin | None = None, fast_shutdown: bool = False, scrubbing_patterns: Sequence[str] | None = None, scrubbing_callback: ScrubCallback | None = None, scrubbing: ScrubbingOptions | Literal[False] | None = None, inspect_arguments: bool | None = None, tail_sampling: TailSamplingOptions | None = None) -> None:
6059
"""Configure the logfire SDK.
6160
6261
Args:
@@ -89,9 +88,6 @@ def configure(*, send_to_logfire: bool | Literal['if-token-present'] | None = No
8988
Python standard library.
9089
processors: Legacy argument, use `additional_span_processors` instead.
9190
additional_span_processors: Span processors to use in addition to the default processor which exports spans to Logfire's API.
92-
default_span_processor: A function to create the default span processor. Defaults to `BatchSpanProcessor` from the OpenTelemetry SDK. You can configure the export delay for
93-
[`BatchSpanProcessor`](https://opentelemetry-python.readthedocs.io/en/latest/sdk/trace.export.html#opentelemetry.sdk.trace.export.BatchSpanProcessor)
94-
by setting the `OTEL_BSP_SCHEDULE_DELAY_MILLIS` environment variable.
9591
metric_readers: Legacy argument, use `additional_metric_readers` instead.
9692
additional_metric_readers: Sequence of metric readers to be used in addition to the default reader
9793
which exports metrics to Logfire's API.
@@ -133,21 +129,20 @@ class _LogfireConfigData:
133129
ns_timestamp_generator: Callable[[], int]
134130
additional_span_processors: Sequence[SpanProcessor] | None
135131
pydantic_plugin: PydanticPlugin
136-
default_span_processor: Callable[[SpanExporter], SpanProcessor]
137132
fast_shutdown: bool
138133
scrubbing: ScrubbingOptions | Literal[False]
139134
inspect_arguments: bool
140135
tail_sampling: TailSamplingOptions | None
141136

142137
class LogfireConfig(_LogfireConfigData):
143-
def __init__(self, base_url: str | None = None, send_to_logfire: bool | None = None, token: str | None = None, project_name: str | None = None, service_name: str | None = None, service_version: str | None = None, trace_sample_rate: float | None = None, console: ConsoleOptions | Literal[False] | None = None, show_summary: bool | None = None, config_dir: Path | None = None, data_dir: Path | None = None, id_generator: IdGenerator | None = None, ns_timestamp_generator: Callable[[], int] | None = None, additional_span_processors: Sequence[SpanProcessor] | None = None, default_span_processor: Callable[[SpanExporter], SpanProcessor] | None = None, additional_metric_readers: Sequence[MetricReader] | None = None, pydantic_plugin: PydanticPlugin | None = None, fast_shutdown: bool = False, scrubbing: ScrubbingOptions | Literal[False] | None = None, inspect_arguments: bool | None = None, tail_sampling: TailSamplingOptions | None = None) -> None:
138+
def __init__(self, base_url: str | None = None, send_to_logfire: bool | None = None, token: str | None = None, project_name: str | None = None, service_name: str | None = None, service_version: str | None = None, trace_sample_rate: float | None = None, console: ConsoleOptions | Literal[False] | None = None, show_summary: bool | None = None, config_dir: Path | None = None, data_dir: Path | None = None, id_generator: IdGenerator | None = None, ns_timestamp_generator: Callable[[], int] | None = None, additional_span_processors: Sequence[SpanProcessor] | None = None, additional_metric_readers: Sequence[MetricReader] | None = None, pydantic_plugin: PydanticPlugin | None = None, fast_shutdown: bool = False, scrubbing: ScrubbingOptions | Literal[False] | None = None, inspect_arguments: bool | None = None, tail_sampling: TailSamplingOptions | None = None) -> None:
144139
"""Create a new LogfireConfig.
145140
146141
Users should never need to call this directly, instead use `logfire.configure`.
147142
148143
See `_LogfireConfigData` for parameter documentation.
149144
"""
150-
def configure(self, base_url: str | None, send_to_logfire: bool | Literal['if-token-present'] | None, token: str | None, project_name: str | None, service_name: str | None, service_version: str | None, trace_sample_rate: float | None, console: ConsoleOptions | Literal[False] | None, show_summary: bool | None, config_dir: Path | None, data_dir: Path | None, id_generator: IdGenerator | None, ns_timestamp_generator: Callable[[], int] | None, additional_span_processors: Sequence[SpanProcessor] | None, default_span_processor: Callable[[SpanExporter], SpanProcessor] | None, additional_metric_readers: Sequence[MetricReader] | None, pydantic_plugin: PydanticPlugin | None, fast_shutdown: bool, scrubbing: ScrubbingOptions | Literal[False] | None, inspect_arguments: bool | None, tail_sampling: TailSamplingOptions | None) -> None: ...
145+
def configure(self, base_url: str | None, send_to_logfire: bool | Literal['if-token-present'] | None, token: str | None, project_name: str | None, service_name: str | None, service_version: str | None, trace_sample_rate: float | None, console: ConsoleOptions | Literal[False] | None, show_summary: bool | None, config_dir: Path | None, data_dir: Path | None, id_generator: IdGenerator | None, ns_timestamp_generator: Callable[[], int] | None, additional_span_processors: Sequence[SpanProcessor] | None, additional_metric_readers: Sequence[MetricReader] | None, pydantic_plugin: PydanticPlugin | None, fast_shutdown: bool, scrubbing: ScrubbingOptions | Literal[False] | None, inspect_arguments: bool | None, tail_sampling: TailSamplingOptions | None) -> None: ...
151146
def initialize(self) -> ProxyTracerProvider:
152147
"""Configure internals to start exporting traces and metrics."""
153148
def force_flush(self, timeout_millis: int = 30000) -> bool:

logfire/_internal/config.py

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
from opentelemetry.sdk.metrics.view import ExponentialBucketHistogramAggregation, View
4545
from opentelemetry.sdk.resources import Resource
4646
from opentelemetry.sdk.trace import SpanProcessor, TracerProvider as SDKTracerProvider
47-
from opentelemetry.sdk.trace.export import BatchSpanProcessor, SimpleSpanProcessor, SpanExporter
47+
from opentelemetry.sdk.trace.export import BatchSpanProcessor, SimpleSpanProcessor
4848
from opentelemetry.sdk.trace.id_generator import IdGenerator, RandomIdGenerator
4949
from opentelemetry.sdk.trace.sampling import ParentBasedTraceIdRatio
5050
from opentelemetry.semconv.resource import ResourceAttributes
@@ -162,7 +162,6 @@ def configure(
162162
ns_timestamp_generator: Callable[[], int] | None = None,
163163
processors: None = None,
164164
additional_span_processors: Sequence[SpanProcessor] | None = None,
165-
default_span_processor: Callable[[SpanExporter], SpanProcessor] | None = None,
166165
metric_readers: None = None,
167166
additional_metric_readers: Sequence[MetricReader] | None = None,
168167
pydantic_plugin: PydanticPlugin | None = None,
@@ -205,9 +204,6 @@ def configure(
205204
Python standard library.
206205
processors: Legacy argument, use `additional_span_processors` instead.
207206
additional_span_processors: Span processors to use in addition to the default processor which exports spans to Logfire's API.
208-
default_span_processor: A function to create the default span processor. Defaults to `BatchSpanProcessor` from the OpenTelemetry SDK. You can configure the export delay for
209-
[`BatchSpanProcessor`](https://opentelemetry-python.readthedocs.io/en/latest/sdk/trace.export.html#opentelemetry.sdk.trace.export.BatchSpanProcessor)
210-
by setting the `OTEL_BSP_SCHEDULE_DELAY_MILLIS` environment variable.
211207
metric_readers: Legacy argument, use `additional_metric_readers` instead.
212208
additional_metric_readers: Sequence of metric readers to be used in addition to the default reader
213209
which exports metrics to Logfire's API.
@@ -275,7 +271,6 @@ def configure(
275271
id_generator=id_generator,
276272
ns_timestamp_generator=ns_timestamp_generator,
277273
additional_span_processors=additional_span_processors,
278-
default_span_processor=default_span_processor,
279274
additional_metric_readers=additional_metric_readers,
280275
pydantic_plugin=pydantic_plugin,
281276
fast_shutdown=fast_shutdown,
@@ -346,9 +341,6 @@ class _LogfireConfigData:
346341
pydantic_plugin: PydanticPlugin
347342
"""Options for the Pydantic plugin"""
348343

349-
default_span_processor: Callable[[SpanExporter], SpanProcessor]
350-
"""The span processor used for the logfire exporter and console exporter"""
351-
352344
fast_shutdown: bool
353345
"""Whether to shut down exporters and providers quickly, mostly used for tests"""
354346

@@ -380,7 +372,6 @@ def _load_configuration(
380372
id_generator: IdGenerator | None,
381373
ns_timestamp_generator: Callable[[], int] | None,
382374
additional_span_processors: Sequence[SpanProcessor] | None,
383-
default_span_processor: Callable[[SpanExporter], SpanProcessor] | None,
384375
additional_metric_readers: Sequence[MetricReader] | None,
385376
pydantic_plugin: PydanticPlugin | None,
386377
fast_shutdown: bool,
@@ -454,7 +445,6 @@ def _load_configuration(
454445
self.id_generator = id_generator or RandomIdGenerator()
455446
self.ns_timestamp_generator = ns_timestamp_generator or time.time_ns
456447
self.additional_span_processors = additional_span_processors
457-
self.default_span_processor = default_span_processor or _get_default_span_processor
458448
self.additional_metric_readers = additional_metric_readers
459449
if self.service_version is None:
460450
try:
@@ -482,7 +472,6 @@ def __init__(
482472
id_generator: IdGenerator | None = None,
483473
ns_timestamp_generator: Callable[[], int] | None = None,
484474
additional_span_processors: Sequence[SpanProcessor] | None = None,
485-
default_span_processor: Callable[[SpanExporter], SpanProcessor] | None = None,
486475
additional_metric_readers: Sequence[MetricReader] | None = None,
487476
pydantic_plugin: PydanticPlugin | None = None,
488477
fast_shutdown: bool = False,
@@ -513,7 +502,6 @@ def __init__(
513502
id_generator=id_generator,
514503
ns_timestamp_generator=ns_timestamp_generator,
515504
additional_span_processors=additional_span_processors,
516-
default_span_processor=default_span_processor,
517505
additional_metric_readers=additional_metric_readers,
518506
pydantic_plugin=pydantic_plugin,
519507
fast_shutdown=fast_shutdown,
@@ -548,7 +536,6 @@ def configure(
548536
id_generator: IdGenerator | None,
549537
ns_timestamp_generator: Callable[[], int] | None,
550538
additional_span_processors: Sequence[SpanProcessor] | None,
551-
default_span_processor: Callable[[SpanExporter], SpanProcessor] | None,
552539
additional_metric_readers: Sequence[MetricReader] | None,
553540
pydantic_plugin: PydanticPlugin | None,
554541
fast_shutdown: bool,
@@ -573,7 +560,6 @@ def configure(
573560
id_generator,
574561
ns_timestamp_generator,
575562
additional_span_processors,
576-
default_span_processor,
577563
additional_metric_readers,
578564
pydantic_plugin,
579565
fast_shutdown,
@@ -719,7 +705,8 @@ def check_token():
719705
span_exporter, FileSpanExporter(self.data_dir / DEFAULT_FALLBACK_FILE_NAME, warn=True)
720706
)
721707
span_exporter = RemovePendingSpansExporter(span_exporter)
722-
add_span_processor(self.default_span_processor(span_exporter))
708+
schedule_delay_millis = _get_int_from_env(OTEL_BSP_SCHEDULE_DELAY) or 500
709+
add_span_processor(BatchSpanProcessor(span_exporter, schedule_delay_millis=schedule_delay_millis))
723710

724711
metric_readers += [
725712
PeriodicExportingMetricReader(
@@ -905,11 +892,6 @@ def wrapper(*args: Any, **kwargs: Any) -> Any:
905892
traceback.print_exception(e)
906893

907894

908-
def _get_default_span_processor(exporter: SpanExporter) -> SpanProcessor:
909-
schedule_delay_millis = _get_int_from_env(OTEL_BSP_SCHEDULE_DELAY) or 500
910-
return BatchSpanProcessor(exporter, schedule_delay_millis=schedule_delay_millis)
911-
912-
913895
# The global config is the single global object in logfire
914896
# It also does not initialize anything when it's created (right now)
915897
# but when `logfire.configure` aka `GLOBAL_CONFIG.configure` is called

tests/test_configure.py

Lines changed: 78 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import threading
88
from contextlib import ExitStack
99
from pathlib import Path
10+
from time import sleep, time
1011
from typing import Any, Iterable, Sequence
1112
from unittest import mock
1213
from unittest.mock import call, patch
@@ -534,35 +535,98 @@ def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
534535
# This should cause FallbackSpanExporter to call its own fallback file exporter.
535536
return SpanExportResult.FAILURE
536537

537-
def default_span_processor(exporter: SpanExporter) -> SimpleSpanProcessor:
538-
# It's OK if these exporter types change.
539-
# We just need access to the FallbackSpanExporter either way to swap out its underlying exporter.
540-
assert isinstance(exporter, WrapperSpanExporter)
541-
fallback_exporter = exporter.wrapped_exporter
542-
assert isinstance(fallback_exporter, FallbackSpanExporter)
543-
fallback_exporter.exporter = FailureExporter()
544-
545-
return SimpleSpanProcessor(exporter)
546-
538+
data_dir = Path(tmp_path) / 'logfire_data'
547539
with request_mocker:
548-
data_dir = Path(tmp_path) / 'logfire_data'
549540
logfire.configure(
550541
send_to_logfire=True,
551542
data_dir=data_dir,
552543
token='abc1',
553-
default_span_processor=default_span_processor,
554-
additional_metric_readers=[InMemoryMetricReader()],
544+
console=False,
555545
)
556546
wait_for_check_token_thread()
557547

548+
send_to_logfire_processor, *_ = get_span_processors()
549+
# It's OK if these processor/exporter types change.
550+
# We just need access to the FallbackSpanExporter either way to swap out its underlying exporter.
551+
assert isinstance(send_to_logfire_processor, MainSpanProcessorWrapper)
552+
batch_span_processor = send_to_logfire_processor.processor
553+
assert isinstance(batch_span_processor, BatchSpanProcessor)
554+
exporter = batch_span_processor.span_exporter
555+
assert isinstance(exporter, WrapperSpanExporter)
556+
fallback_exporter = exporter.wrapped_exporter
557+
assert isinstance(fallback_exporter, FallbackSpanExporter)
558+
fallback_exporter.exporter = FailureExporter()
559+
560+
with logfire.span('test'):
561+
pass
562+
558563
assert not data_dir.exists()
559564
path = data_dir / 'logfire_spans.bin'
560565

561566
with pytest.warns(WritingFallbackWarning, match=f'Failed to export spans, writing to fallback file: {path}'):
567+
logfire.force_flush()
568+
569+
assert path.exists()
570+
571+
572+
def test_configure_export_delay() -> None:
573+
class TrackingExporter(SpanExporter):
574+
def __init__(self) -> None:
575+
self.last_export_timestamp: float | None = None
576+
self.export_delays: list[float] = []
577+
578+
def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
579+
t = time()
580+
if self.last_export_timestamp is not None:
581+
self.export_delays.append(t - self.last_export_timestamp)
582+
self.last_export_timestamp = t
583+
return SpanExportResult.SUCCESS
584+
585+
def configure_tracking_exporter():
586+
request_mocker = requests_mock.Mocker()
587+
request_mocker.get(
588+
'https://logfire-api.pydantic.dev/v1/info',
589+
json={'project_name': 'myproject', 'project_url': 'fake_project_url'},
590+
)
591+
592+
with request_mocker:
593+
logfire.configure(
594+
send_to_logfire=True,
595+
token='abc1',
596+
console=False,
597+
fast_shutdown=True,
598+
)
599+
wait_for_check_token_thread()
600+
601+
send_to_logfire_processor, *_ = get_span_processors()
602+
assert isinstance(send_to_logfire_processor, MainSpanProcessorWrapper)
603+
batch_span_processor = send_to_logfire_processor.processor
604+
assert isinstance(batch_span_processor, BatchSpanProcessor)
605+
606+
batch_span_processor.span_exporter = TrackingExporter()
607+
return batch_span_processor.span_exporter
608+
609+
def check_delays(exp: TrackingExporter, min_delay: float, max_delay: float) -> None:
610+
for delay in exp.export_delays:
611+
assert min_delay < delay < max_delay, f'delay was {delay}, which is not between {min_delay} and {max_delay}'
612+
613+
# test the default value
614+
exporter = configure_tracking_exporter()
615+
while not exporter.export_delays:
562616
with logfire.span('test'):
563617
pass
618+
sleep(0.1)
619+
check_delays(exporter, 0.4, 1.0) # our default is 500ms
564620

565-
assert path.exists()
621+
# test a very small value
622+
with patch.dict(os.environ, {'OTEL_BSP_SCHEDULE_DELAY': '1'}):
623+
exporter = configure_tracking_exporter()
624+
625+
while not exporter.export_delays:
626+
with logfire.span('test'):
627+
pass
628+
sleep(0.03)
629+
check_delays(exporter, 0.0, 0.1) # since we set 1ms it should be a very short delay
566630

567631

568632
def test_configure_service_version(tmp_path: str) -> None:

0 commit comments

Comments
 (0)