fix(langfuse): normalise the OTLP export path so a trailing host slash never yields a double slash

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
yucheng 2026-09-17 03:30:55 +00:00
parent 13d12ae4b8
commit edb9d6ef7f
2 changed files with 25 additions and 2 deletions

View file

@ -559,8 +559,8 @@ def _build_span_exporter(*, public_key: object, secret_key: object, base_url: ob
ca_bundle: Final = ssl_verify if isinstance(ssl_verify, str) and os.path.exists(ssl_verify) else None
configured_certificate: Final = os.getenv("SSL_CERTIFICATE") or litellm.ssl_certificate
client_certificate: Final = configured_certificate if isinstance(configured_certificate, str) else None
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"
export_path: Final = os.getenv("LANGFUSE_OTEL_TRACES_EXPORT_PATH") or "/api/public/otel/v1/traces"
endpoint: Final = f"{str(base_url).rstrip('/')}/{export_path.lstrip('/')}"
encoded_auth: Final = b64encode(f"{public_key}:{secret_key}".encode()).decode("ascii")
exporter: Final = OTLPSpanExporter(
endpoint=endpoint,

View file

@ -1078,6 +1078,29 @@ def test_ssl_exporter_carries_litellm_tls_material(monkeypatch, tmp_path):
assert exporter._headers["x-langfuse-sdk-version"] == installed_langfuse_version()
@pytest.mark.parametrize(
("base_url", "export_path", "expected"),
[
("https://lf.internal.example/", None, "https://lf.internal.example/api/public/otel/v1/traces"),
("https://lf.internal.example", "/otel/traces", "https://lf.internal.example/otel/traces"),
("https://lf.internal.example/", "/otel/traces", "https://lf.internal.example/otel/traces"),
("https://lf.internal.example", "otel/traces", "https://lf.internal.example/otel/traces"),
],
)
def test_export_endpoint_never_doubles_the_slash(monkeypatch, base_url, export_path, expected):
"""A trailing host slash or a leading export path slash must not produce `//` in the OTLP route."""
from litellm.integrations.langfuse.langfuse_sdk import _build_span_exporter
if export_path is None:
monkeypatch.delenv("LANGFUSE_OTEL_TRACES_EXPORT_PATH", raising=False)
else:
monkeypatch.setenv("LANGFUSE_OTEL_TRACES_EXPORT_PATH", export_path)
exporter = _build_span_exporter(public_key="pk", secret_key="sk", base_url=base_url).exporter
assert exporter._endpoint == expected
def test_retrying_exporter_retries_a_raised_export_and_then_succeeds(monkeypatch):
"""A read timeout used to drop the batch outright; v2 backed off and re-sent it."""
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult