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
73
74
75
|
# File 'lib/wide_event.rb', line 73
def active?
!store.nil?
end
|
.attributes ⇒ Object
123
124
125
|
# File 'lib/wide_event.rb', line 123
def attributes
store&.dup
end
|
.config ⇒ Object
32
33
34
|
# File 'lib/wide_event.rb', line 32
def config
@config ||= Configuration.new
end
|
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
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
|
.peek ⇒ Object
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 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_attributes ⇒ Object
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
|
.with ⇒ Object
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
|