Skip to content

fix: restore OTLP HTTP telemetry broken by opentelemetry-otlp 0.31 feature conflict#304

Merged
rubberduck203 merged 3 commits intomainfrom
fix/otel-http-client-feature-conflict
Apr 17, 2026
Merged

fix: restore OTLP HTTP telemetry broken by opentelemetry-otlp 0.31 feature conflict#304
rubberduck203 merged 3 commits intomainfrom
fix/otel-http-client-feature-conflict

Conversation

@rubberduck203
Copy link
Copy Markdown
Contributor

@rubberduck203 rubberduck203 commented Apr 17, 2026

Problem

After the opentelemetry-otlp 0.27 → 0.31 upgrade (#251), any deployment using SCOPE_OTEL_PROTOCOL=http silently stopped sending traces and metrics. The runtime error:

opentelemetry configuration failed. Events will not be sent. no http client specified

The CLI continues running normally with telemetry disabled, so the failure is invisible to users.

Root cause

opentelemetry-otlp 0.31 changed how the default HTTP client is selected (source). A client is only auto-installed when exactly one of hyper-client, reqwest-client, or reqwest-blocking-client is enabled. The async-reqwest auto-default has this cfg gate:

all(not(feature = "hyper-client"), not(feature = "reqwest-blocking-client"), feature = "reqwest-client")

Our Cargo.toml explicitly enabled reqwest-client but did not set default-features = false. The crate's own defaults include reqwest-blocking-client (Cargo.toml:69). With both features active, the cfg never matched, http_config.client stayed None, and .build() returned ExporterBuildError::NoHttpClient.

This was silent in 0.27 because HttpConfig::default() always provided a blocking client unconditionally regardless of feature flags.

Fix

  • Set default-features = false on opentelemetry-otlp
  • Switch from reqwest-client (async) to reqwest-blocking-client (blocking) — correct for this use case since the OTLP BatchProcessor runs in a plain std::thread, not a tokio task. The async client caused a secondary panic ("there is no reactor running") when the batch thread tried to flush on shutdown.
  • Re-add trace and internal-logs to the explicit feature list (previously supplied by the now-disabled defaults)

Testing

  • Confirmed the error reproduces on main before this fix
  • After fix: no error, traces confirmed arriving in Jaeger via OTLP HTTP
  • Two regression tests added that assert SpanExporter and MetricExporter HTTP builders return Ok — both fail on pre-fix code with NoHttpClient and pass after

Checklist

  • cargo test passes
  • End-to-end verified: traces visible in Jaeger
  • Regression tests added

…ature conflict

## Problem

After upgrading opentelemetry-otlp from 0.27 to 0.31 (PR #251), any
user with SCOPE_OTEL_PROTOCOL=http would see:

  opentelemetry configuration failed. Events will not be sent.
  no http client specified

Traces and metrics stopped arriving in the collector entirely. The
binary continued running normally, so the failure was silent to users.

## Root cause

opentelemetry-otlp 0.31 changed how the default HTTP client is selected
(https://github.com/open-telemetry/opentelemetry-rust/blob/opentelemetry-otlp-0.31.1/opentelemetry-otlp/src/exporter/http/mod.rs#L151-L195).

A client is only auto-installed when *exactly one* of hyper-client,
reqwest-client, or reqwest-blocking-client is enabled. The async-reqwest
auto-default has this cfg gate:

  all(
    not(feature = "hyper-client"),
    not(feature = "reqwest-blocking-client"),
    feature = "reqwest-client"
  )

Our Cargo.toml explicitly enabled reqwest-client, but did not set
default-features = false. The crate's own default feature set includes
reqwest-blocking-client (opentelemetry-otlp Cargo.toml line 69:
https://github.com/open-telemetry/opentelemetry-rust/blob/opentelemetry-otlp-0.31.1/opentelemetry-otlp/Cargo.toml).

With both reqwest-client AND reqwest-blocking-client active, the cfg
never matched, http_config.client remained None, and .build() returned
ExporterBuildError::NoHttpClient at:
https://github.com/open-telemetry/opentelemetry-rust/blob/opentelemetry-otlp-0.31.1/opentelemetry-otlp/src/exporter/http/mod.rs#L195

This was not a problem on 0.27 because HttpConfig::default() always
provided a blocking client unconditionally, regardless of feature flags.

## Fix

Set default-features = false on opentelemetry-otlp so reqwest-blocking-client
is no longer pulled in from crate defaults. Switch the explicit HTTP
client feature from reqwest-client (async) to reqwest-blocking-client
(blocking). The blocking client is the correct choice here: the OTLP
BatchProcessor runs in a plain std::thread, not a tokio task. Using the
async reqwest-client caused a secondary panic ("there is no reactor
running") when the batch processor thread tried to flush on shutdown.

Also re-add trace and internal-logs to the explicit feature list, since
these were previously supplied by the now-disabled defaults.

## Verification

- Confirmed error reproduces on main: `cargo run -- doctor list` with
  SCOPE_OTEL_PROTOCOL=http printed the NoHttpClient message.
- After fix: no error, and traces appear in Jaeger
  (OTLP HTTP via local gdev Jaeger service on port 14318).
- Two regression tests added to src/shared/logging.rs that call
  SpanExporter::builder().with_http()...build() and
  MetricExporter::builder().with_http()...build() and assert Ok.
  Both tests fail on the pre-fix code and pass after.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@rubberduck203 rubberduck203 marked this pull request as draft April 17, 2026 10:50
@rubberduck203 rubberduck203 marked this pull request as ready for review April 17, 2026 11:10
@rubberduck203 rubberduck203 merged commit 18878a3 into main Apr 17, 2026
6 checks passed
@rubberduck203 rubberduck203 deleted the fix/otel-http-client-feature-conflict branch April 17, 2026 17:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants