e11y 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.rubocop.yml +20 -0
- data/CHANGELOG.md +151 -13
- data/README.md +1138 -104
- data/RELEASE.md +254 -0
- data/Rakefile +377 -0
- data/benchmarks/OPTIMIZATION.md +246 -0
- data/benchmarks/README.md +103 -0
- data/benchmarks/allocation_profiling.rb +253 -0
- data/benchmarks/e11y_benchmarks.rb +447 -0
- data/benchmarks/ruby_baseline_allocations.rb +175 -0
- data/benchmarks/run_all.rb +9 -21
- data/docs/00-ICP-AND-TIMELINE.md +2 -2
- data/docs/ADR-001-architecture.md +1 -1
- data/docs/ADR-004-adapter-architecture.md +247 -0
- data/docs/ADR-009-cost-optimization.md +231 -115
- data/docs/ADR-017-multi-rails-compatibility.md +103 -0
- data/docs/ADR-INDEX.md +99 -0
- data/docs/CONTRIBUTING.md +312 -0
- data/docs/IMPLEMENTATION_PLAN.md +1 -1
- data/docs/QUICK-START.md +0 -6
- data/docs/use_cases/UC-019-retention-based-routing.md +584 -0
- data/e11y.gemspec +28 -17
- data/lib/e11y/adapters/adaptive_batcher.rb +3 -0
- data/lib/e11y/adapters/audit_encrypted.rb +10 -4
- data/lib/e11y/adapters/base.rb +15 -0
- data/lib/e11y/adapters/file.rb +4 -1
- data/lib/e11y/adapters/in_memory.rb +6 -0
- data/lib/e11y/adapters/loki.rb +9 -0
- data/lib/e11y/adapters/otel_logs.rb +11 -9
- data/lib/e11y/adapters/sentry.rb +9 -0
- data/lib/e11y/adapters/yabeda.rb +54 -10
- data/lib/e11y/buffers.rb +8 -8
- data/lib/e11y/console.rb +52 -60
- data/lib/e11y/event/base.rb +75 -10
- data/lib/e11y/event/value_sampling_config.rb +10 -4
- data/lib/e11y/events/rails/http/request.rb +1 -1
- data/lib/e11y/instruments/active_job.rb +6 -3
- data/lib/e11y/instruments/rails_instrumentation.rb +51 -28
- data/lib/e11y/instruments/sidekiq.rb +7 -7
- data/lib/e11y/logger/bridge.rb +24 -54
- data/lib/e11y/metrics/cardinality_protection.rb +257 -12
- data/lib/e11y/metrics/cardinality_tracker.rb +17 -0
- data/lib/e11y/metrics/registry.rb +6 -2
- data/lib/e11y/metrics/relabeling.rb +0 -56
- data/lib/e11y/metrics.rb +6 -1
- data/lib/e11y/middleware/audit_signing.rb +12 -9
- data/lib/e11y/middleware/pii_filter.rb +18 -10
- data/lib/e11y/middleware/request.rb +10 -4
- data/lib/e11y/middleware/routing.rb +117 -90
- data/lib/e11y/middleware/sampling.rb +47 -28
- data/lib/e11y/middleware/trace_context.rb +40 -11
- data/lib/e11y/middleware/validation.rb +20 -2
- data/lib/e11y/middleware/versioning.rb +1 -1
- data/lib/e11y/pii.rb +7 -7
- data/lib/e11y/railtie.rb +24 -20
- data/lib/e11y/reliability/circuit_breaker.rb +3 -0
- data/lib/e11y/reliability/dlq/file_storage.rb +16 -5
- data/lib/e11y/reliability/dlq/filter.rb +3 -0
- data/lib/e11y/reliability/retry_handler.rb +4 -0
- data/lib/e11y/sampling/error_spike_detector.rb +16 -5
- data/lib/e11y/sampling/load_monitor.rb +13 -4
- data/lib/e11y/self_monitoring/reliability_monitor.rb +3 -0
- data/lib/e11y/version.rb +1 -1
- data/lib/e11y.rb +86 -9
- metadata +83 -38
- data/docs/use_cases/UC-019-tiered-storage-migration.md +0 -562
- data/lib/e11y/middleware/pii_filtering.rb +0 -280
- data/lib/e11y/middleware/slo.rb +0 -168
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module E11y
|
|
4
|
-
module Middleware
|
|
5
|
-
# PII Filtering Middleware - 3-Tier Strategy
|
|
6
|
-
#
|
|
7
|
-
# Filters Personally Identifiable Information (PII) from event payloads
|
|
8
|
-
# before they reach adapters. Implements ADR-006 3-tier security model.
|
|
9
|
-
#
|
|
10
|
-
# **Three-Tier Strategy:**
|
|
11
|
-
# - Tier 1: No PII (`contains_pii false`) - Skip filtering (0ms overhead)
|
|
12
|
-
# - Tier 2: Default - Rails filters only (~0.05ms overhead)
|
|
13
|
-
# - Tier 3: Explicit PII (`contains_pii true`) - Deep filtering (~0.2ms overhead)
|
|
14
|
-
#
|
|
15
|
-
# @example Basic Usage (Tier 2 - Default)
|
|
16
|
-
# class Events::OrderCreated < E11y::Event::Base
|
|
17
|
-
# schema do
|
|
18
|
-
# required(:order_id).filled(:string)
|
|
19
|
-
# optional(:api_key).filled(:string) # Rails will filter this
|
|
20
|
-
# end
|
|
21
|
-
# end
|
|
22
|
-
#
|
|
23
|
-
# @example Tier 1: No PII (High Performance)
|
|
24
|
-
# class Events::HealthCheck < E11y::Event::Base
|
|
25
|
-
# contains_pii false # Skip all filtering
|
|
26
|
-
# end
|
|
27
|
-
#
|
|
28
|
-
# @example Tier 3: Explicit PII (Deep Filtering)
|
|
29
|
-
# class Events::UserRegistered < E11y::Event::Base
|
|
30
|
-
# contains_pii true
|
|
31
|
-
#
|
|
32
|
-
# pii_filtering do
|
|
33
|
-
# masks :password
|
|
34
|
-
# hashes :email
|
|
35
|
-
# allows :user_id
|
|
36
|
-
# end
|
|
37
|
-
# end
|
|
38
|
-
#
|
|
39
|
-
# @see ADR-006 PII Security & Compliance
|
|
40
|
-
# @see UC-007 PII Filtering
|
|
41
|
-
# @see E11y::PII::Patterns
|
|
42
|
-
class PIIFiltering < Base
|
|
43
|
-
middleware_zone :security
|
|
44
|
-
|
|
45
|
-
# Initialize PII filtering middleware
|
|
46
|
-
#
|
|
47
|
-
# @param app [Proc] Next middleware in chain
|
|
48
|
-
# @param config [Hash] Configuration options
|
|
49
|
-
def initialize(app, config = {})
|
|
50
|
-
super(app)
|
|
51
|
-
@config = config
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# Process event and filter PII based on tier
|
|
55
|
-
#
|
|
56
|
-
# @param event_data [Hash] Event data with payload
|
|
57
|
-
# @return [Hash] Processed event data
|
|
58
|
-
def call(event_data)
|
|
59
|
-
# Determine filtering tier
|
|
60
|
-
tier = determine_tier(event_data)
|
|
61
|
-
|
|
62
|
-
case tier
|
|
63
|
-
when :tier1
|
|
64
|
-
# Tier 1: No PII - Skip filtering (0ms overhead)
|
|
65
|
-
@app.call(event_data)
|
|
66
|
-
when :tier2
|
|
67
|
-
# Tier 2: Rails filters only (~0.05ms overhead)
|
|
68
|
-
filtered_data = apply_rails_filters(event_data)
|
|
69
|
-
@app.call(filtered_data)
|
|
70
|
-
when :tier3
|
|
71
|
-
# Tier 3: Deep filtering (~0.2ms overhead)
|
|
72
|
-
filtered_data = apply_deep_filtering(event_data)
|
|
73
|
-
@app.call(filtered_data)
|
|
74
|
-
else
|
|
75
|
-
@app.call(event_data)
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
private
|
|
80
|
-
|
|
81
|
-
# Determine PII filtering tier for event
|
|
82
|
-
#
|
|
83
|
-
# @param event_data [Hash] Event data
|
|
84
|
-
# @return [Symbol] :tier1, :tier2, or :tier3
|
|
85
|
-
def determine_tier(event_data)
|
|
86
|
-
event_class = event_data[:event_class]
|
|
87
|
-
return :tier2 unless event_class
|
|
88
|
-
|
|
89
|
-
# Check explicit declaration
|
|
90
|
-
if event_class.respond_to?(:pii_tier)
|
|
91
|
-
case event_class.pii_tier
|
|
92
|
-
when :none
|
|
93
|
-
:tier1
|
|
94
|
-
when :explicit
|
|
95
|
-
:tier3
|
|
96
|
-
else
|
|
97
|
-
:tier2
|
|
98
|
-
end
|
|
99
|
-
else
|
|
100
|
-
# Default: Tier 2 (Rails filters)
|
|
101
|
-
:tier2
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# Apply Rails filter_parameters (Tier 2)
|
|
106
|
-
#
|
|
107
|
-
# @param event_data [Hash] Event data
|
|
108
|
-
# @return [Hash] Filtered event data
|
|
109
|
-
def apply_rails_filters(event_data)
|
|
110
|
-
return event_data unless defined?(Rails)
|
|
111
|
-
|
|
112
|
-
# Clone to avoid modifying original
|
|
113
|
-
filtered_data = deep_dup(event_data)
|
|
114
|
-
|
|
115
|
-
# Apply Rails parameter filter
|
|
116
|
-
filter = parameter_filter
|
|
117
|
-
filtered_data[:payload] = filter.filter(filtered_data[:payload])
|
|
118
|
-
|
|
119
|
-
filtered_data
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
# Apply deep PII filtering (Tier 3)
|
|
123
|
-
#
|
|
124
|
-
# @param event_data [Hash] Event data
|
|
125
|
-
# @return [Hash] Filtered event data
|
|
126
|
-
def apply_deep_filtering(event_data)
|
|
127
|
-
event_class = event_data[:event_class]
|
|
128
|
-
return event_data unless event_class
|
|
129
|
-
|
|
130
|
-
# Clone to avoid modifying original
|
|
131
|
-
filtered_data = deep_dup(event_data)
|
|
132
|
-
|
|
133
|
-
# Get PII filtering config from event class
|
|
134
|
-
pii_config = event_class.pii_filtering_config if event_class.respond_to?(:pii_filtering_config)
|
|
135
|
-
return filtered_data unless pii_config
|
|
136
|
-
|
|
137
|
-
# Apply field-level strategies
|
|
138
|
-
filtered_data[:payload] = apply_field_strategies(
|
|
139
|
-
filtered_data[:payload],
|
|
140
|
-
pii_config
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
# Apply pattern-based filtering
|
|
144
|
-
filtered_data[:payload] = apply_pattern_filtering(
|
|
145
|
-
filtered_data[:payload]
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
filtered_data
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
# Apply field-level filtering strategies
|
|
152
|
-
#
|
|
153
|
-
# @param payload [Hash] Payload to filter
|
|
154
|
-
# @param config [Hash] PII configuration
|
|
155
|
-
# @return [Hash] Filtered payload
|
|
156
|
-
def apply_field_strategies(payload, config)
|
|
157
|
-
return payload unless config
|
|
158
|
-
|
|
159
|
-
filtered = {}
|
|
160
|
-
|
|
161
|
-
payload.each do |key, value|
|
|
162
|
-
strategy = config.dig(:fields, key, :strategy) || :allow
|
|
163
|
-
|
|
164
|
-
filtered[key] = case strategy
|
|
165
|
-
when :mask
|
|
166
|
-
"[FILTERED]"
|
|
167
|
-
when :hash
|
|
168
|
-
hash_value(value)
|
|
169
|
-
when :partial
|
|
170
|
-
partial_mask(value)
|
|
171
|
-
when :redact
|
|
172
|
-
nil
|
|
173
|
-
when :allow
|
|
174
|
-
value
|
|
175
|
-
else
|
|
176
|
-
value
|
|
177
|
-
end
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
filtered
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
# Apply pattern-based filtering to string values
|
|
184
|
-
#
|
|
185
|
-
# @param data [Object] Data to filter (recursively)
|
|
186
|
-
# @return [Object] Filtered data
|
|
187
|
-
def apply_pattern_filtering(data)
|
|
188
|
-
case data
|
|
189
|
-
when Hash
|
|
190
|
-
data.transform_values { |v| apply_pattern_filtering(v) }
|
|
191
|
-
when Array
|
|
192
|
-
data.map { |v| apply_pattern_filtering(v) }
|
|
193
|
-
when String
|
|
194
|
-
filter_string_patterns(data)
|
|
195
|
-
else
|
|
196
|
-
data
|
|
197
|
-
end
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
# Filter PII patterns in string
|
|
201
|
-
#
|
|
202
|
-
# @param str [String] String to filter
|
|
203
|
-
# @return [String] Filtered string
|
|
204
|
-
def filter_string_patterns(str)
|
|
205
|
-
result = str.dup
|
|
206
|
-
|
|
207
|
-
# Apply all PII patterns
|
|
208
|
-
E11y::PII::Patterns::ALL.each do |pattern|
|
|
209
|
-
result = result.gsub(pattern, "[FILTERED]")
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
result
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
# Hash value using SHA256
|
|
216
|
-
#
|
|
217
|
-
# @param value [Object] Value to hash
|
|
218
|
-
# @return [String] Hashed value
|
|
219
|
-
def hash_value(value)
|
|
220
|
-
return "[FILTERED]" if value.nil?
|
|
221
|
-
|
|
222
|
-
require "digest"
|
|
223
|
-
"hashed_#{Digest::SHA256.hexdigest(value.to_s)[0..15]}"
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
# Partial mask (show first/last chars)
|
|
227
|
-
#
|
|
228
|
-
# @param value [String] Value to mask
|
|
229
|
-
# @return [String] Partially masked value
|
|
230
|
-
def partial_mask(value)
|
|
231
|
-
return "[FILTERED]" unless value.is_a?(String)
|
|
232
|
-
return "[FILTERED]" if value.length < 4
|
|
233
|
-
|
|
234
|
-
if value.include?("@")
|
|
235
|
-
# Email: show first 2 chars before @, last 2 after @
|
|
236
|
-
local, domain = value.split("@", 2)
|
|
237
|
-
"#{local[0..1]}***@#{domain[-3..-1]}"
|
|
238
|
-
else
|
|
239
|
-
# Generic: show first/last 2 chars
|
|
240
|
-
"#{value[0..1]}***#{value[-2..-1]}"
|
|
241
|
-
end
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
# Deep duplicate data structure
|
|
245
|
-
#
|
|
246
|
-
# @param data [Object] Data to duplicate
|
|
247
|
-
# @return [Object] Duplicated data
|
|
248
|
-
def deep_dup(data)
|
|
249
|
-
case data
|
|
250
|
-
when Hash
|
|
251
|
-
data.transform_values { |v| deep_dup(v) }
|
|
252
|
-
when Array
|
|
253
|
-
data.map { |v| deep_dup(v) }
|
|
254
|
-
when String, Symbol, Integer, Float, TrueClass, FalseClass, NilClass
|
|
255
|
-
data
|
|
256
|
-
else
|
|
257
|
-
begin
|
|
258
|
-
data.dup
|
|
259
|
-
rescue StandardError
|
|
260
|
-
data
|
|
261
|
-
end
|
|
262
|
-
end
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
# Get Rails parameter filter
|
|
266
|
-
#
|
|
267
|
-
# @return [ActiveSupport::ParameterFilter] Parameter filter
|
|
268
|
-
def parameter_filter
|
|
269
|
-
@parameter_filter ||= if defined?(Rails)
|
|
270
|
-
ActiveSupport::ParameterFilter.new(
|
|
271
|
-
Rails.application.config.filter_parameters
|
|
272
|
-
)
|
|
273
|
-
else
|
|
274
|
-
# Fallback for non-Rails environments
|
|
275
|
-
ActiveSupport::ParameterFilter.new([])
|
|
276
|
-
end
|
|
277
|
-
end
|
|
278
|
-
end
|
|
279
|
-
end
|
|
280
|
-
end
|
data/lib/e11y/middleware/slo.rb
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "e11y/middleware/base"
|
|
4
|
-
require "e11y/slo/event_driven"
|
|
5
|
-
|
|
6
|
-
module E11y
|
|
7
|
-
module Middleware
|
|
8
|
-
# SLO Middleware for Event-Driven SLO tracking (ADR-014).
|
|
9
|
-
#
|
|
10
|
-
# Automatically processes events with SLO configuration enabled,
|
|
11
|
-
# computes `slo_status` from payload, and emits metrics.
|
|
12
|
-
#
|
|
13
|
-
# **Features:**
|
|
14
|
-
# - Auto-detects events with `slo { enabled true }`
|
|
15
|
-
# - Calls `slo_status_from` proc to compute 'success'/'failure'
|
|
16
|
-
# - Emits `slo_event_result_total{slo_status}` metric to Yabeda
|
|
17
|
-
# - Never fails event tracking (graceful error handling)
|
|
18
|
-
#
|
|
19
|
-
# **Middleware Zone:** `:post_processing` (after routing, before adapters)
|
|
20
|
-
#
|
|
21
|
-
# **ADR References:**
|
|
22
|
-
# - ADR-014 §3 (Event SLO DSL)
|
|
23
|
-
# - ADR-014 §4 (SLO Status Calculation)
|
|
24
|
-
# - ADR-014 §6 (Metrics Export)
|
|
25
|
-
# - ADR-015 §3 (Middleware Order)
|
|
26
|
-
#
|
|
27
|
-
# **Use Case:** UC-014 (Event-Driven SLO)
|
|
28
|
-
#
|
|
29
|
-
# @example Configuration
|
|
30
|
-
# E11y.configure do |config|
|
|
31
|
-
# # Enable SLO middleware (auto-enabled if any Events have slo { enabled true })
|
|
32
|
-
# config.pipeline.use E11y::Middleware::SLO, zone: :post_processing
|
|
33
|
-
# end
|
|
34
|
-
#
|
|
35
|
-
# @example Event with SLO
|
|
36
|
-
# module Events
|
|
37
|
-
# class PaymentProcessed < E11y::Event::Base
|
|
38
|
-
# schema do
|
|
39
|
-
# required(:payment_id).filled(:string)
|
|
40
|
-
# required(:status).filled(:string)
|
|
41
|
-
# end
|
|
42
|
-
#
|
|
43
|
-
# slo do
|
|
44
|
-
# enabled true
|
|
45
|
-
# slo_status_from do |payload|
|
|
46
|
-
# case payload[:status]
|
|
47
|
-
# when 'completed' then 'success'
|
|
48
|
-
# when 'failed' then 'failure'
|
|
49
|
-
# else nil # Not counted
|
|
50
|
-
# end
|
|
51
|
-
# end
|
|
52
|
-
# end
|
|
53
|
-
# end
|
|
54
|
-
# end
|
|
55
|
-
#
|
|
56
|
-
# # Tracking will automatically emit SLO metric:
|
|
57
|
-
# Events::PaymentProcessed.track(payment_id: 'p123', status: 'completed')
|
|
58
|
-
# # → Emits: slo_event_result_total{event_name="payment.processed", slo_status="success"} +1
|
|
59
|
-
#
|
|
60
|
-
# @see ADR-014 for complete Event-Driven SLO architecture
|
|
61
|
-
class SLO < Base
|
|
62
|
-
middleware_zone :post_processing
|
|
63
|
-
|
|
64
|
-
# Process event and emit SLO metric if SLO is enabled.
|
|
65
|
-
#
|
|
66
|
-
# @param event_data [Hash] Event payload
|
|
67
|
-
# @return [Hash] Unchanged event_data (passthrough)
|
|
68
|
-
def call(event_data)
|
|
69
|
-
# Skip if SLO not enabled for this event
|
|
70
|
-
event_class = resolve_event_class(event_data)
|
|
71
|
-
return event_data unless event_class&.respond_to?(:slo_config)
|
|
72
|
-
return event_data unless event_class.slo_config&.enabled
|
|
73
|
-
|
|
74
|
-
# Compute slo_status from payload
|
|
75
|
-
slo_status = compute_slo_status(event_class, event_data[:payload])
|
|
76
|
-
return event_data unless slo_status
|
|
77
|
-
|
|
78
|
-
# Emit SLO metric
|
|
79
|
-
emit_slo_metric(event_class, slo_status, event_data[:payload])
|
|
80
|
-
|
|
81
|
-
event_data # Passthrough (never modify event_data)
|
|
82
|
-
rescue StandardError => e
|
|
83
|
-
# Never fail event tracking due to SLO processing
|
|
84
|
-
E11y.logger.error(
|
|
85
|
-
"[E11y::Middleware::SLO] SLO processing failed for #{event_data[:event_name]}: #{e.message}"
|
|
86
|
-
)
|
|
87
|
-
event_data
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
private
|
|
91
|
-
|
|
92
|
-
# Resolve Event class from event_name.
|
|
93
|
-
#
|
|
94
|
-
# @param event_data [Hash] Event payload
|
|
95
|
-
# @return [Class, nil] Event class or nil if not found
|
|
96
|
-
def resolve_event_class(event_data)
|
|
97
|
-
event_name = event_data[:event_name]
|
|
98
|
-
return nil unless event_name
|
|
99
|
-
|
|
100
|
-
# Convert event_name to class name (e.g., "payment.processed" → "Events::PaymentProcessed")
|
|
101
|
-
# This assumes Rails autoloading or explicit requires
|
|
102
|
-
class_name = event_name.to_s.split(".").map(&:capitalize).join
|
|
103
|
-
"Events::#{class_name}".constantize
|
|
104
|
-
rescue NameError
|
|
105
|
-
# Event class not found (may be from external source)
|
|
106
|
-
nil
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
# Compute slo_status using event's slo_status_from proc.
|
|
110
|
-
#
|
|
111
|
-
# @param event_class [Class] Event class
|
|
112
|
-
# @param payload [Hash] Event payload
|
|
113
|
-
# @return [String, nil] 'success', 'failure', or nil
|
|
114
|
-
def compute_slo_status(event_class, payload)
|
|
115
|
-
return nil unless event_class.slo_config.slo_status_proc
|
|
116
|
-
|
|
117
|
-
event_class.slo_config.slo_status_proc.call(payload)
|
|
118
|
-
rescue StandardError => e
|
|
119
|
-
E11y.logger.error(
|
|
120
|
-
"[E11y::Middleware::SLO] Failed to compute slo_status for #{event_class.name}: #{e.message}"
|
|
121
|
-
)
|
|
122
|
-
nil
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
# Emit SLO metric to Yabeda/Prometheus.
|
|
126
|
-
#
|
|
127
|
-
# @param event_class [Class] Event class
|
|
128
|
-
# @param slo_status [String] 'success' or 'failure'
|
|
129
|
-
# @param payload [Hash] Event payload
|
|
130
|
-
# @return [void]
|
|
131
|
-
def emit_slo_metric(event_class, slo_status, payload)
|
|
132
|
-
labels = build_slo_labels(event_class, slo_status, payload)
|
|
133
|
-
|
|
134
|
-
E11y::Metrics.increment(:slo_event_result_total, labels)
|
|
135
|
-
rescue StandardError => e
|
|
136
|
-
E11y.logger.error(
|
|
137
|
-
"[E11y::Middleware::SLO] Failed to emit SLO metric for #{event_class.name}: #{e.message}"
|
|
138
|
-
)
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
# Build metric labels for SLO.
|
|
142
|
-
#
|
|
143
|
-
# @param event_class [Class] Event class
|
|
144
|
-
# @param slo_status [String] 'success' or 'failure'
|
|
145
|
-
# @param payload [Hash] Event payload
|
|
146
|
-
# @return [Hash] Metric labels
|
|
147
|
-
def build_slo_labels(event_class, slo_status, payload)
|
|
148
|
-
labels = {
|
|
149
|
-
event_name: event_class.event_name,
|
|
150
|
-
slo_status: slo_status
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
# Add custom SLO name if configured
|
|
154
|
-
if event_class.slo_config.contributes_to
|
|
155
|
-
labels[:slo_name] = event_class.slo_config.contributes_to
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
# Add group_by field if configured
|
|
159
|
-
if event_class.slo_config.group_by_field
|
|
160
|
-
field = event_class.slo_config.group_by_field
|
|
161
|
-
labels[:group_by] = payload[field].to_s if payload[field]
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
labels
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
end
|
|
168
|
-
end
|