Skip to main content
  1. Blog/

When the sampler hides the span you need

·378 words·2 mins
Mohammed Abdessetar Elyagoubi
Author
Mohammed Abdessetar Elyagoubi
Java and OpenTelemetry contributor based in Rabat. I write about traces you can trust, browser tooling, and public open-source work.
Table of Contents

A blank trace is not always a missing spanBuilder. In OpenTelemetry Java, the sampler decides whether a root span is recorded. Child spans usually inherit that decision. If the root is dropped, the waterfall you expected never exists — and the auto-instrumentation agent did its job.

This is the short version I use before I start rewriting instrumentation.

Parent-based is the default mental model
#

Most production Java setups use a parent-based sampler in front of a ratio or a remote policy (Jaeger remote, for example). Incoming requests that already carry a sampled trace context stay sampled. New roots are decided locally.

That is useful. It is also how a service can look “uninstrumented” in the UI when only unsampled roots arrive, or when a remote sampler is still on its default poll interval.

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.sdk.trace.samplers.ParentBasedSampler;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.sdk.trace.samplers.SamplingResult;

public final class SamplingNotes {
  // Illustrative only: wire this through the SDK or declarative config,
  // not as a one-off in business code.
  static Sampler parentThenRatio(double ratio) {
    return Sampler.parentBasedBuilder(Sampler.traceIdRatioBased(ratio))
        .build();
  }

  static boolean isRecorded(SamplingResult result) {
    return result.getDecision().isSampled();
  }

  static Attributes decisionAttributes(SamplingResult result) {
    return result.getAttributes();
  }
}

If you start a custom span without making the parent current, you can also get a new root that the ratio sampler drops independently. That looks like a sampling bug. It is usually a missing Scope. The earlier post on custom spans covers that path.

Check the interval you think you configured
#

Remote samplers poll. If declarative config exposes interval but the provider reads a different key, you stay on the default cadence and keep exporting yesterday’s strategy. That class of bug is easy to miss because the process still starts and still exports some traces.

When a ratio or remote sampler looks inert:

  1. Confirm the root decision in the collector or backend (sampling.decision, or the equivalent field your exporter writes).
  2. Confirm the config key the SDK actually reads — especially for Jaeger remote / declarative maps.
  3. Do not raise the sample rate to “debug a missing span” until you know whether the span was never started or was never recorded.

Treat sampler settings as production config: no secrets in the values you log when parsing fails.

Related OpenTelemetry Java work is listed on Projects/Talks.