diff --git a/litellm/integrations/otel/plumbing/providers.py b/litellm/integrations/otel/plumbing/providers.py index 8aee90ffe0a..0fbe70a2ae4 100644 --- a/litellm/integrations/otel/plumbing/providers.py +++ b/litellm/integrations/otel/plumbing/providers.py @@ -1,5 +1,6 @@ """Provider / exporter factory + the Baggage span processor.""" +import math import os import queue import threading @@ -940,13 +941,13 @@ def build_metric_reader(config: OpenTelemetryV2Config) -> "MetricReader": else: exporter = ConsoleMetricExporter() - return PeriodicExportingMetricReader(exporter, export_interval_millis=_metric_export_interval_millis()) + return PeriodicExportingMetricReader(exporter, export_interval_millis=resolve_metric_export_interval_millis()) DEFAULT_METRIC_EXPORT_INTERVAL_MILLIS: Final = 5000 -def _metric_export_interval_millis() -> float: +def resolve_metric_export_interval_millis() -> float: """The metric export period: ``OTEL_METRIC_EXPORT_INTERVAL`` if set, else 5s. The SDK only consults the variable when no explicit interval is passed, so @@ -965,9 +966,9 @@ def _metric_export_interval_millis() -> float: DEFAULT_METRIC_EXPORT_INTERVAL_MILLIS, ) return DEFAULT_METRIC_EXPORT_INTERVAL_MILLIS - if interval <= 0: + if not math.isfinite(interval) or interval <= 0: verbose_logger.warning( - "OTEL_METRIC_EXPORT_INTERVAL=%r must be positive; using %sms", + "OTEL_METRIC_EXPORT_INTERVAL=%r must be a positive finite number; using %sms", raw, DEFAULT_METRIC_EXPORT_INTERVAL_MILLIS, ) diff --git a/tests/unit/integrations/otel/test_otel_v2_components.py b/tests/unit/integrations/otel/test_otel_v2_components.py index 6f6003659aa..5e5bb6244b2 100644 --- a/tests/unit/integrations/otel/test_otel_v2_components.py +++ b/tests/unit/integrations/otel/test_otel_v2_components.py @@ -893,13 +893,12 @@ def test_otlp_metric_exporter_uses_cumulative_histogram_temporality(): assert temporality[Histogram] is AggregationTemporality.CUMULATIVE -def test_metric_reader_default_interval_is_5s(monkeypatch): +def test_metric_export_interval_defaults_to_5s(monkeypatch): monkeypatch.delenv("OTEL_METRIC_EXPORT_INTERVAL", raising=False) - reader = providers.build_metric_reader(OpenTelemetryV2Config(exporter="console")) - assert reader._export_interval_millis == 5000 # noqa: SLF001 # reader exposes no public accessor + assert providers.resolve_metric_export_interval_millis() == 5000 -def test_metric_reader_honours_otel_metric_export_interval(monkeypatch): +def test_metric_export_interval_honours_otel_metric_export_interval(monkeypatch): """``OTEL_METRIC_EXPORT_INTERVAL`` is the standard knob for the export period. The SDK reads it only when no explicit interval is passed, and litellm @@ -909,15 +908,14 @@ def test_metric_reader_honours_otel_metric_export_interval(monkeypatch): charges for. """ monkeypatch.setenv("OTEL_METRIC_EXPORT_INTERVAL", "60000") - reader = providers.build_metric_reader(OpenTelemetryV2Config(exporter="console")) - assert reader._export_interval_millis == 60000 # noqa: SLF001 # reader exposes no public accessor + assert providers.resolve_metric_export_interval_millis() == 60000 -@pytest.mark.parametrize("raw", ["", "abc", "0", "-5"]) -def test_metric_reader_rejects_bad_interval(monkeypatch, raw): +@pytest.mark.parametrize("raw", ["", "abc", "0", "-5", "nan", "inf", "-inf"]) +def test_metric_export_interval_rejects_bad_values(monkeypatch, raw): + """A bad value keeps the 5s default; ``inf`` in particular would start no export worker.""" monkeypatch.setenv("OTEL_METRIC_EXPORT_INTERVAL", raw) - reader = providers.build_metric_reader(OpenTelemetryV2Config(exporter="console")) - assert reader._export_interval_millis == 5000 # noqa: SLF001 # reader exposes no public accessor + assert providers.resolve_metric_export_interval_millis() == 5000 def test_otlp_logs_endpoint_normalization():