fix(otel): reject non-finite metric export intervals and test the resolver directly

An `inf` or `nan` OTEL_METRIC_EXPORT_INTERVAL passed the positive check, and
the SDK starts no periodic export worker for an infinite period, so metric
export stopped silently. Treat any non-finite value like any other bad
value: warn and keep the 5s default.

The tests now call the public resolver instead of reading the reader's
private `_export_interval_millis`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Mihidum Hettiyahandi 2026-09-14 09:15:48 +10:00
parent 5f09c3e48a
commit c8ef233929
2 changed files with 13 additions and 14 deletions

View file

@ -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,
)

View file

@ -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():