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/sinks/store.rb,
lib/wide_event/subscribers.rb,
lib/wide_event/test_helper.rb,
lib/wide_event/sinks/memory.rb,
lib/wide_event/store/client.rb,
lib/wide_event/store/sender.rb,
lib/wide_event/configuration.rb,
lib/wide_event/setup/checker.rb,
lib/wide_event/sinks/log_line.rb,
lib/wide_event/store/envelope.rb,
lib/wide_event/sinks/otel_span.rb,
lib/wide_event/store/formatter.rb,
lib/wide_event/store/query_result.rb,
lib/wide_event/job_instrumentation.rb,
lib/wide_event/kamal/deploy_editor.rb,
lib/wide_event/kamal/secrets_editor.rb,
lib/wide_event/setup/command_runner.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, Kamal, Setup, Sinks, Store, 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.2.1"

Class Method Summary collapse

Class Method Details

.active?Boolean

Returns:

  • (Boolean)


73
74
75
# File 'lib/wide_event.rb', line 73

def active?
  !store.nil?
end

.attributesObject



123
124
125
# File 'lib/wide_event.rb', line 123

def attributes
  store&.dup
end

.configObject



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

def config
  @config ||= Configuration.new
end

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

Yields:



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

def configure
  yield config
  config
end

.count(dep, duration_ms) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
# File 'lib/wide_event.rb', line 86

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)


60
61
62
# File 'lib/wide_event.rb', line 60

def enabled?
  config.enabled
end

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



111
112
113
114
115
116
117
118
119
120
121
# File 'lib/wide_event.rb', line 111

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.



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

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.



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

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.



189
190
191
192
193
194
# File 'lib/wide_event.rb', line 189

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.



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

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!.



130
131
132
# File 'lib/wide_event.rb', line 130

def peek
  store
end

.phase(name) ⇒ Object



98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/wide_event.rb', line 98

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



41
42
43
44
# File 'lib/wide_event.rb', line 41

def reset_configuration!
  @config&.shutdown_store_sender!
  @config = Configuration.new
end

.sanitize(attrs) ⇒ Object



174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/wide_event.rb', line 174

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



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

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

.shutdown!(timeout: 2.0) ⇒ Object

Best-effort flush of any in-flight store delivery: gives the sender's worker thread up to timeout seconds to drain its queue before the process exits. A no-op when no sink has been resolved yet (nothing to flush) or when the resolved sink has no shutdown hook. Never raises.



50
51
52
53
54
55
56
57
58
# File 'lib/wide_event.rb', line 50

def shutdown!(timeout: 2.0)
  return nil unless config.resolved?
  sink = config.resolved_sink
  sink.shutdown(timeout: timeout) if sink.respond_to?(:shutdown)
  nil
rescue StandardError => e
  handle_error(e, "shutdown")
  nil
end

.uptime_attributesObject



134
135
136
137
# File 'lib/wide_event.rb', line 134

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



64
65
66
67
68
69
70
71
# File 'lib/wide_event.rb', line 64

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