From 90682100e0e45cf451494863479291ee5ba4cfd3 Mon Sep 17 00:00:00 2001 From: ephraimduncan Date: Wed, 29 Jul 2026 13:03:34 +0000 Subject: [PATCH 1/2] feat(integrations): add telemetry.dev logging integration --- litellm/__init__.py | 1 + .../integrations/telemetry_dev/__init__.py | 3 ++ .../telemetry_dev/telemetry_dev.py | 47 +++++++++++++++++++ .../custom_logger_registry.py | 1 + litellm/litellm_core_utils/litellm_logging.py | 25 ++++++++++ .../test_unit_tests_init_callbacks.py | 1 + 6 files changed, 78 insertions(+) create mode 100644 litellm/integrations/telemetry_dev/__init__.py create mode 100644 litellm/integrations/telemetry_dev/telemetry_dev.py diff --git a/litellm/__init__.py b/litellm/__init__.py index 3f8c742c5a2..8c0f212c5c2 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -128,6 +128,7 @@ _custom_logger_compatible_callbacks_literal = Literal[ "arize", "arize_phoenix", "langtrace", + "telemetry_dev", "gcs_bucket", "azure_storage", "opik", diff --git a/litellm/integrations/telemetry_dev/__init__.py b/litellm/integrations/telemetry_dev/__init__.py new file mode 100644 index 00000000000..60bbd536b5f --- /dev/null +++ b/litellm/integrations/telemetry_dev/__init__.py @@ -0,0 +1,3 @@ +from litellm.integrations.telemetry_dev.telemetry_dev import TelemetryDevLogger + +__all__ = ["TelemetryDevLogger"] diff --git a/litellm/integrations/telemetry_dev/telemetry_dev.py b/litellm/integrations/telemetry_dev/telemetry_dev.py new file mode 100644 index 00000000000..ed92657ae28 --- /dev/null +++ b/litellm/integrations/telemetry_dev/telemetry_dev.py @@ -0,0 +1,47 @@ +import os +from typing import TYPE_CHECKING, Any + +from litellm.integrations.opentelemetry import OpenTelemetry + +if TYPE_CHECKING: + from litellm.integrations.opentelemetry import OpenTelemetryConfig + from litellm.types.integrations.arize import Protocol +else: + Protocol = Any + OpenTelemetryConfig = Any + + +class TelemetryDevConfig: + """Configuration for the telemetry.dev OTLP integration.""" + + def __init__( + self, + otlp_auth_headers: str, + protocol: Protocol, + endpoint: str, + ): + self.otlp_auth_headers = otlp_auth_headers + self.protocol = protocol + self.endpoint = endpoint + + +class TelemetryDevLogger(OpenTelemetry): + """telemetry.dev logger that extends OpenTelemetry for OTLP export.""" + + @staticmethod + def get_telemetry_dev_config() -> TelemetryDevConfig: + api_key = os.environ.get("TELEMETRY_DEV_API_KEY") + if not api_key: + raise ValueError( + "TELEMETRY_DEV_API_KEY environment variable is required for the telemetry.dev integration." + ) + + base_url = os.environ.get( + "TELEMETRY_DEV_BASE_URL", "https://ingest.telemetry.dev" + ).rstrip("/") + + return TelemetryDevConfig( + otlp_auth_headers=f"Authorization=Bearer {api_key}", + protocol="otlp_http", + endpoint=f"{base_url}/v1/traces", + ) diff --git a/litellm/litellm_core_utils/custom_logger_registry.py b/litellm/litellm_core_utils/custom_logger_registry.py index a7fae104c92..67593ea9dbe 100644 --- a/litellm/litellm_core_utils/custom_logger_registry.py +++ b/litellm/litellm_core_utils/custom_logger_registry.py @@ -85,6 +85,7 @@ class CustomLoggerRegistry: "langfuse_otel": OpenTelemetry, "arize_phoenix": OpenTelemetry, "langtrace": OpenTelemetry, + "telemetry_dev": OpenTelemetry, "weave_otel": OpenTelemetry, "levo": OpenTelemetry, "mlflow": MlflowLogger, diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 83d6fcc0bee..39f7b44f759 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -3825,6 +3825,31 @@ def _init_custom_logger_compatible_class( _levo_otel_logger = LevoLogger(config=otel_config, callback_name="levo") _in_memory_loggers.append(_levo_otel_logger) return _levo_otel_logger # type: ignore + elif logging_integration == "telemetry_dev": + from litellm.integrations.opentelemetry import OpenTelemetryConfig + from litellm.integrations.telemetry_dev.telemetry_dev import ( + TelemetryDevLogger, + ) + + telemetry_dev_config = TelemetryDevLogger.get_telemetry_dev_config() + otel_config = OpenTelemetryConfig( + exporter=telemetry_dev_config.protocol, + endpoint=telemetry_dev_config.endpoint, + headers=telemetry_dev_config.otlp_auth_headers, + ) + + for callback in _in_memory_loggers: + if ( + isinstance(callback, TelemetryDevLogger) + and callback.callback_name == "telemetry_dev" + ): + return callback # type: ignore + + telemetry_dev_logger = TelemetryDevLogger( + config=otel_config, callback_name="telemetry_dev" + ) + _in_memory_loggers.append(telemetry_dev_logger) + return telemetry_dev_logger # type: ignore elif logging_integration == "otel": # Gate the new typed V2 adapter behind LITELLM_OTEL_V2. When off, # the legacy 3,227-line god-class is used unchanged. The two are diff --git a/tests/logging_callback_tests/test_unit_tests_init_callbacks.py b/tests/logging_callback_tests/test_unit_tests_init_callbacks.py index b2243eed049..ee0f57efb86 100644 --- a/tests/logging_callback_tests/test_unit_tests_init_callbacks.py +++ b/tests/logging_callback_tests/test_unit_tests_init_callbacks.py @@ -42,6 +42,7 @@ expected_env_vars = { "GOOGLE_APPLICATION_CREDENTIALS": "gcs_credentials", "OPIK_API_KEY": "opik_api_key", "LANGTRACE_API_KEY": "langtrace_api_key", + "TELEMETRY_DEV_API_KEY": "td_live_test", "LOGFIRE_TOKEN": "logfire_token", "ARIZE_SPACE_KEY": "arize_space_key", "ARIZE_API_KEY": "arize_api_key", From 9acc14da610a5329d312fcdf1b5e39d246450096 Mon Sep 17 00:00:00 2001 From: ephraimduncan Date: Wed, 19 Aug 2026 11:56:59 +0000 Subject: [PATCH 2/2] fix(integrations): correct telemetry_dev callback lookup and lint errors - Map telemetry_dev to TelemetryDevLogger in the callback registry. - Add a telemetry_dev branch to get_custom_logger_compatible_class. - Apply ruff format to telemetry_dev.py and litellm_logging.py. - Add a test for the init, config, and cache of the telemetry_dev callback. --- .../integrations/telemetry_dev/__init__.py | 2 +- .../telemetry_dev/telemetry_dev.py | 15 ++------- .../custom_logger_registry.py | 3 +- litellm/litellm_core_utils/litellm_logging.py | 18 ++++++---- .../test_unit_tests_init_callbacks.py | 33 +++++++++++++++++++ 5 files changed, 50 insertions(+), 21 deletions(-) diff --git a/litellm/integrations/telemetry_dev/__init__.py b/litellm/integrations/telemetry_dev/__init__.py index 60bbd536b5f..71d27a8fc4a 100644 --- a/litellm/integrations/telemetry_dev/__init__.py +++ b/litellm/integrations/telemetry_dev/__init__.py @@ -1,3 +1,3 @@ from litellm.integrations.telemetry_dev.telemetry_dev import TelemetryDevLogger -__all__ = ["TelemetryDevLogger"] +__all__ = ["TelemetryDevLogger"] # mutable-ok: module export list diff --git a/litellm/integrations/telemetry_dev/telemetry_dev.py b/litellm/integrations/telemetry_dev/telemetry_dev.py index ed92657ae28..b3475dbf809 100644 --- a/litellm/integrations/telemetry_dev/telemetry_dev.py +++ b/litellm/integrations/telemetry_dev/telemetry_dev.py @@ -1,14 +1,7 @@ import os -from typing import TYPE_CHECKING, Any from litellm.integrations.opentelemetry import OpenTelemetry - -if TYPE_CHECKING: - from litellm.integrations.opentelemetry import OpenTelemetryConfig - from litellm.types.integrations.arize import Protocol -else: - Protocol = Any - OpenTelemetryConfig = Any +from litellm.types.integrations.arize import Protocol class TelemetryDevConfig: @@ -19,7 +12,7 @@ class TelemetryDevConfig: otlp_auth_headers: str, protocol: Protocol, endpoint: str, - ): + ) -> None: self.otlp_auth_headers = otlp_auth_headers self.protocol = protocol self.endpoint = endpoint @@ -36,9 +29,7 @@ class TelemetryDevLogger(OpenTelemetry): "TELEMETRY_DEV_API_KEY environment variable is required for the telemetry.dev integration." ) - base_url = os.environ.get( - "TELEMETRY_DEV_BASE_URL", "https://ingest.telemetry.dev" - ).rstrip("/") + base_url = os.environ.get("TELEMETRY_DEV_BASE_URL", "https://ingest.telemetry.dev").rstrip("/") return TelemetryDevConfig( otlp_auth_headers=f"Authorization=Bearer {api_key}", diff --git a/litellm/litellm_core_utils/custom_logger_registry.py b/litellm/litellm_core_utils/custom_logger_registry.py index ae207ee53cc..6c2620db2bd 100644 --- a/litellm/litellm_core_utils/custom_logger_registry.py +++ b/litellm/litellm_core_utils/custom_logger_registry.py @@ -47,6 +47,7 @@ from litellm.integrations.posthog import PostHogLogger from litellm.integrations.prometheus import PrometheusLogger from litellm.integrations.s3_v2 import S3Logger from litellm.integrations.sqs import SQSLogger +from litellm.integrations.telemetry_dev.telemetry_dev import TelemetryDevLogger from litellm.integrations.vantage.vantage_logger import VantageLogger from litellm.integrations.vector_store_integrations.vector_store_pre_call_hook import ( VectorStorePreCallHook, @@ -85,7 +86,7 @@ class CustomLoggerRegistry: "langfuse_otel": OpenTelemetry, "arize_phoenix": OpenTelemetry, "langtrace": OpenTelemetry, - "telemetry_dev": OpenTelemetry, + "telemetry_dev": TelemetryDevLogger, "weave_otel": OpenTelemetry, "levo": OpenTelemetry, "mlflow": MlflowLogger, diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index 2ecece0b262..a9a1e127076 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -4119,15 +4119,10 @@ def _init_custom_logger_compatible_class( ) for callback in _in_memory_loggers: - if ( - isinstance(callback, TelemetryDevLogger) - and callback.callback_name == "telemetry_dev" - ): + if isinstance(callback, TelemetryDevLogger) and callback.callback_name == "telemetry_dev": return callback - telemetry_dev_logger = TelemetryDevLogger( - config=otel_config, callback_name="telemetry_dev" - ) + telemetry_dev_logger = TelemetryDevLogger(config=otel_config, callback_name="telemetry_dev") _in_memory_loggers.append(telemetry_dev_logger) return telemetry_dev_logger elif logging_integration == "otel": @@ -4713,6 +4708,15 @@ def get_custom_logger_compatible_class( if isinstance(callback, OpenTelemetry) and callback.callback_name == "langtrace": return callback + elif logging_integration == "telemetry_dev": + from litellm.integrations.telemetry_dev.telemetry_dev import ( + TelemetryDevLogger, + ) + + for callback in _in_memory_loggers: + if isinstance(callback, TelemetryDevLogger) and callback.callback_name == "telemetry_dev": + return callback + elif logging_integration == "mlflow": for callback in _in_memory_loggers: if isinstance(callback, MlflowLogger): diff --git a/tests/logging_callback_tests/test_unit_tests_init_callbacks.py b/tests/logging_callback_tests/test_unit_tests_init_callbacks.py index ee0f57efb86..da39c161485 100644 --- a/tests/logging_callback_tests/test_unit_tests_init_callbacks.py +++ b/tests/logging_callback_tests/test_unit_tests_init_callbacks.py @@ -319,3 +319,36 @@ def test_get_combined_callback_list_returns_copy_when_dynamic_is_none(): combined_callbacks.append("new_callback") assert global_callbacks == ["langfuse"] + + +def test_telemetry_dev_callback_initialization(monkeypatch): + from litellm.integrations.telemetry_dev.telemetry_dev import TelemetryDevLogger + from litellm.litellm_core_utils.litellm_logging import ( + _init_custom_logger_compatible_class, + get_custom_logger_compatible_class, + ) + + monkeypatch.setenv("TELEMETRY_DEV_API_KEY", "td_live_test") + monkeypatch.delenv("TELEMETRY_DEV_BASE_URL", raising=False) + + config = TelemetryDevLogger.get_telemetry_dev_config() + assert config.endpoint == "https://ingest.telemetry.dev/v1/traces" + assert config.otlp_auth_headers == "Authorization=Bearer td_live_test" + assert config.protocol == "otlp_http" + + monkeypatch.setenv("TELEMETRY_DEV_BASE_URL", "https://example.com/") + assert TelemetryDevLogger.get_telemetry_dev_config().endpoint == "https://example.com/v1/traces" + monkeypatch.delenv("TELEMETRY_DEV_BASE_URL") + + logger = _init_custom_logger_compatible_class("telemetry_dev", internal_usage_cache=None, llm_router=None) + assert isinstance(logger, TelemetryDevLogger) + assert logger.callback_name == "telemetry_dev" + assert CustomLoggerRegistry.CALLBACK_CLASS_STR_TO_CLASS_TYPE["telemetry_dev"] is TelemetryDevLogger + + # init is cached and lookup resolves to the same instance + assert _init_custom_logger_compatible_class("telemetry_dev", internal_usage_cache=None, llm_router=None) is logger + assert get_custom_logger_compatible_class("telemetry_dev") is logger + + monkeypatch.delenv("TELEMETRY_DEV_API_KEY") + with pytest.raises(ValueError, match="TELEMETRY_DEV_API_KEY"): + TelemetryDevLogger.get_telemetry_dev_config()