fix(otel): auto-infer otlp_http exporter when endpoint is configured (#20438)

When OpenTelemetry is configured via the UI, only OTEL_ENDPOINT and
OTEL_HEADERS are set, but OTEL_EXPORTER is not specified. This caused
the exporter to default to "console", meaning traces were printed to
stdout instead of being sent to the configured endpoint.

This fix adds logic in OpenTelemetryConfig.__post_init__ to automatically
infer "otlp_http" as the exporter when an endpoint is specified but the
exporter is still the default "console".

Fixes issue reported by Elastic team where traces weren't being sent
to their OTEL endpoint when configured through the LiteLLM UI.
This commit is contained in:
michelligabriele
2026-02-10 09:33:16 -08:00
committed by GitHub
parent 0f01802dde
commit 1afe3032fd
2 changed files with 27 additions and 0 deletions
+7
View File
@@ -72,6 +72,13 @@ class OpenTelemetryConfig:
model_id: Optional[str] = None
def __post_init__(self) -> None:
# If endpoint is specified but exporter is still the default "console",
# automatically infer "otlp_http" to send traces to the endpoint.
# This fixes an issue where UI-configured OTEL settings would default
# to console output instead of sending traces to the configured endpoint.
if self.endpoint and isinstance(self.exporter, str) and self.exporter == "console":
self.exporter = "otlp_http"
if not self.service_name:
self.service_name = os.getenv("OTEL_SERVICE_NAME", "litellm")
if not self.deployment_environment:
@@ -274,6 +274,26 @@ class TestOpenTelemetry(unittest.TestCase):
self.assertEqual(config.deployment_environment, "production")
self.assertEqual(config.model_id, "custom-service")
@patch.dict(os.environ, {}, clear=True)
def test_open_telemetry_config_auto_infer_otlp_http_when_endpoint_set(self):
"""When endpoint is set but exporter is default 'console', auto-infer 'otlp_http'.
This fixes an issue where UI-configured OTEL settings would default to console
output instead of sending traces to the configured endpoint.
See: https://github.com/BerriAI/litellm/issues/XXXX
"""
# When endpoint is specified without explicit exporter, should auto-infer otlp_http
config = OpenTelemetryConfig(endpoint="https://otel-collector.example.com:443")
self.assertEqual(config.exporter, "otlp_http")
# When exporter is explicitly set to something other than console, should not override
config_grpc = OpenTelemetryConfig(exporter="grpc", endpoint="https://otel-collector.example.com:443")
self.assertEqual(config_grpc.exporter, "grpc")
# When no endpoint is set, should keep console as default
config_no_endpoint = OpenTelemetryConfig()
self.assertEqual(config_no_endpoint.exporter, "console")
def wait_for_spans(self, exporter: InMemorySpanExporter, prefix: str):
"""Poll until we see at least one span with an attribute key starting with `prefix`."""
deadline = time.time() + self.POLL_TIMEOUT