Module: Foam::Otel::Errors

Defined in:
lib/foam/otel/errors.rb

Overview

One exception, one event (contract SPEC.md section 12): every capture path — the Rack middleware's unhandled-raise rescue, the railtie's process_action notification hook, the error-tracker bridge, and the public record_exception API — records through here, deduped per span by exception object identity.

The seen-set lives ON the span object (an instance variable), not in a thread-local: a request span can be recorded on one thread and finished on another (ActionController::Live streams the response body from a separate thread), so a thread-local would both miss the dedupe/recorded check across threads and leak entries for spans that never pass back through their owning thread. Span-carried state is GC'd with the span and travels with it across threads.

Constant Summary collapse

IVAR =
:@__foam_otel_recorded

Class Method Summary collapse

Class Method Details

.record_once(span, error) ⇒ Object

Records error on span (exception event + status ERROR) unless this exact exception object was already recorded there. Never raises.



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/foam/otel/errors.rb', line 25

def record_once(span, error)
  return false unless span.respond_to?(:context) && span.context.valid?
  return false unless span.respond_to?(:recording?) && span.recording?

  seen = span.instance_variable_get(IVAR)
  unless seen
    seen = []
    span.instance_variable_set(IVAR, seen)
  end
  return false if seen.include?(error.object_id)

  seen << error.object_id
  span.record_exception(error)
  span.status = OpenTelemetry::Trace::Status.error(error.message.to_s)
  true
rescue StandardError
  false
end

.recorded?(span) ⇒ Boolean

Whether any exception has been recorded on this span (so a later OK status never clobbers a thrown-then-mapped ERROR — SPEC section 6).

Returns:

  • (Boolean)


46
47
48
49
50
51
# File 'lib/foam/otel/errors.rb', line 46

def recorded?(span)
  seen = span.instance_variable_get(IVAR)
  !seen.nil? && !seen.empty?
rescue StandardError
  false
end