Module: Foam::Otel::Metrics

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

Overview

Backs the four metric helpers — one per synchronous OTel instrument (BASE_PACKAGE_SPEC section 0 metrics row): counter, histogram, up/down counter, and gauge. Instruments are lazy-created and cached, keyed on (kind, name); the whole cache rebinds exactly once when the meter provider identity changes (init swapping the proxy for foam's provider, or a foreign provider owning the slot). Creating an instrument per call re-registers it under a mutex and warns — hence the cache. Names pass through verbatim. Never raises.

The source provider is always OpenTelemetry.meter_provider: foam's own when the metrics slot was FREE, or the FOREIGN provider when a foreign SDK owns it (inert mode, rule 18 B — the metric helpers emit through whoever owns the slot).

Constant Summary collapse

MUTEX =
Mutex.new

Class Method Summary collapse

Class Method Details

.add_up_down_counter(name, by:, attributes: nil) ⇒ Object



52
53
54
55
56
57
58
59
60
# File 'lib/foam/otel/metrics.rb', line 52

def add_up_down_counter(name, by:, attributes: nil)
  return true if disabled?

  inst = instrument(:up_down_counter, name) or return false
  inst.add(Float(by), attributes: stringify(attributes))
  true
rescue StandardError, SystemStackError
  false
end

.build(meter, kind, name) ⇒ Object



92
93
94
95
96
97
98
99
# File 'lib/foam/otel/metrics.rb', line 92

def build(meter, kind, name)
  case kind
  when :counter then meter.create_counter(name)
  when :histogram then meter.create_histogram(name)
  when :up_down_counter then meter.create_up_down_counter(name)
  when :gauge then meter.create_gauge(name)
  end
end

.disabled?Boolean

section 0: disabled means fully dark — the metric helpers never touch the live (possibly foreign) meter provider once foam is initialized with effective enabled=false.

Returns:

  • (Boolean)


104
105
106
# File 'lib/foam/otel/metrics.rb', line 104

def disabled?
  Foam::Otel.send(:helpers_disabled?)
end

.increment_counter(name, by: 1, attributes: nil) ⇒ Object



32
33
34
35
36
37
38
39
40
# File 'lib/foam/otel/metrics.rb', line 32

def increment_counter(name, by: 1, attributes: nil)
  return true if disabled? # fully dark — accepted, silently dropped (section 0)

  inst = instrument(:counter, name) or return false
  inst.add(Float(by), attributes: stringify(attributes))
  true
rescue StandardError, SystemStackError
  false
end

.instrument(kind, name) ⇒ Object

---- internals -------------------------------------------------------



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/foam/otel/metrics.rb', line 74

def instrument(kind, name)
  key = [kind, name.to_s]
  return nil if name.to_s.empty?

  provider = OpenTelemetry.meter_provider
  # The metric cache rebind is the one place foam notices a provider
  # identity change — surface a late-arrival displacement here too
  # (rule 11 / 18 step 5).
  Foam::Otel.send(:warn_if_displaced, :metrics, provider)
  MUTEX.synchronize do
    unless @provider.equal?(provider)
      @provider = provider
      @cache = {}
    end
    @cache[key] ||= build(provider.meter("foam"), kind, name.to_s)
  end
end

.record_histogram(name, value, attributes: nil) ⇒ Object



42
43
44
45
46
47
48
49
50
# File 'lib/foam/otel/metrics.rb', line 42

def record_histogram(name, value, attributes: nil)
  return true if disabled?

  inst = instrument(:histogram, name) or return false
  inst.record(Float(value), attributes: stringify(attributes))
  true
rescue StandardError, SystemStackError
  false
end

.reset_for_tests!Object



25
26
27
28
29
30
# File 'lib/foam/otel/metrics.rb', line 25

def reset_for_tests!
  MUTEX.synchronize do
    @provider = nil
    @cache = {}
  end
end

.set_metric(name, value, attributes: nil) ⇒ Object



62
63
64
65
66
67
68
69
70
# File 'lib/foam/otel/metrics.rb', line 62

def set_metric(name, value, attributes: nil)
  return true if disabled?

  inst = instrument(:gauge, name) or return false
  inst.record(Float(value), attributes: stringify(attributes))
  true
rescue StandardError, SystemStackError
  false
end

.stringify(attributes) ⇒ Object

Metric attributes ride the always-on redaction floor like span/log attributes (rule 14: no raw secret in ANY exported metric) — a secret-shaped label a caller passes to a metric helper is masked before it becomes a metric-stream dimension. (Ruby official gems rarely emit metrics; the floor covers the foam-helper surface.)

nil (the helpers' own default) becomes {} — NEVER nil: the OTLP metrics exporter's encoder crashes on a nil-attributes data point (opentelemetry-exporter-otlp-metrics 0.10.0 metrics_exporter.rb number_data_point → attributes.map), and one such point silently drops the ENTIRE metrics export request, healthy instruments included (rule 15 silent data loss). Garbage degrades to {} too.



120
121
122
123
124
125
# File 'lib/foam/otel/metrics.rb', line 120

def stringify(attributes)
  stringified = (attributes || {}).each_with_object({}) { |(k, v), out| out[k.to_s] = v }
  Redaction.mask_attributes(stringified, Foam::Otel.active_config)
rescue StandardError, SystemStackError
  {}
end