Class: Tracelit::ErrorSpanProcessor

Inherits:
Object
  • Object
show all
Defined in:
lib/tracelit/error_span_processor.rb

Overview

ErrorSpanProcessor ensures error spans are always exported regardless of the sampling decision made at span creation time.

How it works:

  • ErrorAlwaysOnSampler returns RECORD_ONLY (not DROP) for unsampled spans, which ensures this processor’s on_finish is called for every span

  • On span finish, if the span has status ERROR, this processor forces it through the exporter directly, bypassing the BatchSpanProcessor

  • BatchSpanProcessor ignores RECORD_ONLY spans (trace_flags.sampled? false) so there is no double-export for sampled error spans

NOTE: opentelemetry-sdk 1.x uses on_finish (not on_end) as the hook name.

Important: this processor must never block application threads. Exporting an unsampled error span synchronously in on_finish can block request / console threads when the ingest endpoint is slow or retrying. We enqueue span data into a bounded in-memory queue and export on a background worker thread.

Constant Summary collapse

QUEUE_CAPACITY =
512
SHUTDOWN_SENTINEL =
Object.new

Instance Method Summary collapse

Constructor Details

#initialize(exporter) ⇒ ErrorSpanProcessor

Returns a new instance of ErrorSpanProcessor.



25
26
27
28
29
30
31
32
33
34
# File 'lib/tracelit/error_span_processor.rb', line 25

def initialize(exporter)
  @exporter = exporter
  @queue = SizedQueue.new(QUEUE_CAPACITY)
  @shutdown = false
  @worker = Thread.new do
    Thread.current[:tracelit_error_export_worker] = true
    worker_loop
  end
  @worker.abort_on_exception = false
end

Instance Method Details

#force_flush(timeout: nil) ⇒ Object



54
55
56
57
# File 'lib/tracelit/error_span_processor.rb', line 54

def force_flush(timeout: nil)
  wait_for_queue_drain(timeout)
  @exporter.force_flush(timeout: timeout)
end

#on_finish(span) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/tracelit/error_span_processor.rb', line 40

def on_finish(span)
  # Skip spans that are not in error — only intervene for errors
  return if span.status.ok?

  # Skip spans that were fully sampled — BatchSpanProcessor handles those.
  # This prevents double-export of error spans on traces that were sampled.
  return if span.context.trace_flags.sampled?

  # Queue for background export; never block the caller.
  enqueue(span.to_span_data)
rescue StandardError
  # Never let processor errors propagate to the application
end

#on_start(_span, _parent_context) ⇒ Object



36
37
38
# File 'lib/tracelit/error_span_processor.rb', line 36

def on_start(_span, _parent_context)
  # nothing to do at start
end

#shutdown(timeout: nil) ⇒ Object



59
60
61
62
63
64
65
66
# File 'lib/tracelit/error_span_processor.rb', line 59

def shutdown(timeout: nil)
  return if @shutdown
  @shutdown = true
  enqueue_shutdown_signal
  @worker&.join(timeout || 1)
  # Do not shut down the shared exporter here —
  # the BatchSpanProcessor owns its lifecycle
end