From 9e3d9dc4665d440f0a6d31a1625f5303284f2d99 Mon Sep 17 00:00:00 2001 From: Yucheng He Date: Sat, 5 Sep 2026 07:40:38 -0700 Subject: [PATCH] fix(otel): preserve valid exporter beside gated preset --- litellm/litellm_core_utils/litellm_logging.py | 5 ++++- .../otel/test_otel_v2_destinations.py | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index d6f80e89c66..d9a78156f4b 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -4835,7 +4835,10 @@ def _maybe_construct_otel_v2(callback_name: str, _in_memory_loggers: list[Custom # If env vars are missing or the preset raises, defer to the legacy path # so customers get the same error story they had before V2 landed. return None - config: Final = _only_the_gated_exporter(built) if _is_credential_gated(built) else built + gated: Final = _is_credential_gated(built) + if _exports_nowhere(built) and not (serves_a_destination and has_v2_logger): + return None + config: Final = _only_the_gated_exporter(built) if gated and serves_a_destination and has_v2_logger else built if _exports_nowhere(config): verbose_logger.warning( "OTel V2: no operator credentials for '%s'; only key/team destinations will receive its traces", diff --git a/tests/test_litellm/integrations/otel/test_otel_v2_destinations.py b/tests/test_litellm/integrations/otel/test_otel_v2_destinations.py index 0a608ee1099..30e067df9f9 100644 --- a/tests/test_litellm/integrations/otel/test_otel_v2_destinations.py +++ b/tests/test_litellm/integrations/otel/test_otel_v2_destinations.py @@ -1157,6 +1157,23 @@ class TestPresetDegradation: assert logger is None + def test_a_valid_newrelic_base_exporter_survives_without_a_license_key(self, monkeypatch): + from litellm.litellm_core_utils.litellm_logging import _maybe_construct_otel_v2 + + monkeypatch.delenv("NEW_RELIC_LICENSE_KEY", raising=False) + monkeypatch.setenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://collector.local:4318") + monkeypatch.setenv("LITELLM_OTEL_V2", "true") + + is_otel_v2_enabled.cache_clear() + logger = in_fresh_context(_maybe_construct_otel_v2, "newrelic", []) + is_otel_v2_enabled.cache_clear() + + assert logger is not None + assert [spec.endpoint for spec in logger.config.exporters] == [ + "http://collector.local:4318", + "https://otlp.nr-data.net", + ] + def test_a_destination_for_one_backend_does_not_degrade_another(self, monkeypatch): from litellm.litellm_core_utils.litellm_logging import _maybe_construct_otel_v2