Skip to content

[Bug]: BatchSpanProcessor exports spans that are recorded but not sampled #3812

Open

Description

@valkum

What happened?

BatchSpanProcessor sends spans with IsRecording = true and Sampled = false to the exporter. SimpleSpanProcessor drops these spans.

The trace SDK specification says that the exporter SHOULD NOT receive these spans (Sampling, Recording Sampled reaction table):

[...] However, Span Exporter SHOULD NOT receive them unless the Sampled flag was also set.

Steps to reproduce:

  1. Configure a sampler that returns SamplingDecision::RecordOnly.
  2. Add a SimpleSpanProcessor and a BatchSpanProcessor, each with an InMemorySpanExporter.
  3. Create and end one span. Call force_flush.
/// Records every span, but samples none.
  #[derive(Clone, Debug)]
  struct RecordOnly;

  impl ShouldSample for RecordOnly {
      fn should_sample(
          &self,
          _parent_context: Option<&Context>,
          _trace_id: TraceId,
          _name: &str,
          _span_kind: &SpanKind,
          _attributes: &[KeyValue],
          _links: &[Link],
      ) -> SamplingResult {
          SamplingResult {
              decision: SamplingDecision::RecordOnly,
              attributes: Vec::new(),
              trace_state: TraceState::default(),
          }
      }
  }

  fn main() {
      let simple = InMemorySpanExporter::default();
      let batch = InMemorySpanExporter::default();
      let provider = SdkTracerProvider::builder()
          .with_sampler(RecordOnly)
          .with_span_processor(SimpleSpanProcessor::new(simple.clone()))
          .with_span_processor(BatchSpanProcessor::builder(batch.clone()).build())
          .build();

      provider.tracer("repro").in_span("record-only", |_cx| {});
      provider.force_flush().unwrap();

      let simple = simple.get_finished_spans().unwrap();
      let batch = batch.get_finished_spans().unwrap();
      println!("SimpleSpanProcessor exported {} span(s)", simple.len());
      println!(
          "BatchSpanProcessor exported {} span(s), sampled = {:?}",
          batch.len(),
          batch.iter().map(|s| s.span_context.is_sampled()).collect::<Vec<_>>()
      );
  }

Output:

SimpleSpanProcessor exported 0 span(s)
BatchSpanProcessor exported 1 span(s), sampled = [false]

Expected: BatchSpanProcessor exports 0 spans, the same as SimpleSpanProcessor.

SimpleSpanProcessor::on_end in opentelemetry-sdk/src/trace/span_processor.rs returns early when span.span_context.is_sampled() is false. The
BatchSpanProcessor in span_processor_with_async_runtime.rs does the same check. BatchSpanProcessor::on_end in span_processor.rs does not do this check.

OpenTelemetry API Version (i.e version of opentelemetry crate)

0.33.1

OpenTelemetry SDK Version (i.e version of opentelemetry_sdk crate)

0.33.1

What Exporter(s) are you seeing the problem on?

N/A

Relevant log output

Tip

React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it. Learn more here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingtriage:todoNeeds to be traiged.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions