This commit is contained in:
Vedant Patel 2026-09-23 14:49:45 +00:00 • committed by GitHub
commit f75e1e5f4e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 60 additions and 16 deletions

View file

@ -205,6 +205,17 @@ class LangFuseLogger:
raise Exception(
f"\033[91mLangfuse not installed, try running 'pip install langfuse' to fix this error: {e}\n{traceback.format_exc()}\033[0m"
)
import warnings
warnings.warn(
"LangFuseLogger (the 'langfuse' callback) uses the Langfuse Python SDK "
"and is deprecated in favour of LangfuseOtelLogger (the 'langfuse_otel' "
"callback), which sends spans directly over OpenTelemetry and is compatible "
"with Langfuse v4 Cloud Fast Preview. "
"Set success_callback=['langfuse_otel'] to switch to the new integration.",
DeprecationWarning,
stacklevel=2,
)
self.public_key, self.secret_key, self.langfuse_host = resolve_langfuse_credentials(
langfuse_public_key=langfuse_public_key,
langfuse_secret=langfuse_secret,

View file

@ -401,7 +401,7 @@ also_copy = [
# Run the test suite once before mutation to gather line coverage, then skip
# mutating lines no test exercises. Those mutants would survive regardless
# (no test hits the line to kill them), so generating them wastes hours of CI.
# The score now reads as "mutation score over covered code" — pair with a
# The score now reads as "mutation score over covered code" — pair with a
# line-coverage number when reporting.
mutate_only_covered_lines = true
# Disable rerun/parallel plugins for mutation runs:

View file

@ -25,9 +25,7 @@ class TestServiceLoggerOTEL(unittest.IsolatedAsyncioTestCase):
@patch("litellm.integrations.opentelemetry.OpenTelemetry._init_tracing")
@patch("litellm.integrations.opentelemetry.OpenTelemetry._init_metrics")
@patch("litellm.integrations.opentelemetry.OpenTelemetry._init_logs")
async def test_langfuse_otel_ignores_service_logs(
self, mock_logs, mock_metrics, mock_tracing
):
async def test_langfuse_otel_ignores_service_logs(self, mock_logs, mock_metrics, mock_tracing):
"""
Test that LangfuseOtelLogger overrides the service logging hooks with 'pass'.
"""
@ -46,9 +44,7 @@ class TestServiceLoggerOTEL(unittest.IsolatedAsyncioTestCase):
@patch("litellm.integrations.opentelemetry.OpenTelemetry._init_tracing")
@patch("litellm.integrations.opentelemetry.OpenTelemetry._init_metrics")
@patch("litellm.integrations.opentelemetry.OpenTelemetry._init_logs")
async def test_langfuse_otel_does_not_create_proxy_request_span(
self, mock_logs, mock_metrics, mock_tracing
):
async def test_langfuse_otel_does_not_create_proxy_request_span(self, mock_logs, mock_metrics, mock_tracing):
"""
Test that LangfuseOtelLogger returns None for create_litellm_proxy_request_started_span.
@ -73,9 +69,7 @@ class TestServiceLoggerOTEL(unittest.IsolatedAsyncioTestCase):
@patch("litellm.integrations.opentelemetry.OpenTelemetry._init_tracing")
@patch("litellm.integrations.opentelemetry.OpenTelemetry._init_metrics")
@patch("litellm.integrations.opentelemetry.OpenTelemetry._init_logs")
async def test_service_logging_shadowing_fix(
self, mock_logs, mock_metrics, mock_tracing
):
async def test_service_logging_shadowing_fix(self, mock_logs, mock_metrics, mock_tracing):
"""
Test the architectural fix: multiple OTEL loggers should receive logs independently.
"""
@ -89,9 +83,7 @@ class TestServiceLoggerOTEL(unittest.IsolatedAsyncioTestCase):
service_logging = ServiceLogging()
# 3. Mock the base OpenTelemetry hook
with patch.object(
OpenTelemetry, "async_service_success_hook", new_callable=AsyncMock
) as mock_base_hook:
with patch.object(OpenTelemetry, "async_service_success_hook", new_callable=AsyncMock) as mock_base_hook:
# Trigger a service event
await service_logging.async_service_success_hook(
service=ServiceTypes.DB,
@ -112,9 +104,7 @@ class TestServiceLoggerOTEL(unittest.IsolatedAsyncioTestCase):
@patch("litellm.integrations.opentelemetry.OpenTelemetry._init_tracing")
@patch("litellm.integrations.opentelemetry.OpenTelemetry._init_metrics")
@patch("litellm.integrations.opentelemetry.OpenTelemetry._init_logs")
async def test_langfuse_otel_env_config_includes_v4_ingestion_header(
self, mock_logs, mock_metrics, mock_tracing
):
async def test_langfuse_otel_env_config_includes_v4_ingestion_header(self, mock_logs, mock_metrics, mock_tracing):
logger = LangfuseOtelLogger()
headers = OpenTelemetry._get_headers_dictionary(logger.config.headers)
@ -147,6 +137,49 @@ class TestServiceLoggerOTEL(unittest.IsolatedAsyncioTestCase):
)
self.assertTrue(headers["Authorization"].startswith("Basic "))
def test_langfuse_legacy_callback_emits_deprecation_warning(self):
"""
Test that instantiating LangFuseLogger emits a DeprecationWarning
directing users to switch to the langfuse_otel callback.
"""
import warnings
from unittest.mock import MagicMock, patch
mock_langfuse_module = MagicMock()
mock_langfuse_module.Langfuse = MagicMock()
with patch.dict(
"sys.modules",
{
"langfuse": mock_langfuse_module,
"langfuse.client": MagicMock(),
},
):
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
from litellm.integrations.langfuse.langfuse import LangFuseLogger
with patch( # test-quality-ok: mocking litellm credential resolver so test runs without real API keys
"litellm.integrations.langfuse.langfuse.resolve_langfuse_credentials",
return_value=("pk", "sk", "https://cloud.langfuse.com"),
):
LangFuseLogger(
langfuse_public_key="pk",
langfuse_secret="sk",
langfuse_host="https://cloud.langfuse.com",
allow_env_credentials=False,
)
deprecation_warnings = [w for w in caught if issubclass(w.category, DeprecationWarning)]
self.assertTrue(
len(deprecation_warnings) >= 1,
"Expected at least one DeprecationWarning to be emitted",
)
self.assertIn(
"langfuse_otel",
str(deprecation_warnings[0].message),
)
if __name__ == "__main__":
unittest.main()