Class: CMDx::Telemetry

Inherits:
Object
  • Object
show all
Defined in:
lib/cmdx/telemetry.rb

Overview

Pub/sub for runtime lifecycle and infrastructure events (see EVENTS). Subscribers are callables receiving an Event data object. Telemetry.emit dispatches only when subscribers are registered so telemetry has zero cost when unused.

Defined Under Namespace

Classes: Event

Constant Summary collapse

EVENTS =

Lifecycle and infrastructure event names the runtime and store-backed helpers emit.

%i[
  task_started
  task_deprecated
  task_retried
  task_rolled_back
  task_executed
  lock_acquired
  lock_busy
  idempotency_claimed
  idempotency_completed
  idempotency_duplicate
  circuit_open
  circuit_tripped
  circuit_reset
  rate_limit_admitted
  rate_limit_exceeded
].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize ⇒ Telemetry

Returns a new instance of Telemetry.



69
70
71
# File 'lib/cmdx/telemetry.rb', line 69

def initialize
  @registry = {}
end

Instance Attribute Details

#registry ⇒ Object (readonly)

Returns the value of attribute registry.



67
68
69
# File 'lib/cmdx/telemetry.rb', line 67

def registry
  @registry
end

Class Method Details

.emit(task, name, root: false, **payload) ⇒ void

This method returns an undefined value.

Dispatches payload when task's telemetry registry has subscribers for name. No-op when nothing is subscribed.

Parameters:

  • task (Task)
  • name (Symbol) —

    one of EVENTS

  • root (Boolean) (defaults to: false) —

    whether this task started the active chain

  • payload (Hash{Symbol => Object}) —

    extra fields merged into the event payload



57
58
59
60
61
62
63
# File 'lib/cmdx/telemetry.rb', line 57

def emit(task, name, root: false, **payload)
  telemetry = task.class.telemetry
  return unless telemetry.subscribed?(name)

  event = Event.build(task, name, root:, payload:)
  telemetry.emit(name, event)
end

Instance Method Details

#count ⇒ Integer

Returns total subscribers across all events.

Returns:

  • (Integer) —

    total subscribers across all events



161
162
163
# File 'lib/cmdx/telemetry.rb', line 161

def count
  registry.each_value.sum(&:size)
end

#emit(event, payload) ⇒ void

This method returns an undefined value.

Dispatches payload to every subscriber of event. No-op when there are no subscribers.

Parameters:

  • event (Symbol)
  • payload (Event)


171
172
173
174
175
176
177
178
# File 'lib/cmdx/telemetry.rb', line 171

def emit(event, payload)
  return if empty?

  subscribers = lookup(event)
  return if subscribers.nil? || subscribers.empty?

  subscribers.each { |callable| callable.call(payload) }
end

#empty? ⇒ Boolean

Returns:

  • (Boolean)


151
152
153
# File 'lib/cmdx/telemetry.rb', line 151

def empty?
  registry.empty?
end

#initialize_copy(source) ⇒ void

This method returns an undefined value.

Parameters:

  • source (Telemetry) —

    registry to duplicate



75
76
77
# File 'lib/cmdx/telemetry.rb', line 75

def initialize_copy(source)
  @registry = source.registry.transform_values(&:dup)
end

#lookup(event) ⇒ #call

Parameters:

  • event (Symbol)

Returns:

  • (#call)

Raises:



141
142
143
144
145
146
147
148
# File 'lib/cmdx/telemetry.rb', line 141

def lookup(event)
  registry[event] || begin
    raise UnknownEntryError, <<~MSG.chomp
      unknown telemetry event #{event.inspect}; registered: #{registry.keys.inspect}.
      See https://drexed.github.io/cmdx/configuration/#telemetry
    MSG
  end
end

#size ⇒ Integer

Returns number of subscribed events.

Returns:

  • (Integer) —

    number of subscribed events



156
157
158
# File 'lib/cmdx/telemetry.rb', line 156

def size
  registry.size
end

#subscribe(event, callable = nil, &block) {|evt| ... } ⇒ Telemetry

Registers a subscriber for event.

Parameters:

  • event (Symbol) —

    one of EVENTS

  • callable (#call, nil) (defaults to: nil) —

    subscriber callable; pass either this or a block

  • block (#call, nil) —

    subscriber when callable is omitted

Yield Parameters:

Returns:

Raises:

  • (ArgumentError) —

    when both callable and a block are provided, when the subscriber isn't callable, or when event is unknown



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/cmdx/telemetry.rb', line 88

def subscribe(event, callable = nil, &block)
  subscriber = callable || block

  if callable && block
    raise ArgumentError, "subscriber: provide either a callable or a block, not both"
  elsif !subscriber.respond_to?(:call)
    raise ArgumentError, <<~MSG.chomp
      subscriber must respond to #call (got #{subscriber.class}).
      See https://drexed.github.io/cmdx/configuration/#telemetry
    MSG
  elsif !EVENTS.include?(event)
    raise ArgumentError, <<~MSG.chomp
      unknown telemetry event #{event.inspect}, must be one of #{EVENTS.inspect}.
      See https://drexed.github.io/cmdx/configuration/#telemetry
    MSG
  end

  (registry[event] ||= []) << subscriber
  self
end

#subscribed?(event) ⇒ Boolean

Returns true when at least one subscriber exists for event.

Parameters:

  • event (Symbol)

Returns:

  • (Boolean) —

    true when at least one subscriber exists for event



134
135
136
# File 'lib/cmdx/telemetry.rb', line 134

def subscribed?(event)
  registry.key?(event)
end

#unsubscribe(event, callable) ⇒ Telemetry

Removes a previously registered subscriber. Drops the event entry entirely when no subscribers remain.

Parameters:

  • event (Symbol) —

    one of EVENTS

  • callable (#call) —

    the subscriber to remove

Returns:

Raises:



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/cmdx/telemetry.rb', line 116

def unsubscribe(event, callable)
  unless EVENTS.include?(event)
    raise UnknownEntryError, <<~MSG.chomp
      unknown telemetry event #{event.inspect}, must be one of #{EVENTS.inspect}.
      See https://drexed.github.io/cmdx/configuration/#telemetry
    MSG
  end

  if (subscribers = registry[event])
    subscribers.delete(callable)
    registry.delete(event) if subscribers.empty?
  end

  self
end