fix(langfuse): honor ssl_verify=False and SSL_VERIFY on the v4 OTLP exporter

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
yucheng 2026-09-15 00:15:48 +00:00
parent 45fc3111dc
commit dddc1e0ad8
2 changed files with 45 additions and 7 deletions

View file

@ -431,22 +431,24 @@ def _build_verified_span_exporter(*, public_key: object, secret_key: object, bas
bundle and client certificate; v4 ships every observation through its own
OTLP exporter, so a private-CA deployment would fail TLS on every export in
a background thread while ``auth_check`` (still on the httpx client) stays
green. Only built when custom TLS material is configured; endpoint and
headers mirror ``langfuse._client.span_processor``.
green. Only built when TLS is configured away from the default (a CA bundle,
a client certificate, or verification switched off); endpoint and headers
mirror ``langfuse._client.span_processor``.
"""
import litellm
from litellm.llms.custom_httpx.http_handler import get_ssl_verify
ca_bundle: Final = litellm.ssl_verify if isinstance(litellm.ssl_verify, str) else None
ssl_verify: Final = get_ssl_verify()
configured_certificate: Final = os.getenv("SSL_CERTIFICATE") or litellm.ssl_certificate
client_certificate: Final = configured_certificate if isinstance(configured_certificate, str) else None
if ca_bundle is None and client_certificate is None:
if ssl_verify is True and client_certificate is None:
return None
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
export_path: Final = os.getenv("LANGFUSE_OTEL_TRACES_EXPORT_PATH")
endpoint: Final = f"{base_url}/{export_path}" if export_path else f"{base_url}/api/public/otel/v1/traces"
encoded_auth: Final = b64encode(f"{public_key}:{secret_key}".encode()).decode("ascii")
return OTLPSpanExporter(
exporter: Final = OTLPSpanExporter(
endpoint=endpoint,
headers={ # mutable-ok: the exporter copies these into its session headers
"Authorization": "Basic " + encoded_auth,
@ -454,9 +456,12 @@ def _build_verified_span_exporter(*, public_key: object, secret_key: object, bas
"x-langfuse-sdk-version": version("langfuse"),
"x-langfuse-public-key": str(public_key),
},
certificate_file=ca_bundle,
certificate_file=ssl_verify if isinstance(ssl_verify, str) else None,
client_certificate_file=client_certificate,
)
if ssl_verify is False:
exporter._certificate_file = False # pyright: ignore[reportPrivateUsage] # the ctor coerces a False certificate_file back to True
return exporter
def acquire_langfuse_client(

View file

@ -955,7 +955,8 @@ def test_ssl_exporter_is_only_built_with_custom_tls_material(monkeypatch, tmp_pa
import litellm
from litellm.integrations.langfuse.langfuse_sdk import _build_verified_span_exporter
monkeypatch.delenv("SSL_CERTIFICATE", raising=False)
for name in ("SSL_CERTIFICATE", "SSL_VERIFY", "SSL_CERT_FILE"):
monkeypatch.delenv(name, raising=False)
monkeypatch.setattr(litellm, "ssl_verify", True)
monkeypatch.setattr(litellm, "ssl_certificate", None)
assert (
@ -973,6 +974,38 @@ def test_ssl_exporter_is_only_built_with_custom_tls_material(monkeypatch, tmp_pa
assert exporter._headers["x-langfuse-sdk-version"] == installed_langfuse_version()
@pytest.mark.parametrize("switch", ["attribute", "env"])
def test_ssl_exporter_disables_verification_when_litellm_does(monkeypatch, switch):
"""v2 exported through the httpx client, so ``ssl_verify=False`` reached ingestion; v4's exporter must match."""
import litellm
from litellm.integrations.langfuse.langfuse_sdk import _build_verified_span_exporter
for name in ("SSL_CERTIFICATE", "SSL_VERIFY", "SSL_CERT_FILE"):
monkeypatch.delenv(name, raising=False)
monkeypatch.setattr(litellm, "ssl_certificate", None)
if switch == "attribute":
monkeypatch.setattr(litellm, "ssl_verify", False)
else:
monkeypatch.setattr(litellm, "ssl_verify", True)
monkeypatch.setenv("SSL_VERIFY", "False")
exporter = _build_verified_span_exporter(public_key="pk", secret_key="sk", base_url="https://lf.internal.example")
assert exporter is not None
assert exporter._certificate_file is False
assert exporter._client_cert is None
posted = []
def post(self, url, **kwargs):
posted.append((url, kwargs["verify"]))
raise ConnectionError("stop before the network")
monkeypatch.setattr("requests.Session.post", post)
with pytest.raises(ConnectionError):
exporter._export(b"payload")
assert posted[0] == ("https://lf.internal.example/api/public/otel/v1/traces", False)
def test_second_client_on_the_same_key_does_not_build_another_provider():
"""A discarded TracerProvider is pinned forever by its atexit hook."""
import gc