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
|
@@ -28,6 +28,8 @@ module E11y
|
|
|
28
28
|
#
|
|
29
29
|
# @see ADR-006 §4.0 Audit Trail Security
|
|
30
30
|
# @see UC-012 Audit Trail
|
|
31
|
+
# rubocop:disable Metrics/ClassLength
|
|
32
|
+
# Audit adapter contains encryption/decryption logic as cohesive unit
|
|
31
33
|
class AuditEncrypted < Base
|
|
32
34
|
# AES-256-GCM cipher
|
|
33
35
|
CIPHER = "aes-256-gcm"
|
|
@@ -125,6 +127,8 @@ module E11y
|
|
|
125
127
|
#
|
|
126
128
|
# @param encrypted [Hash] Encrypted data with nonce and tag
|
|
127
129
|
# @return [Hash] Decrypted event data
|
|
130
|
+
# rubocop:disable Metrics/AbcSize
|
|
131
|
+
# Cryptographic operations require multiple steps for secure decryption
|
|
128
132
|
def decrypt_event(encrypted)
|
|
129
133
|
cipher = OpenSSL::Cipher.new(CIPHER)
|
|
130
134
|
cipher.decrypt
|
|
@@ -137,6 +141,7 @@ module E11y
|
|
|
137
141
|
|
|
138
142
|
JSON.parse(plaintext, symbolize_names: true)
|
|
139
143
|
end
|
|
144
|
+
# rubocop:enable Metrics/AbcSize
|
|
140
145
|
|
|
141
146
|
# Write encrypted data to storage
|
|
142
147
|
#
|
|
@@ -185,7 +190,7 @@ module E11y
|
|
|
185
190
|
#
|
|
186
191
|
# @return [Boolean]
|
|
187
192
|
def production?
|
|
188
|
-
defined?(Rails) && Rails.env.production?
|
|
193
|
+
defined?(::Rails) && ::Rails.env.production?
|
|
189
194
|
end
|
|
190
195
|
|
|
191
196
|
# Ensure storage directory exists
|
|
@@ -212,7 +217,7 @@ module E11y
|
|
|
212
217
|
# @return [String] Encryption key
|
|
213
218
|
def default_encryption_key
|
|
214
219
|
key = ENV.fetch("E11Y_AUDIT_ENCRYPTION_KEY") do
|
|
215
|
-
if defined?(Rails) && Rails.env.production?
|
|
220
|
+
if defined?(::Rails) && ::Rails.env.production?
|
|
216
221
|
raise E11y::Error, "E11Y_AUDIT_ENCRYPTION_KEY must be set in production"
|
|
217
222
|
end
|
|
218
223
|
|
|
@@ -228,12 +233,13 @@ module E11y
|
|
|
228
233
|
#
|
|
229
234
|
# @return [String] Storage path
|
|
230
235
|
def default_storage_path
|
|
231
|
-
if defined?(Rails)
|
|
232
|
-
Rails.root.join("log", "audit").to_s
|
|
236
|
+
if defined?(::Rails) && ::Rails.root
|
|
237
|
+
::Rails.root.join("log", "audit").to_s
|
|
233
238
|
else
|
|
234
239
|
::File.join(Dir.pwd, "log", "audit")
|
|
235
240
|
end
|
|
236
241
|
end
|
|
237
242
|
end
|
|
243
|
+
# rubocop:enable Metrics/ClassLength
|
|
238
244
|
end
|
|
239
245
|
end
|
data/lib/e11y/adapters/base.rb
CHANGED
|
@@ -47,6 +47,8 @@ module E11y
|
|
|
47
47
|
# end
|
|
48
48
|
#
|
|
49
49
|
# @see ADR-004 Section 3.1 (Base Adapter Contract)
|
|
50
|
+
# rubocop:disable Metrics/ClassLength
|
|
51
|
+
# Base adapter is a foundational class with core adapter functionality
|
|
50
52
|
class Base
|
|
51
53
|
attr_reader :config
|
|
52
54
|
|
|
@@ -103,6 +105,8 @@ module E11y
|
|
|
103
105
|
# @param event_data [Hash] Event payload
|
|
104
106
|
# @return [Boolean] true on success
|
|
105
107
|
# @raise [RetryExhaustedError, CircuitOpenError] if fail_on_error=true
|
|
108
|
+
# rubocop:disable Metrics/MethodLength
|
|
109
|
+
# Core reliability logic with retry and circuit breaker - should stay as cohesive unit
|
|
106
110
|
def write_with_reliability(event_data)
|
|
107
111
|
return write(event_data) unless @reliability_enabled
|
|
108
112
|
|
|
@@ -125,6 +129,7 @@ module E11y
|
|
|
125
129
|
handle_reliability_error(event_data, e, :circuit_open)
|
|
126
130
|
end
|
|
127
131
|
end
|
|
132
|
+
# rubocop:enable Metrics/MethodLength
|
|
128
133
|
|
|
129
134
|
# Write a batch of events (preferred for performance)
|
|
130
135
|
#
|
|
@@ -301,6 +306,8 @@ module E11y
|
|
|
301
306
|
# def retriable_error?(error)
|
|
302
307
|
# super || error.is_a?(CustomTransientError)
|
|
303
308
|
# end
|
|
309
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
310
|
+
# This method checks many different error types for retryability - splitting would reduce clarity
|
|
304
311
|
def retriable_error?(error)
|
|
305
312
|
# Network timeout errors
|
|
306
313
|
return true if error.is_a?(Timeout::Error)
|
|
@@ -327,6 +334,7 @@ module E11y
|
|
|
327
334
|
|
|
328
335
|
false
|
|
329
336
|
end
|
|
337
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
330
338
|
|
|
331
339
|
# Calculate exponential backoff delay with jitter
|
|
332
340
|
#
|
|
@@ -373,6 +381,8 @@ module E11y
|
|
|
373
381
|
# end
|
|
374
382
|
#
|
|
375
383
|
# @see ADR-004 Section 7.2 (Circuit Breaker)
|
|
384
|
+
# rubocop:disable Metrics/MethodLength
|
|
385
|
+
# Circuit breaker state machine logic should stay as cohesive unit
|
|
376
386
|
def with_circuit_breaker(failure_threshold: 5, timeout: 60)
|
|
377
387
|
init_circuit_breaker! unless @circuit_state
|
|
378
388
|
|
|
@@ -397,6 +407,7 @@ module E11y
|
|
|
397
407
|
raise
|
|
398
408
|
end
|
|
399
409
|
end
|
|
410
|
+
# rubocop:enable Metrics/MethodLength
|
|
400
411
|
|
|
401
412
|
# Initialize circuit breaker state
|
|
402
413
|
#
|
|
@@ -487,6 +498,8 @@ module E11y
|
|
|
487
498
|
# @raise [StandardError] Re-raises if fail_on_error=true
|
|
488
499
|
#
|
|
489
500
|
# @api private
|
|
501
|
+
# rubocop:disable Naming/PredicateMethod
|
|
502
|
+
# This is an action method (handle error), not a predicate (is error handled?)
|
|
490
503
|
def handle_reliability_error(event_data, error, reason)
|
|
491
504
|
# Save to DLQ if filter allows
|
|
492
505
|
save_to_dlq_if_needed(event_data, error, reason)
|
|
@@ -503,6 +516,7 @@ module E11y
|
|
|
503
516
|
# TODO: Track metric e11y.event.tracking_failed_silent
|
|
504
517
|
false
|
|
505
518
|
end
|
|
519
|
+
# rubocop:enable Naming/PredicateMethod
|
|
506
520
|
|
|
507
521
|
# Save event to DLQ if filter allows.
|
|
508
522
|
#
|
|
@@ -573,6 +587,7 @@ module E11y
|
|
|
573
587
|
warn "[E11y] Self-monitoring error: #{e.message}"
|
|
574
588
|
end
|
|
575
589
|
end
|
|
590
|
+
# rubocop:enable Metrics/ClassLength
|
|
576
591
|
|
|
577
592
|
# Circuit breaker open error
|
|
578
593
|
class CircuitOpenError < Error; end
|
data/lib/e11y/adapters/file.rb
CHANGED
|
@@ -31,6 +31,8 @@ module E11y
|
|
|
31
31
|
# :file_logger,
|
|
32
32
|
# E11y::Adapters::File.new(path: "log/events.log")
|
|
33
33
|
# )
|
|
34
|
+
# rubocop:disable Metrics/ClassLength
|
|
35
|
+
# File adapter contains file rotation and buffering logic as cohesive unit
|
|
34
36
|
class File < Base
|
|
35
37
|
# Default maximum file size before rotation (100MB)
|
|
36
38
|
DEFAULT_MAX_SIZE = 100 * 1024 * 1024
|
|
@@ -191,7 +193,7 @@ module E11y
|
|
|
191
193
|
|
|
192
194
|
# Perform actual file rotation
|
|
193
195
|
def perform_rotation!
|
|
194
|
-
@file
|
|
196
|
+
@file&.close
|
|
195
197
|
|
|
196
198
|
timestamp = Time.now.strftime("%Y%m%d-%H%M%S")
|
|
197
199
|
rotated_path = "#{@path}.#{timestamp}"
|
|
@@ -220,5 +222,6 @@ module E11y
|
|
|
220
222
|
warn "E11y File adapter compression error: #{e.message}"
|
|
221
223
|
end
|
|
222
224
|
end
|
|
225
|
+
# rubocop:enable Metrics/ClassLength
|
|
223
226
|
end
|
|
224
227
|
end
|
|
@@ -80,6 +80,8 @@ module E11y
|
|
|
80
80
|
#
|
|
81
81
|
# @param event_data [Hash] Event payload
|
|
82
82
|
# @return [Boolean] true on success
|
|
83
|
+
# rubocop:disable Naming/PredicateMethod
|
|
84
|
+
# This is an action method (write event), not a predicate (is written?)
|
|
83
85
|
def write(event_data)
|
|
84
86
|
@mutex.synchronize do
|
|
85
87
|
@events << event_data
|
|
@@ -87,11 +89,14 @@ module E11y
|
|
|
87
89
|
end
|
|
88
90
|
true
|
|
89
91
|
end
|
|
92
|
+
# rubocop:enable Naming/PredicateMethod
|
|
90
93
|
|
|
91
94
|
# Write batch of events to memory
|
|
92
95
|
#
|
|
93
96
|
# @param events [Array<Hash>] Array of event payloads
|
|
94
97
|
# @return [Boolean] true on success
|
|
98
|
+
# rubocop:disable Naming/PredicateMethod
|
|
99
|
+
# This is an action method (write batch), not a predicate (is written?)
|
|
95
100
|
def write_batch(events)
|
|
96
101
|
@mutex.synchronize do
|
|
97
102
|
@events.concat(events)
|
|
@@ -100,6 +105,7 @@ module E11y
|
|
|
100
105
|
end
|
|
101
106
|
true
|
|
102
107
|
end
|
|
108
|
+
# rubocop:enable Naming/PredicateMethod
|
|
103
109
|
|
|
104
110
|
# Clear all stored events and batches
|
|
105
111
|
#
|
data/lib/e11y/adapters/loki.rb
CHANGED
|
@@ -59,6 +59,8 @@ module E11y
|
|
|
59
59
|
#
|
|
60
60
|
# @see https://grafana.com/docs/loki/latest/api/#push-log-entries-to-loki
|
|
61
61
|
# @see ADR-009 §8 (C04 Resolution - Universal Cardinality Protection)
|
|
62
|
+
# rubocop:disable Metrics/ClassLength
|
|
63
|
+
# Loki adapter contains HTTP client, batching, and Loki-specific formatting logic
|
|
62
64
|
class Loki < Base
|
|
63
65
|
# Default batch size (events)
|
|
64
66
|
DEFAULT_BATCH_SIZE = 100
|
|
@@ -82,6 +84,8 @@ module E11y
|
|
|
82
84
|
# @option config [String] :tenant_id (nil) Loki tenant ID (X-Scope-OrgID header)
|
|
83
85
|
# @option config [Boolean] :enable_cardinality_protection (false) Enable cardinality protection for labels (C04)
|
|
84
86
|
# @option config [Integer] :max_label_cardinality (100) Max unique values per label when protection enabled
|
|
87
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
88
|
+
# Adapter initialization requires many instance variable assignments
|
|
85
89
|
def initialize(config = {})
|
|
86
90
|
@url = config[:url]
|
|
87
91
|
@labels = config.fetch(:labels, {})
|
|
@@ -108,6 +112,7 @@ module E11y
|
|
|
108
112
|
|
|
109
113
|
build_connection!
|
|
110
114
|
end
|
|
115
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
111
116
|
|
|
112
117
|
# Write a single event to buffer
|
|
113
118
|
#
|
|
@@ -189,6 +194,8 @@ module E11y
|
|
|
189
194
|
#
|
|
190
195
|
# @see ADR-004 Section 7.1 (Retry Policy via gem-level middleware)
|
|
191
196
|
# @see ADR-004 Section 6.1 (Connection pooling via HTTP client)
|
|
197
|
+
# rubocop:disable Metrics/MethodLength
|
|
198
|
+
# HTTP client configuration requires detailed retry and connection settings
|
|
192
199
|
def build_connection!
|
|
193
200
|
@connection = Faraday.new(url: @url) do |f|
|
|
194
201
|
# Retry middleware (exponential backoff: 1s, 2s, 4s)
|
|
@@ -211,6 +218,7 @@ module E11y
|
|
|
211
218
|
f.adapter Faraday.default_adapter
|
|
212
219
|
end
|
|
213
220
|
end
|
|
221
|
+
# rubocop:enable Metrics/MethodLength
|
|
214
222
|
|
|
215
223
|
# Check if buffer should be flushed
|
|
216
224
|
def flush_if_needed!
|
|
@@ -329,5 +337,6 @@ module E11y
|
|
|
329
337
|
headers
|
|
330
338
|
end
|
|
331
339
|
end
|
|
340
|
+
# rubocop:enable Metrics/ClassLength
|
|
332
341
|
end
|
|
333
342
|
end
|
|
@@ -58,14 +58,16 @@ module E11y
|
|
|
58
58
|
# @see ADR-007 for OpenTelemetry integration architecture
|
|
59
59
|
# @see UC-008 for use cases
|
|
60
60
|
class OTelLogs < Base
|
|
61
|
-
# E11y severity → OTel
|
|
61
|
+
# E11y severity → OTel severity_number mapping
|
|
62
|
+
# See: https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-severitynumber
|
|
63
|
+
# Severity numbers: TRACE=1, DEBUG=5, INFO=9, WARN=13, ERROR=17, FATAL=21
|
|
62
64
|
SEVERITY_MAPPING = {
|
|
63
|
-
debug:
|
|
64
|
-
info:
|
|
65
|
-
success:
|
|
66
|
-
warn:
|
|
67
|
-
error:
|
|
68
|
-
fatal:
|
|
65
|
+
debug: 5, # DEBUG
|
|
66
|
+
info: 9, # INFO
|
|
67
|
+
success: 9, # INFO (OTel has no "success" level)
|
|
68
|
+
warn: 13, # WARN
|
|
69
|
+
error: 17, # ERROR
|
|
70
|
+
fatal: 21 # FATAL
|
|
69
71
|
}.freeze
|
|
70
72
|
|
|
71
73
|
# Default baggage allowlist (safe keys that don't contain PII)
|
|
@@ -108,7 +110,7 @@ module E11y
|
|
|
108
110
|
#
|
|
109
111
|
# @return [Boolean] true if OTel SDK available and configured
|
|
110
112
|
def healthy?
|
|
111
|
-
|
|
113
|
+
!@logger_provider.nil? && !@logger.nil?
|
|
112
114
|
end
|
|
113
115
|
|
|
114
116
|
# Adapter capabilities
|
|
@@ -157,7 +159,7 @@ module E11y
|
|
|
157
159
|
# @param severity [Symbol] E11y severity (:debug, :info, etc.)
|
|
158
160
|
# @return [Integer] OTel severity number
|
|
159
161
|
def map_severity(severity)
|
|
160
|
-
SEVERITY_MAPPING[severity] ||
|
|
162
|
+
SEVERITY_MAPPING[severity] || 9 # Default to INFO
|
|
161
163
|
end
|
|
162
164
|
|
|
163
165
|
# Build OTel attributes from E11y payload
|
data/lib/e11y/adapters/sentry.rb
CHANGED
|
@@ -40,6 +40,8 @@ module E11y
|
|
|
40
40
|
# )
|
|
41
41
|
#
|
|
42
42
|
# @see https://docs.sentry.io/platforms/ruby/
|
|
43
|
+
# rubocop:disable Metrics/ClassLength
|
|
44
|
+
# Sentry adapter contains error transformation and context enrichment logic
|
|
43
45
|
class Sentry < Base
|
|
44
46
|
# Severity levels in order
|
|
45
47
|
SEVERITY_LEVELS = %i[debug info success warn error fatal].freeze
|
|
@@ -153,6 +155,8 @@ module E11y
|
|
|
153
155
|
# Send error to Sentry
|
|
154
156
|
#
|
|
155
157
|
# @param event_data [Hash] Event data
|
|
158
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
159
|
+
# Sentry scope configuration requires multiple context enrichment steps
|
|
156
160
|
def send_error_to_sentry(event_data)
|
|
157
161
|
::Sentry.with_scope do |scope|
|
|
158
162
|
# Set tags
|
|
@@ -183,6 +187,7 @@ module E11y
|
|
|
183
187
|
end
|
|
184
188
|
end
|
|
185
189
|
end
|
|
190
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
186
191
|
|
|
187
192
|
# Send breadcrumb to Sentry
|
|
188
193
|
#
|
|
@@ -215,6 +220,8 @@ module E11y
|
|
|
215
220
|
#
|
|
216
221
|
# @param severity [Symbol] E11y severity
|
|
217
222
|
# @return [Symbol] Sentry level
|
|
223
|
+
# rubocop:disable Lint/DuplicateBranch
|
|
224
|
+
# Multiple severity levels intentionally map to :info (info, success, unknown)
|
|
218
225
|
def sentry_level(severity)
|
|
219
226
|
case severity
|
|
220
227
|
when :debug then :debug
|
|
@@ -225,6 +232,8 @@ module E11y
|
|
|
225
232
|
else :info
|
|
226
233
|
end
|
|
227
234
|
end
|
|
235
|
+
# rubocop:enable Lint/DuplicateBranch
|
|
228
236
|
end
|
|
237
|
+
# rubocop:enable Metrics/ClassLength
|
|
229
238
|
end
|
|
230
239
|
end
|
data/lib/e11y/adapters/yabeda.rb
CHANGED
|
@@ -44,23 +44,33 @@ module E11y
|
|
|
44
44
|
#
|
|
45
45
|
# @see ADR-002 Metrics & Yabeda Integration
|
|
46
46
|
# @see UC-003 Pattern-Based Metrics
|
|
47
|
+
# rubocop:disable Metrics/ClassLength
|
|
48
|
+
# Yabeda adapter contains metrics registration and update logic as cohesive unit
|
|
47
49
|
class Yabeda < Base
|
|
48
50
|
# Initialize Yabeda adapter
|
|
49
51
|
#
|
|
50
52
|
# @param config [Hash] Configuration options
|
|
51
53
|
# @option config [Integer] :cardinality_limit (1000) Max unique values per label per metric
|
|
52
54
|
# @option config [Array<Symbol>] :forbidden_labels ([]) Additional labels to denylist
|
|
55
|
+
# @option config [Symbol] :overflow_strategy (:drop) Strategy on overflow - :drop, :alert, or :relabel
|
|
53
56
|
# @option config [Boolean] :auto_register (true) Automatically register metrics from Registry
|
|
54
57
|
def initialize(config = {})
|
|
55
58
|
super
|
|
56
59
|
|
|
57
60
|
@cardinality_protection = E11y::Metrics::CardinalityProtection.new(
|
|
58
61
|
cardinality_limit: config.fetch(:cardinality_limit, 1000),
|
|
59
|
-
|
|
62
|
+
additional_denylist: config.fetch(:forbidden_labels, []),
|
|
63
|
+
overflow_strategy: config.fetch(:overflow_strategy, :drop)
|
|
60
64
|
)
|
|
61
65
|
|
|
62
66
|
# Auto-register metrics from Registry
|
|
63
|
-
|
|
67
|
+
return unless config.fetch(:auto_register, true)
|
|
68
|
+
|
|
69
|
+
register_metrics_from_registry!
|
|
70
|
+
|
|
71
|
+
# Apply configuration in non-Rails environments (Rails does this automatically)
|
|
72
|
+
# In tests, Yabeda.configure! should be called explicitly in before blocks
|
|
73
|
+
apply_yabeda_configuration!
|
|
64
74
|
end
|
|
65
75
|
|
|
66
76
|
# Write a single event to Yabeda
|
|
@@ -144,7 +154,7 @@ module E11y
|
|
|
144
154
|
# Update Yabeda metric
|
|
145
155
|
::Yabeda.e11y.send(name).increment(safe_labels, by: value)
|
|
146
156
|
rescue StandardError => e
|
|
147
|
-
E11y.logger.warn("Failed to increment Yabeda metric #{name}: #{e.message}"
|
|
157
|
+
E11y.logger.warn("Failed to increment Yabeda metric #{name}: #{e.message}")
|
|
148
158
|
end
|
|
149
159
|
|
|
150
160
|
# Track a histogram metric (for E11y::Metrics facade).
|
|
@@ -164,9 +174,9 @@ module E11y
|
|
|
164
174
|
register_metric_if_needed(name, :histogram, safe_labels.keys, buckets: buckets)
|
|
165
175
|
|
|
166
176
|
# Update Yabeda metric
|
|
167
|
-
::Yabeda.e11y.send(name).
|
|
177
|
+
::Yabeda.e11y.send(name).measure(safe_labels, value)
|
|
168
178
|
rescue StandardError => e
|
|
169
|
-
E11y.logger.warn("Failed to observe Yabeda histogram #{name}: #{e.message}"
|
|
179
|
+
E11y.logger.warn("Failed to observe Yabeda histogram #{name}: #{e.message}")
|
|
170
180
|
end
|
|
171
181
|
|
|
172
182
|
# Track a gauge metric (for E11y::Metrics facade).
|
|
@@ -185,9 +195,9 @@ module E11y
|
|
|
185
195
|
register_metric_if_needed(name, :gauge, safe_labels.keys)
|
|
186
196
|
|
|
187
197
|
# Update Yabeda metric
|
|
188
|
-
::Yabeda.e11y.send(name).set(
|
|
198
|
+
::Yabeda.e11y.send(name).set(safe_labels, value)
|
|
189
199
|
rescue StandardError => e
|
|
190
|
-
E11y.logger.warn("Failed to set Yabeda gauge #{name}: #{e.message}"
|
|
200
|
+
E11y.logger.warn("Failed to set Yabeda gauge #{name}: #{e.message}")
|
|
191
201
|
end
|
|
192
202
|
|
|
193
203
|
# Validate configuration
|
|
@@ -232,6 +242,27 @@ module E11y
|
|
|
232
242
|
|
|
233
243
|
private
|
|
234
244
|
|
|
245
|
+
# Apply Yabeda configuration (smart detection of environment)
|
|
246
|
+
#
|
|
247
|
+
# In Rails environments, configuration is applied automatically via Railtie.
|
|
248
|
+
# In non-Rails environments (e.g., Sinatra, standalone Ruby), we apply it here.
|
|
249
|
+
# In test environments, configuration should be applied explicitly in test setup.
|
|
250
|
+
#
|
|
251
|
+
# @return [void]
|
|
252
|
+
# @api private
|
|
253
|
+
def apply_yabeda_configuration!
|
|
254
|
+
# Don't auto-apply in Rails - Rails will call configure! via Railtie
|
|
255
|
+
return if defined?(::Rails)
|
|
256
|
+
|
|
257
|
+
# Don't auto-apply if already configured
|
|
258
|
+
return if ::Yabeda.configured?
|
|
259
|
+
|
|
260
|
+
# Apply configuration (non-Rails environments only)
|
|
261
|
+
::Yabeda.configure!
|
|
262
|
+
rescue StandardError => e
|
|
263
|
+
E11y.logger.debug("Could not apply Yabeda configuration: #{e.message}")
|
|
264
|
+
end
|
|
265
|
+
|
|
235
266
|
# Register metrics from Registry into Yabeda
|
|
236
267
|
#
|
|
237
268
|
# This is called during initialization if auto_register is true.
|
|
@@ -251,6 +282,8 @@ module E11y
|
|
|
251
282
|
#
|
|
252
283
|
# @param metric_config [Hash] Metric configuration from Registry
|
|
253
284
|
# @return [void]
|
|
285
|
+
# rubocop:disable Metrics/MethodLength
|
|
286
|
+
# Metric registration requires case/when for different metric types
|
|
254
287
|
def register_yabeda_metric(metric_config)
|
|
255
288
|
metric_name = metric_config[:name]
|
|
256
289
|
metric_type = metric_config[:type]
|
|
@@ -276,6 +309,7 @@ module E11y
|
|
|
276
309
|
# Metric might already be registered - that's OK
|
|
277
310
|
warn "E11y Yabeda: Could not register metric #{metric_name}: #{e.message}"
|
|
278
311
|
end
|
|
312
|
+
# rubocop:enable Metrics/MethodLength
|
|
279
313
|
|
|
280
314
|
# Register a metric if it doesn't exist yet (for direct metric calls).
|
|
281
315
|
#
|
|
@@ -285,6 +319,8 @@ module E11y
|
|
|
285
319
|
# @param buckets [Array<Numeric>, nil] Optional histogram buckets
|
|
286
320
|
# @return [void]
|
|
287
321
|
# @api private
|
|
322
|
+
# rubocop:disable Metrics/MethodLength
|
|
323
|
+
# Metric registration requires case/when for different metric types
|
|
288
324
|
def register_metric_if_needed(name, type, tags, buckets: nil)
|
|
289
325
|
# Check if metric already exists
|
|
290
326
|
return if ::Yabeda.metrics.key?(:"e11y_#{name}")
|
|
@@ -304,16 +340,22 @@ module E11y
|
|
|
304
340
|
end
|
|
305
341
|
end
|
|
306
342
|
end
|
|
343
|
+
|
|
344
|
+
# Apply configuration for runtime-registered metrics (non-Rails environments)
|
|
345
|
+
apply_yabeda_configuration!
|
|
307
346
|
rescue StandardError => e
|
|
308
347
|
# Metric might already be registered - that's OK
|
|
309
|
-
E11y.logger.
|
|
348
|
+
E11y.logger.warn("Could not register Yabeda metric #{name}: #{e.message}")
|
|
310
349
|
end
|
|
350
|
+
# rubocop:enable Metrics/MethodLength
|
|
311
351
|
|
|
312
352
|
# Update a single metric based on event data
|
|
313
353
|
#
|
|
314
354
|
# @param metric_config [Hash] Metric configuration
|
|
315
355
|
# @param event_data [Hash] Event data
|
|
316
356
|
# @return [void]
|
|
357
|
+
# rubocop:disable Metrics/AbcSize
|
|
358
|
+
# Metric update requires multiple steps for label extraction and value handling
|
|
317
359
|
def update_metric(metric_config, event_data)
|
|
318
360
|
metric_name = metric_config[:name]
|
|
319
361
|
labels = extract_labels(metric_config, event_data)
|
|
@@ -329,13 +371,14 @@ module E11y
|
|
|
329
371
|
when :counter
|
|
330
372
|
::Yabeda.e11y.send(metric_name).increment(safe_labels)
|
|
331
373
|
when :histogram
|
|
332
|
-
::Yabeda.e11y.send(metric_name).
|
|
374
|
+
::Yabeda.e11y.send(metric_name).measure(safe_labels, value)
|
|
333
375
|
when :gauge
|
|
334
|
-
::Yabeda.e11y.send(metric_name).set(
|
|
376
|
+
::Yabeda.e11y.send(metric_name).set(safe_labels, value)
|
|
335
377
|
end
|
|
336
378
|
rescue StandardError => e
|
|
337
379
|
warn "E11y Yabeda: Error updating metric #{metric_name}: #{e.message}"
|
|
338
380
|
end
|
|
381
|
+
# rubocop:enable Metrics/AbcSize
|
|
339
382
|
|
|
340
383
|
# Extract labels from event data
|
|
341
384
|
#
|
|
@@ -366,5 +409,6 @@ module E11y
|
|
|
366
409
|
end
|
|
367
410
|
end
|
|
368
411
|
end
|
|
412
|
+
# rubocop:enable Metrics/ClassLength
|
|
369
413
|
end
|
|
370
414
|
end
|
data/lib/e11y/buffers.rb
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# E11y::Buffers module - Event buffering implementations
|
|
4
|
-
#
|
|
5
|
-
# This module contains buffer implementations for high-throughput event storage:
|
|
6
|
-
# - RingBuffer: Lock-free SPSC ring buffer (100K+ events/sec)
|
|
7
|
-
# - AdaptiveBuffer: Memory-aware buffer with backpressure (Phase 1.2.2)
|
|
8
|
-
#
|
|
9
|
-
# @see E11y::Buffers::RingBuffer
|
|
10
|
-
# @see ADR-001 §3.3 (Buffer Architecture)
|
|
11
3
|
module E11y
|
|
4
|
+
# Event buffering implementations
|
|
5
|
+
#
|
|
6
|
+
# This module contains buffer implementations for high-throughput event storage:
|
|
7
|
+
# - RingBuffer: Lock-free SPSC ring buffer (100K+ events/sec)
|
|
8
|
+
# - AdaptiveBuffer: Memory-aware buffer with backpressure (Phase 1.2.2)
|
|
9
|
+
#
|
|
10
|
+
# @see E11y::Buffers::RingBuffer
|
|
11
|
+
# @see ADR-001 §3.3 (Buffer Architecture)
|
|
12
12
|
module Buffers
|
|
13
13
|
end
|
|
14
14
|
end
|
data/lib/e11y/console.rb
CHANGED
|
@@ -27,75 +27,67 @@ module E11y
|
|
|
27
27
|
# Define helper methods on E11y module
|
|
28
28
|
# @return [void]
|
|
29
29
|
def self.define_helper_methods
|
|
30
|
-
E11y.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
E11y.extend(ConsoleHelpers)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Console helper methods module
|
|
34
|
+
module ConsoleHelpers
|
|
35
|
+
# Show E11y statistics
|
|
36
|
+
def stats
|
|
37
|
+
{
|
|
38
|
+
enabled: config.enabled,
|
|
39
|
+
environment: config.environment,
|
|
40
|
+
service_name: config.service_name,
|
|
41
|
+
adapters: adapters_info,
|
|
42
|
+
buffer: buffer_info
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Track a test event
|
|
47
|
+
def test_event
|
|
48
|
+
puts "✅ E11y test event would be tracked here"
|
|
49
|
+
puts " (Waiting for Events::Console::Test implementation)"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# List all registered event classes
|
|
53
|
+
def events
|
|
54
|
+
puts "📋 E11y events list"
|
|
55
|
+
puts " (Waiting for Event registry implementation)"
|
|
56
|
+
[]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# List all registered adapters
|
|
60
|
+
def adapters
|
|
61
|
+
Adapters::Registry.all.map do |adapter|
|
|
34
62
|
{
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
{
|
|
40
|
-
name: a.name,
|
|
41
|
-
class: a.class.name,
|
|
42
|
-
healthy: a.healthy?
|
|
43
|
-
}
|
|
44
|
-
end,
|
|
45
|
-
buffer: {
|
|
46
|
-
size: buffer_size,
|
|
47
|
-
max_size: E11y.config.buffer&.max_size
|
|
48
|
-
}
|
|
63
|
+
name: adapter.name,
|
|
64
|
+
class: adapter.class.name,
|
|
65
|
+
healthy: adapter.healthy?,
|
|
66
|
+
capabilities: adapter.capabilities
|
|
49
67
|
}
|
|
50
68
|
end
|
|
69
|
+
end
|
|
51
70
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
puts "✅ E11y test event would be tracked here"
|
|
58
|
-
puts " (Waiting for Events::Console::Test implementation)"
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# List all registered event classes
|
|
62
|
-
# @return [Array<String>] Event class names
|
|
63
|
-
def events
|
|
64
|
-
# TODO: Implement event registry
|
|
65
|
-
puts "📋 E11y events list"
|
|
66
|
-
puts " (Waiting for Event registry implementation)"
|
|
67
|
-
[]
|
|
68
|
-
end
|
|
71
|
+
# Reset buffers
|
|
72
|
+
def reset!
|
|
73
|
+
puts "✅ E11y buffers would be cleared here"
|
|
74
|
+
puts " (Waiting for Buffer#clear! implementation)"
|
|
75
|
+
end
|
|
69
76
|
|
|
70
|
-
|
|
71
|
-
# @return [Array<Hash>] Adapter details
|
|
72
|
-
def adapters
|
|
73
|
-
E11y::Adapters::Registry.all.map do |adapter|
|
|
74
|
-
{
|
|
75
|
-
name: adapter.name,
|
|
76
|
-
class: adapter.class.name,
|
|
77
|
-
healthy: adapter.healthy?,
|
|
78
|
-
capabilities: adapter.capabilities
|
|
79
|
-
}
|
|
80
|
-
end
|
|
81
|
-
end
|
|
77
|
+
private
|
|
82
78
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
# TODO: Implement buffer clearing
|
|
87
|
-
puts "✅ E11y buffers would be cleared here"
|
|
88
|
-
puts " (Waiting for Buffer#clear! implementation)"
|
|
79
|
+
def adapters_info
|
|
80
|
+
Adapters::Registry.all.map do |a|
|
|
81
|
+
{ name: a.name, class: a.class.name, healthy: a.healthy? }
|
|
89
82
|
end
|
|
83
|
+
end
|
|
90
84
|
|
|
91
|
-
|
|
85
|
+
def buffer_info
|
|
86
|
+
{ size: buffer_size }
|
|
87
|
+
end
|
|
92
88
|
|
|
93
|
-
|
|
94
|
-
#
|
|
95
|
-
def buffer_size
|
|
96
|
-
# TODO: Implement buffer size tracking
|
|
97
|
-
0
|
|
98
|
-
end
|
|
89
|
+
def buffer_size
|
|
90
|
+
0 # TODO: Implement buffer size tracking
|
|
99
91
|
end
|
|
100
92
|
end
|
|
101
93
|
|