Skip to content

Python: Fix OTLP HTTP base-endpoint losing /v1/{signal} auto-append - #5913

Merged
eavanvalkenburg merged 2 commits into
microsoft:mainfrom
droideronline:fix/otlp-http-endpoint-base-url-auto-append
Jun 2, 2026
Merged

Python: Fix OTLP HTTP base-endpoint losing /v1/{signal} auto-append#5913
eavanvalkenburg merged 2 commits into
microsoft:mainfrom
droideronline:fix/otlp-http-endpoint-base-url-auto-append

Conversation

@droideronline

Copy link
Copy Markdown
Contributor

Summary

Fixes #5912

Per the OpenTelemetry specification, OTEL_EXPORTER_OTLP_ENDPOINT is a base URL for HTTP transport — the SDK automatically appends /v1/traces, /v1/metrics, and /v1/logs when it reads the env var directly. Signal-specific env vars (e.g. OTEL_EXPORTER_OTLP_TRACES_ENDPOINT) are full URLs used verbatim.

_get_exporters_from_env in observability.py was reading the base endpoint and passing it directly as the endpoint= constructor argument to the exporters. The OTel SDK always treats a programmatically-provided endpoint= as a full signal URL (no auto-append). This silently dropped the /v1/{signal} path suffix, causing all HTTP telemetry to be sent to the wrong URL (e.g. http://localhost:4318 instead of http://localhost:4318/v1/traces).

Changes

python/packages/core/agent_framework/observability.py

  • Read signal-specific endpoint env vars separately from the base endpoint.
  • When using HTTP protocol (http/protobuf or http) and falling back to OTEL_EXPORTER_OTLP_ENDPOINT, auto-append /v1/traces, /v1/metrics, /v1/logs to replicate the OTel spec behaviour.
  • Signal-specific env vars continue to be forwarded verbatim (no path appended).
  • gRPC behaviour is unchanged — the base endpoint is used as-is.

python/packages/core/tests/core/test_observability.py

  • Added test cases covering:
    • HTTP protocol with base endpoint → signal paths are auto-appended.
    • HTTP protocol with signal-specific endpoints → used verbatim (no double-append).
    • gRPC protocol with base endpoint → base URL used as-is (no path appended).

Before / After

Scenario Before After
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318, PROTOCOL=http/protobuf Sends to http://localhost:4318 Sends to http://localhost:4318/v1/traces
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/v1/traces, PROTOCOL=http/protobuf Sends to http://localhost:4318/v1/traces Sends to http://localhost:4318/v1/traces
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317, PROTOCOL=grpc Sends to http://localhost:4317 Sends to http://localhost:4317

Testing

Unit tests added in test_observability.py covering all three scenarios above. No integration-level changes required.

Copilot AI review requested due to automatic review settings May 18, 2026 08:33
@moonbox3 moonbox3 added the python Usage: [Issues, PRs], Target: Python label May 18, 2026
@droideronline

Copy link
Copy Markdown
Contributor Author

@TaoChenOSU , @moonbox3 , @eavanvalkenburg , @chetantoshniwal - Kindly review when you get a chance.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes OTLP/HTTP exporter endpoint handling in Python observability so that OTEL_EXPORTER_OTLP_ENDPOINT (base URL) correctly results in signal-specific URLs (/v1/traces, /v1/metrics, /v1/logs) when configuring exporters programmatically, matching the OpenTelemetry spec and avoiding telemetry being sent to the wrong path.

Changes:

  • Compute HTTP signal endpoints by auto-appending /v1/{signal} when falling back to OTEL_EXPORTER_OTLP_ENDPOINT; keep signal-specific endpoint env vars verbatim.
  • Preserve existing gRPC behavior (base endpoint used as-is).
  • Add unit tests validating HTTP base endpoint appends, trailing-slash handling, verbatim signal-specific endpoints, and unchanged gRPC behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
python/packages/core/agent_framework/observability.py Implements correct OTLP/HTTP base-endpoint → signal endpoint derivation while leaving gRPC behavior unchanged.
python/packages/core/tests/core/test_observability.py Adds focused tests to verify endpoint computation logic across HTTP and gRPC scenarios.

@moonbox3

moonbox3 commented May 22, 2026

Copy link
Copy Markdown
Contributor

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/core/agent_framework
   observability.py8346092%382, 384–385, 388, 391, 394–395, 400–401, 407–408, 414–415, 422, 424–426, 429–431, 436–437, 443–444, 450–451, 458, 635–636, 835, 839–841, 843, 847–848, 852, 890, 892, 903–905, 907–909, 913, 921, 1045–1046, 1281, 1541–1542, 1770, 1811–1812, 1955, 2091, 2288, 2506, 2508
TOTAL37404435088% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
7455 34 💤 0 ❌ 0 🔥 2m 1s ⏱️

@moonbox3 moonbox3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking, two gaps I noticed while reading that this PR doesn't introduce:

  1. OTEL_EXPORTER_OTLP_PROTOCOL=http/json is a valid spec value but _create_otlp_exporters only matches ("http/protobuf", "http") (observability.py:418), so http/json is silently ignored. The new auto-append check inherits the same set, which is consistent, just worth knowing.

  2. Per-signal protocol env vars (OTEL_EXPORTER_OTLP_TRACES_PROTOCOL, etc.) aren't read in _get_exporters_from_env. A user pinning base to grpc and traces to http/protobuf wouldn't get auto-append on traces. Pre-existing.

Happy to leave both for a follow-up if you'd rather keep this PR scoped to the base-endpoint fix. Fine to merge as-is from my side.

@moonbox3

moonbox3 commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

@droideronline please have a look at the failing CI/CD checks. Thanks.

@droideronline
droideronline force-pushed the fix/otlp-http-endpoint-base-url-auto-append branch from e3aeabb to 3bae52d Compare June 1, 2026 08:28
Per the OTel spec, OTEL_EXPORTER_OTLP_ENDPOINT is a *base* URL for HTTP —
the SDK auto-appends /v1/traces, /v1/metrics, /v1/logs when it reads the
env var directly. Signal-specific endpoint env vars are *full* URLs used
verbatim.

_get_exporters_from_env read the base endpoint and forwarded it as the
constructor ``endpoint=`` argument, which the SDK always treats as a full
signal URL. As a result, with OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
and HTTP protocol, the exporter sent to http://localhost:4318 instead of
http://localhost:4318/v1/traces (and likewise for metrics/logs).

Replicate the spec's auto-append here when falling back to the base
endpoint under HTTP. gRPC behavior is unchanged.
Pre-declare traces_endpoint, metrics_endpoint, logs_endpoint as
str | None before the if/else block. Mypy inferred str from the
if-branch f-string assignments and then rejected the str | None
expressions in the else-branch as incompatible.
@droideronline
droideronline force-pushed the fix/otlp-http-endpoint-base-url-auto-append branch from 3bae52d to ace1059 Compare June 1, 2026 08:48
@droideronline

Copy link
Copy Markdown
Contributor Author

@moonbox3 - Thanks!, mypy was failing. Fixed it. Please have a look when you get a moment.

@moonbox3
moonbox3 requested a review from eavanvalkenburg June 2, 2026 09:22
@eavanvalkenburg
eavanvalkenburg added this pull request to the merge queue Jun 2, 2026
Merged via the queue into microsoft:main with commit a5f355e Jun 2, 2026
36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: OTLP HTTP exporter sends to bare base endpoint instead of /v1/{signal} path

4 participants