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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.rubocop.yml +20 -0
  4. data/CHANGELOG.md +151 -13
  5. data/README.md +1138 -104
  6. data/RELEASE.md +254 -0
  7. data/Rakefile +377 -0
  8. data/benchmarks/OPTIMIZATION.md +246 -0
  9. data/benchmarks/README.md +103 -0
  10. data/benchmarks/allocation_profiling.rb +253 -0
  11. data/benchmarks/e11y_benchmarks.rb +447 -0
  12. data/benchmarks/ruby_baseline_allocations.rb +175 -0
  13. data/benchmarks/run_all.rb +9 -21
  14. data/docs/00-ICP-AND-TIMELINE.md +2 -2
  15. data/docs/ADR-001-architecture.md +1 -1
  16. data/docs/ADR-004-adapter-architecture.md +247 -0
  17. data/docs/ADR-009-cost-optimization.md +231 -115
  18. data/docs/ADR-017-multi-rails-compatibility.md +103 -0
  19. data/docs/ADR-INDEX.md +99 -0
  20. data/docs/CONTRIBUTING.md +312 -0
  21. data/docs/IMPLEMENTATION_PLAN.md +1 -1
  22. data/docs/QUICK-START.md +0 -6
  23. data/docs/use_cases/UC-019-retention-based-routing.md +584 -0
  24. data/e11y.gemspec +28 -17
  25. data/lib/e11y/adapters/adaptive_batcher.rb +3 -0
  26. data/lib/e11y/adapters/audit_encrypted.rb +10 -4
  27. data/lib/e11y/adapters/base.rb +15 -0
  28. data/lib/e11y/adapters/file.rb +4 -1
  29. data/lib/e11y/adapters/in_memory.rb +6 -0
  30. data/lib/e11y/adapters/loki.rb +9 -0
  31. data/lib/e11y/adapters/otel_logs.rb +11 -9
  32. data/lib/e11y/adapters/sentry.rb +9 -0
  33. data/lib/e11y/adapters/yabeda.rb +54 -10
  34. data/lib/e11y/buffers.rb +8 -8
  35. data/lib/e11y/console.rb +52 -60
  36. data/lib/e11y/event/base.rb +75 -10
  37. data/lib/e11y/event/value_sampling_config.rb +10 -4
  38. data/lib/e11y/events/rails/http/request.rb +1 -1
  39. data/lib/e11y/instruments/active_job.rb +6 -3
  40. data/lib/e11y/instruments/rails_instrumentation.rb +51 -28
  41. data/lib/e11y/instruments/sidekiq.rb +7 -7
  42. data/lib/e11y/logger/bridge.rb +24 -54
  43. data/lib/e11y/metrics/cardinality_protection.rb +257 -12
  44. data/lib/e11y/metrics/cardinality_tracker.rb +17 -0
  45. data/lib/e11y/metrics/registry.rb +6 -2
  46. data/lib/e11y/metrics/relabeling.rb +0 -56
  47. data/lib/e11y/metrics.rb +6 -1
  48. data/lib/e11y/middleware/audit_signing.rb +12 -9
  49. data/lib/e11y/middleware/pii_filter.rb +18 -10
  50. data/lib/e11y/middleware/request.rb +10 -4
  51. data/lib/e11y/middleware/routing.rb +117 -90
  52. data/lib/e11y/middleware/sampling.rb +47 -28
  53. data/lib/e11y/middleware/trace_context.rb +40 -11
  54. data/lib/e11y/middleware/validation.rb +20 -2
  55. data/lib/e11y/middleware/versioning.rb +1 -1
  56. data/lib/e11y/pii.rb +7 -7
  57. data/lib/e11y/railtie.rb +24 -20
  58. data/lib/e11y/reliability/circuit_breaker.rb +3 -0
  59. data/lib/e11y/reliability/dlq/file_storage.rb +16 -5
  60. data/lib/e11y/reliability/dlq/filter.rb +3 -0
  61. data/lib/e11y/reliability/retry_handler.rb +4 -0
  62. data/lib/e11y/sampling/error_spike_detector.rb +16 -5
  63. data/lib/e11y/sampling/load_monitor.rb +13 -4
  64. data/lib/e11y/self_monitoring/reliability_monitor.rb +3 -0
  65. data/lib/e11y/version.rb +1 -1
  66. data/lib/e11y.rb +86 -9
  67. metadata +83 -38
  68. data/docs/use_cases/UC-019-tiered-storage-migration.md +0 -562
  69. data/lib/e11y/middleware/pii_filtering.rb +0 -280
  70. 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
@@ -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
@@ -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.close if @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
  #
@@ -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 severity mapping
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: OpenTelemetry::SDK::Logs::Severity::DEBUG,
64
- info: OpenTelemetry::SDK::Logs::Severity::INFO,
65
- success: OpenTelemetry::SDK::Logs::Severity::INFO, # OTel has no "success"
66
- warn: OpenTelemetry::SDK::Logs::Severity::WARN,
67
- error: OpenTelemetry::SDK::Logs::Severity::ERROR,
68
- fatal: OpenTelemetry::SDK::Logs::Severity::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
- @logger_provider && @logger
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] || OpenTelemetry::SDK::Logs::Severity::INFO
162
+ SEVERITY_MAPPING[severity] || 9 # Default to INFO
161
163
  end
162
164
 
163
165
  # Build OTel attributes from E11y payload
@@ -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
@@ -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
- forbidden_labels: config.fetch(:forbidden_labels, [])
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
- register_metrics_from_registry! if config.fetch(:auto_register, true)
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}", error: e.class.name)
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).observe(value, safe_labels)
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}", error: e.class.name)
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(value, safe_labels)
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}", error: e.class.name)
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.debug("Could not register Yabeda metric #{name}: #{e.message}")
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).observe(value, safe_labels)
374
+ ::Yabeda.e11y.send(metric_name).measure(safe_labels, value)
333
375
  when :gauge
334
- ::Yabeda.e11y.send(metric_name).set(value, safe_labels)
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.singleton_class.class_eval do
31
- # Show E11y statistics
32
- # @return [Hash] Statistics hash
33
- def stats
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
- enabled: E11y.config.enabled,
36
- environment: E11y.config.environment,
37
- service_name: E11y.config.service_name,
38
- adapters: E11y::Adapters::Registry.all.map do |a|
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
- # Track a test event
53
- # @return [void]
54
- def test_event
55
- # TODO: Implement test event tracking
56
- # For now, just print a message
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
- # List all registered adapters
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
- # Reset buffers (clear all buffered events)
84
- # @return [void]
85
- def reset!
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
- private
85
+ def buffer_info
86
+ { size: buffer_size }
87
+ end
92
88
 
93
- # Get current buffer size
94
- # @return [Integer] Number of buffered events
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