Module: WideEvent

Defined in:
lib/wide_event.rb,
lib/wide_event/railtie.rb,
lib/wide_event/version.rb,
lib/wide_event/registry.rb,
lib/wide_event/middleware.rb,
lib/wide_event/subscribers.rb,
lib/wide_event/test_helper.rb,
lib/wide_event/sinks/memory.rb,
lib/wide_event/configuration.rb,
lib/wide_event/sinks/log_line.rb,
lib/wide_event/sinks/otel_span.rb,
lib/wide_event/job_instrumentation.rb,
lib/wide_event/span_counter_processor.rb

Overview

Wide-event accumulator: one flat attribute hash per unit of work (HTTP request or job execution), flushed to the configured sink by WideEvent::Middleware / WideEvent::JobInstrumentation. Safe no-op when no unit of work is open.

Defined Under Namespace

Modules: JobInstrumentation, Sinks, Subscribers, TestHelper Classes: Configuration, Middleware, Railtie, Registry, SpanCounterProcessor

Constant Summary collapse

KEY =
:wide_event_attributes
PROCESS_START =
Process.clock_gettime(Process::CLOCK_MONOTONIC)
VERSION =
"0.1.1"

Class Method Summary collapse

Class Method Details

.active?Boolean

Returns:

  • (Boolean)


52
53
54
# File 'lib/wide_event.rb', line 52

def active?
  !store.nil?
end

.attributesObject



102
103
104
# File 'lib/wide_event.rb', line 102

def attributes
  store&.dup
end

.configObject



26
27
28
# File 'lib/wide_event.rb', line 26

def config
  @config ||= Configuration.new
end

.configure {|config| ... } ⇒ Object

Yields:



30
31
32
33
# File 'lib/wide_event.rb', line 30

def configure
  yield config
  config
end

.count(dep, duration_ms) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
# File 'lib/wide_event.rb', line 65

def count(dep, duration_ms)
  s = store
  return if s.nil?
  registry_check([ "stats.#{dep}_count", "stats.#{dep}_duration_ms" ])
  s["stats.#{dep}_count"] = s.fetch("stats.#{dep}_count", 0) + 1
  s["stats.#{dep}_duration_ms"] = (s.fetch("stats.#{dep}_duration_ms", 0.0) + duration_ms).round(2)
  nil
rescue StandardError => e
  handle_error(e, "count")
  nil
end

.enabled?Boolean

Returns:

  • (Boolean)


39
40
41
# File 'lib/wide_event.rb', line 39

def enabled?
  config.enabled
end

.error!(slug:, exception: nil, expected: false) ⇒ Object



90
91
92
93
94
95
96
97
98
99
100
# File 'lib/wide_event.rb', line 90

def error!(slug:, exception: nil, expected: false)
  attrs = { "error" => true, "exception.slug" => slug, "exception.expected" => expected }
  if exception
    attrs["exception.type"] = exception.class.name
    attrs["exception.message"] = exception.message.to_s[0, 500]
  end
  set(attrs)
rescue StandardError => e
  handle_error(e, "error!")
  nil
end

.flush(attrs) ⇒ Object

Hands a finished wide event to the configured sink. Never raises into the caller.



120
121
122
123
124
125
126
127
# File 'lib/wide_event.rb', line 120

def flush(attrs)
  return if attrs.nil? || attrs.empty?
  config.resolved_sink&.flush(attrs)
  nil
rescue StandardError => e
  handle_error(e, "flush")
  nil
end

.flush_hash(hash, span) ⇒ Object

Sanitize and write a wide-event hash onto an OTel span. span may be nil (OTel inactive). Never raises into the caller.



145
146
147
148
149
150
151
# File 'lib/wide_event.rb', line 145

def flush_hash(hash, span)
  return if hash.nil? || hash.empty? || span.nil?
  span.add_attributes(sanitize(hash))
rescue StandardError => e
  handle_error(e, "flush")
  nil
end

.handle_error(exception, message) ⇒ Object

Telemetry must never raise into app code: every public entry point funnels its rescues here, and a broken handler is itself swallowed.



168
169
170
171
172
173
# File 'lib/wide_event.rb', line 168

def handle_error(exception, message)
  config.error_handler&.call(exception, message)
  nil
rescue StandardError
  nil
end

.install!Object

Wires the pieces that need the host's OpenTelemetry SDK configured: notification subscribers and, for the OTel sink, the span counter on the global tracer provider. Called by the railtie after the app's initializers; call it manually from a non-Rails setup.



133
134
135
136
137
138
139
140
141
# File 'lib/wide_event.rb', line 133

def install!
  Subscribers.subscribe!
  if config.sink == :otel && defined?(OpenTelemetry) &&
     OpenTelemetry.respond_to?(:tracer_provider) &&
     OpenTelemetry.tracer_provider.respond_to?(:add_span_processor)
    OpenTelemetry.tracer_provider.add_span_processor(SpanCounterProcessor.new)
  end
  nil
end

.peekObject

Read-only view of the live store for hot-path handlers that would otherwise dup per event (e.g. every cache read). Callers must not mutate: writes go through set/count/error!.



109
110
111
# File 'lib/wide_event.rb', line 109

def peek
  store
end

.phase(name) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/wide_event.rb', line 77

def phase(name)
  start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  yield
ensure
  elapsed_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000.0
  begin
    set("#{name}.duration_ms" => elapsed_ms.round(2))
  rescue StandardError => e
    handle_error(e, "phase")
    nil
  end
end

.reset_configuration!Object



35
36
37
# File 'lib/wide_event.rb', line 35

def reset_configuration!
  @config = Configuration.new
end

.sanitize(attrs) ⇒ Object



153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/wide_event.rb', line 153

def sanitize(attrs)
  attrs.each_with_object({}) do |(k, v), out|
    key = k.to_s
    case v
    when String, Integer, Float, true, false then out[key] = v
    when Symbol then out[key] = v.to_s
    when Time, DateTime, ActiveSupport::TimeWithZone then out[key] = v.to_time.utc.iso8601(3)
    when nil then nil # drop
    else out[key] = v.to_s[0, 300]
    end
  end
end

.set(attrs) ⇒ Object



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

def set(attrs)
  registry_check(attrs.keys)
  store&.merge!(attrs)
  nil
rescue StandardError => e
  handle_error(e, "set")
  nil
end

.uptime_attributesObject



113
114
115
116
# File 'lib/wide_event.rb', line 113

def uptime_attributes
  uptime = Process.clock_gettime(Process::CLOCK_MONOTONIC) - PROCESS_START
  { "uptime_sec" => uptime.round, "uptime_sec_log10" => Math.log10([ uptime, 1 ].max).round(3) }
end

.withObject



43
44
45
46
47
48
49
50
# File 'lib/wide_event.rb', line 43

def with
  previous = store
  current = {}
  ActiveSupport::IsolatedExecutionState[KEY] = current
  yield current
ensure
  ActiveSupport::IsolatedExecutionState[KEY] = previous
end