Module: OpenLoam::EventLog

Defined in:
lib/open_loam/event_log.rb

Overview

Captures every published event as a queryable OpenLoam::EventRecord row — the capture half of the event system, where OpenLoam::DurableEvents is the delivery half ("durability is of DELIVERY, not CAPTURE").

Capture is ON by default and captures everything but OpenLoam.uncaptured_events. That inverts OpenLoam.broadcast_events on purpose; see ADR 0007 for why, and for why this stayed in-gem rather than adopting Rails Event Store.

Capture runs INLINE in the publisher's thread, so a failed insert propagates into whatever published the event. Deliberate — do not rescue it. Swallowing the error re-opens the silent gap this module exists to close.

Constant Summary collapse

PRUNE_KEY =
"open_loam_event_log_prune".freeze

Class Method Summary collapse

Class Method Details

.capturable?(event_name) ⇒ Boolean

Exclusion is the only knob, because pattern_matches? has no match-all spelling ("" is an exact match on the empty name). Do not add an allow-list: capture-all has to stay the absence of configuration.

Returns:

  • (Boolean)


25
26
27
28
29
30
31
# File 'lib/open_loam/event_log.rb', line 25

def capturable?(event_name)
  OpenLoam.uncaptured_events.none? do |pattern|
    next false if OpenLoam::Overrides.disabled?(:uncaptured_events, pattern) # an app can re-enable a default exclusion

    OpenLoam::Events.pattern_matches?(pattern, event_name)
  end
end

.capture(event_name, payload) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/open_loam/event_log.rb', line 33

def capture(event_name, payload)
  return unless capturable?(event_name)

  tenant = OpenLoam::Tenant.find_by(id: payload[:tenant_id])
  return if tenant.nil? # nil-tenant events are not captured (as Webhooks.dispatch / DurableEvents.capture)

  OpenLoam.as_tenant(tenant) do
    OpenLoam::EventRecord.create!(
      name: event_name.to_s,
      payload: payload.transform_keys(&:to_s),
      occurred_at: Time.current
    )
  end
end

.prune(now: Time.current) ⇒ Object

delete_all, NOT destroy_all: rows are readonly once persisted, and destroy on a readonly record raises ActiveRecord::ReadOnlyRecord.



69
70
71
72
73
74
# File 'lib/open_loam/event_log.rb', line 69

def prune(now: Time.current)
  retention = OpenLoam.event_log_retention
  return 0 if retention.nil?

  OpenLoam::EventRecord.where(occurred_at: ...(now - retention)).delete_all
end

.read(name_or_prefix = nil, since: nil, limit: nil) ⇒ Object

name_or_prefix takes an exact event name or a trailing-dot domain prefix, the same rule as Events.subscribe. Oldest first.



50
51
52
53
54
55
56
# File 'lib/open_loam/event_log.rb', line 50

def read(name_or_prefix = nil, since: nil, limit: nil)
  scope = OpenLoam::EventRecord.chronological
  scope = scope.matching(name_or_prefix) if name_or_prefix.present?
  scope = scope.where(occurred_at: since..) if since
  scope = scope.limit(limit) if limit
  scope
end

.replay(name_or_prefix = nil, since: nil, limit: nil, &handler) ⇒ Object

A re-read of history, NOT a second publish: nothing else on the bus fires and a replayed event is not re-captured. Handlers must be idempotent, and get string payload keys (from the row) where a live subscriber got symbols.



61
62
63
64
65
# File 'lib/open_loam/event_log.rb', line 61

def replay(name_or_prefix = nil, since: nil, limit: nil, &handler)
  read(name_or_prefix, since: since, limit: limit).each do |record|
    handler.call(record.name, record.payload_hash)
  end
end

.subscribe! ⇒ Object

Idempotent: subscribing twice would capture every event twice.



18
19
20
# File 'lib/open_loam/event_log.rb', line 18

def subscribe!
  @subscription ||= OpenLoam::Events.subscribe_all { |event_name, payload| capture(event_name, payload) }
end