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
@@ -18,6 +18,8 @@ module E11y
18
18
  #
19
19
  # @see ADR-013 §4 (Dead Letter Queue)
20
20
  # @see UC-021 §3 (DLQ File Storage)
21
+ # rubocop:disable Metrics/ClassLength
22
+ # DLQ file storage is a cohesive unit handling event persistence, rotation, and querying
21
23
  class FileStorage
22
24
  # @param file_path [String] Path to DLQ file (default: log/e11y_dlq.jsonl)
23
25
  # @param max_file_size_mb [Integer] Maximum file size in MB before rotation (default: 100)
@@ -36,6 +38,8 @@ module E11y
36
38
  # @param event_data [Hash] Event data
37
39
  # @param metadata [Hash] Failure metadata (error, retry_count, adapter, etc.)
38
40
  # @return [String] Event ID (UUID)
41
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
42
+ # DLQ save requires building entry, writing, rotation, cleanup, and metrics
39
43
  def save(event_data, metadata: {})
40
44
  event_id = SecureRandom.uuid
41
45
  timestamp = Time.now.utc
@@ -61,6 +65,7 @@ module E11y
61
65
 
62
66
  event_id
63
67
  end
68
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
64
69
 
65
70
  # List DLQ entries with optional filters.
66
71
  #
@@ -68,6 +73,8 @@ module E11y
68
73
  # @param offset [Integer] Number of entries to skip
69
74
  # @param filters [Hash] Filter options (event_name, after, before)
70
75
  # @return [Array<Hash>] Array of DLQ entries
76
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
77
+ # DLQ listing requires file iteration, pagination, multiple filters, and error handling
71
78
  def list(limit: 100, offset: 0, filters: {})
72
79
  entries = []
73
80
 
@@ -93,10 +100,13 @@ module E11y
93
100
  increment_metric("e11y.dlq.parse_error", error: e.class.name)
94
101
  entries
95
102
  end
103
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
96
104
 
97
105
  # Get DLQ statistics.
98
106
  #
99
107
  # @return [Hash] Statistics (total_entries, file_size_mb, oldest_entry, newest_entry)
108
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
109
+ # DLQ stats requires reading file size, counting entries, extracting timestamps, and error handling
100
110
  def stats
101
111
  return default_stats unless File.exist?(@file_path)
102
112
 
@@ -124,6 +134,7 @@ module E11y
124
134
  increment_metric("e11y.dlq.stats_error", error: e.class.name)
125
135
  default_stats
126
136
  end
137
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
127
138
 
128
139
  # Replay single event from DLQ.
129
140
  #
@@ -171,22 +182,21 @@ module E11y
171
182
  #
172
183
  # @param event_id [String] Event ID to delete
173
184
  # @return [Boolean] true if deleted
185
+ # rubocop:disable Naming/PredicateMethod
186
+ # delete is an action method returning boolean status, not a predicate query
174
187
  def delete(_event_id)
175
188
  # TODO: Implement deletion (requires rewriting file)
176
189
  # For JSONL, deletion is expensive (requires full file rewrite)
177
190
  # Consider marking as deleted instead or using database
178
191
  false
179
192
  end
193
+ # rubocop:enable Naming/PredicateMethod
180
194
 
181
195
  private
182
196
 
183
197
  # Get default file path (log/e11y_dlq.jsonl).
184
198
  def default_file_path
185
- if defined?(Rails) && Rails.root
186
- Rails.root.join("log", "e11y_dlq.jsonl").to_s
187
- else
188
- File.join("log", "e11y_dlq.jsonl")
189
- end
199
+ ::Rails.root.join("log", "e11y_dlq.jsonl").to_s
190
200
  end
191
201
 
192
202
  # Ensure log directory exists.
@@ -272,6 +282,7 @@ module E11y
272
282
  # E11y::Metrics.increment(metric_name, tags)
273
283
  end
274
284
  end
285
+ # rubocop:enable Metrics/ClassLength
275
286
  end
276
287
  end
277
288
  end
@@ -48,6 +48,8 @@ module E11y
48
48
  #
49
49
  # @param event_data [Hash] Event data
50
50
  # @return [Boolean] true if event should be saved to DLQ
51
+ # rubocop:disable Metrics/MethodLength
52
+ # DLQ filter requires 4-priority decision tree with metrics tracking for each branch
51
53
  def should_save?(event_data)
52
54
  event_name = event_data[:event_name].to_s
53
55
  severity = event_data[:severity]
@@ -79,6 +81,7 @@ module E11y
79
81
  false
80
82
  end
81
83
  end
84
+ # rubocop:enable Metrics/MethodLength
82
85
 
83
86
  # Get filter statistics.
84
87
  #
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "securerandom"
4
+ require "timeout"
4
5
 
5
6
  module E11y
6
7
  module Reliability
@@ -64,6 +65,8 @@ module E11y
64
65
  # @yield Block to execute (adapter send)
65
66
  # @return [Object] Result of block execution
66
67
  # @raise [RetryExhaustedError] if all retries fail and fail_on_error is true
68
+ # rubocop:disable Metrics/MethodLength
69
+ # Retry logic requires error handling, retriability check, backoff calculation, and callbacks
67
70
  def with_retry(adapter:, event:)
68
71
  attempt = 0
69
72
 
@@ -100,6 +103,7 @@ module E11y
100
103
  end
101
104
  end
102
105
  end
106
+ # rubocop:enable Metrics/MethodLength
103
107
 
104
108
  private
105
109
 
@@ -29,6 +29,8 @@ module E11y
29
29
  # end
30
30
  #
31
31
  # detector.record_event(event_name: "payment.processed", severity: :error)
32
+ # rubocop:disable Metrics/ClassLength
33
+ # Error spike detector is a cohesive sliding window algorithm with state management
32
34
  class ErrorSpikeDetector
33
35
  # Default configuration
34
36
  DEFAULT_WINDOW = 60 # 60 seconds sliding window
@@ -64,6 +66,8 @@ module E11y
64
66
  # Check if currently in error spike state
65
67
  #
66
68
  # @return [Boolean] true if error spike detected
69
+ # rubocop:disable Metrics/MethodLength
70
+ # Error spike detection requires checking active spike, expiration, and new spike detection
67
71
  def error_spike?
68
72
  @mutex.synchronize do
69
73
  # Check if spike is still active (within spike_duration)
@@ -90,6 +94,7 @@ module E11y
90
94
  false
91
95
  end
92
96
  end
97
+ # rubocop:enable Metrics/MethodLength
93
98
 
94
99
  # Record an event for error rate tracking
95
100
  #
@@ -107,8 +112,9 @@ module E11y
107
112
  @error_events[event_name] << now
108
113
  @all_errors << now
109
114
 
110
- # Cleanup old events (outside window)
111
- cleanup_old_events(now)
115
+ # Cleanup old events periodically (every 50 errors) instead of on every error
116
+ # This reduces O() overhead significantly while keeping memory bounded
117
+ cleanup_old_events(now) if (@all_errors.size % 50).zero?
112
118
 
113
119
  # Update baseline (if not in spike)
114
120
  update_baseline(event_name) unless @spike_started_at
@@ -122,10 +128,12 @@ module E11y
122
128
  def current_error_rate(event_name = nil)
123
129
  @mutex.synchronize do
124
130
  now = Time.now
125
- cleanup_old_events(now)
131
+ cutoff = now - @window
126
132
 
127
133
  events = event_name ? @error_events[event_name] : @all_errors
128
- count = events.count { |ts| (now - ts) <= @window }
134
+ # Count events within window without cleanup
135
+ # Cleanup is handled periodically in record_event
136
+ count = events.count { |ts| ts >= cutoff }
129
137
 
130
138
  # Convert to per-minute rate
131
139
  (count.to_f / @window) * 60
@@ -186,8 +194,10 @@ module E11y
186
194
  # @return [Float] Errors per minute
187
195
  def current_error_rate_unsafe(event_name = nil)
188
196
  now = Time.now
197
+ cutoff = now - @window
189
198
  events = event_name ? @error_events[event_name] : @all_errors
190
- count = events.count { |ts| (now - ts) <= @window }
199
+ # Count within window without cleanup
200
+ count = events.count { |ts| ts >= cutoff }
191
201
  (count.to_f / @window) * 60
192
202
  end
193
203
 
@@ -221,5 +231,6 @@ module E11y
221
231
  @all_errors.reject! { |ts| ts < cutoff }
222
232
  end
223
233
  end
234
+ # rubocop:enable Metrics/ClassLength
224
235
  end
225
236
  end
@@ -65,8 +65,9 @@ module E11y
65
65
  now = Time.now
66
66
  @events << now
67
67
 
68
- # Cleanup old events (outside window)
69
- cleanup_old_events(now)
68
+ # Cleanup old events periodically (every 100 events) instead of on every event
69
+ # This reduces O() overhead significantly while keeping memory bounded
70
+ cleanup_old_events(now) if (@events.size % 100).zero?
70
71
  end
71
72
  end
72
73
 
@@ -76,9 +77,11 @@ module E11y
76
77
  def current_rate
77
78
  @mutex.synchronize do
78
79
  now = Time.now
79
- cleanup_old_events(now)
80
+ cutoff = now - @window
80
81
 
81
- count = @events.count { |ts| (now - ts) <= @window }
82
+ # Count events within window without cleanup
83
+ # Cleanup is handled periodically in record_event
84
+ count = @events.count { |ts| ts >= cutoff }
82
85
  count.to_f / @window
83
86
  end
84
87
  end
@@ -90,6 +93,8 @@ module E11y
90
93
  rate = current_rate
91
94
 
92
95
  # Check thresholds in descending order
96
+ # rubocop:disable Lint/DuplicateBranch
97
+ # Values between normal and high thresholds intentionally mapped to :high
93
98
  if rate >= @thresholds[:overload]
94
99
  :overload
95
100
  elsif rate >= @thresholds[:very_high]
@@ -101,11 +106,14 @@ module E11y
101
106
  else
102
107
  :normal
103
108
  end
109
+ # rubocop:enable Lint/DuplicateBranch
104
110
  end
105
111
 
106
112
  # Get recommended sample rate for current load
107
113
  #
108
114
  # @return [Float] Sample rate (0.0-1.0)
115
+ # rubocop:disable Style/HashLikeCase
116
+ # Case/when more readable for load level mapping with inline comments
109
117
  def recommended_sample_rate
110
118
  case load_level
111
119
  when :normal
@@ -118,6 +126,7 @@ module E11y
118
126
  0.01 # 1% sampling
119
127
  end
120
128
  end
129
+ # rubocop:enable Style/HashLikeCase
121
130
 
122
131
  # Check if system is overloaded
123
132
  #
@@ -131,6 +131,8 @@ module E11y
131
131
  # @param state [String] Circuit state
132
132
  # @return [Integer] Numeric representation (0=closed, 1=half_open, 2=open)
133
133
  # @api private
134
+ # rubocop:disable Lint/DuplicateBranch
135
+ # Unknown states intentionally fallback to closed (0), same as "closed"
134
136
  def self.state_to_value(state)
135
137
  case state
136
138
  when "closed" then 0
@@ -139,6 +141,7 @@ module E11y
139
141
  else 0
140
142
  end
141
143
  end
144
+ # rubocop:enable Lint/DuplicateBranch
142
145
 
143
146
  private_class_method :state_to_value
144
147
  end
data/lib/e11y/version.rb CHANGED
@@ -5,5 +5,5 @@ module E11y
5
5
  # - MAJOR: Breaking changes (incompatible API changes)
6
6
  # - MINOR: New features (backwards-compatible)
7
7
  # - PATCH: Bug fixes (backwards-compatible)
8
- VERSION = "0.1.0"
8
+ VERSION = "0.2.0"
9
9
  end
data/lib/e11y.rb CHANGED
@@ -1,14 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "zeitwerk"
4
+ require "active_support/core_ext/numeric/time" # For 30.days, 7.years, etc.
4
5
 
5
6
  # Zeitwerk autoloader setup
6
7
  loader = Zeitwerk::Loader.for_gem
7
8
  # Configure inflector for acronyms
8
9
  loader.inflector.inflect(
9
10
  "pii" => "PII",
10
- "pii_filter" => "PIIFilter"
11
+ "pii_filter" => "PIIFilter",
12
+ "otel_logs" => "OTelLogs",
13
+ "slo" => "SLO",
14
+ "dlq" => "DLQ"
11
15
  )
16
+ # Don't autoload railtie - it will be required manually when Rails is available
17
+ loader.do_not_eager_load("#{__dir__}/e11y/railtie.rb")
12
18
  loader.setup
13
19
 
14
20
  # E11y - Event-Driven Observability for Ruby on Rails
@@ -103,29 +109,51 @@ module E11y
103
109
  # config.pipeline.use E11y::Middleware::Sampling, default_sample_rate: 0.1
104
110
  # end
105
111
  class Configuration
106
- attr_accessor :adapters, :log_level, :enabled, :environment, :service_name
107
- attr_reader :adapter_mapping, :pipeline, :rails_instrumentation, :logger_bridge, :request_buffer, :error_handling,
108
- :dlq_storage, :dlq_filter, :rate_limiting, :slo_tracking
112
+ attr_accessor :adapters, :log_level, :enabled, :environment, :service_name, :default_retention_period,
113
+ :routing_rules, :fallback_adapters
114
+ attr_reader :adapter_mapping, :pipeline, :rails_instrumentation, :logger_bridge, :request_buffer, :active_job,
115
+ :sidekiq, :error_handling, :dlq_storage, :dlq_filter, :rate_limiting, :slo_tracking
109
116
 
110
117
  def initialize
118
+ initialize_basic_config
119
+ initialize_routing_config
120
+ initialize_feature_configs
121
+ configure_default_pipeline
122
+ end
123
+
124
+ private
125
+
126
+ def initialize_basic_config
111
127
  @adapters = {} # Hash of adapter_name => adapter_instance
112
128
  @log_level = :info
113
- @adapter_mapping = default_adapter_mapping
114
129
  @pipeline = E11y::Pipeline::Builder.new
115
130
  @enabled = true
116
131
  @environment = nil
117
132
  @service_name = nil
133
+ end
134
+
135
+ def initialize_routing_config
136
+ @adapter_mapping = default_adapter_mapping
137
+ @default_retention_period = 30.days # Default: 30 days retention
138
+ @routing_rules = [] # Array of lambdas for retention-based routing
139
+ @fallback_adapters = [:stdout] # Fallback if no routing rule matches
140
+ end
141
+
142
+ def initialize_feature_configs
118
143
  @rails_instrumentation = RailsInstrumentationConfig.new
119
144
  @logger_bridge = LoggerBridgeConfig.new
120
145
  @request_buffer = RequestBufferConfig.new
146
+ @active_job = ActiveJobConfig.new
147
+ @sidekiq = SidekiqConfig.new
121
148
  @error_handling = ErrorHandlingConfig.new # ✅ C18 Resolution
122
149
  @dlq_storage = nil # Set by user (e.g., DLQ::FileStorage instance)
123
150
  @dlq_filter = nil # Set by user (e.g., DLQ::Filter instance)
124
151
  @rate_limiting = RateLimitingConfig.new
125
152
  @slo_tracking = SLOTrackingConfig.new # ✅ L3.14.1
126
- configure_default_pipeline
127
153
  end
128
154
 
155
+ public
156
+
129
157
  # Get adapters for given severity
130
158
  #
131
159
  # @param severity [Symbol] Severity level
@@ -134,6 +162,13 @@ module E11y
134
162
  @adapter_mapping[severity] || @adapter_mapping[:default] || []
135
163
  end
136
164
 
165
+ # Get built pipeline (cached after first call)
166
+ #
167
+ # @return [#call] Built middleware pipeline
168
+ def built_pipeline
169
+ @built_pipeline ||= @pipeline.build(->(_event_data) {})
170
+ end
171
+
137
172
  private
138
173
 
139
174
  # Default adapter mapping (convention-based)
@@ -207,12 +242,22 @@ module E11y
207
242
  end
208
243
 
209
244
  # Logger Bridge configuration
245
+ #
246
+ # Controls Rails.logger integration:
247
+ # - When enabled = true: wraps Rails.logger and sends logs to E11y
248
+ # - When enabled = false: no integration (default)
249
+ #
250
+ # @example Enable logger bridge
251
+ # E11y.configure do |config|
252
+ # config.logger_bridge.enabled = true # Wrap Rails.logger + send to E11y
253
+ # end
254
+ #
255
+ # @see lib/e11y/logger/bridge.rb
210
256
  class LoggerBridgeConfig
211
- attr_accessor :enabled, :dual_logging
257
+ attr_accessor :enabled
212
258
 
213
259
  def initialize
214
- @enabled = false # Opt-in
215
- @dual_logging = true # Keep writing to original Rails.logger
260
+ @enabled = false # Opt-in: disabled by default
216
261
  end
217
262
  end
218
263
 
@@ -225,6 +270,38 @@ module E11y
225
270
  end
226
271
  end
227
272
 
273
+ # ActiveJob configuration
274
+ #
275
+ # Controls ActiveJob integration (callbacks for event tracking).
276
+ # When enabled, E11y will automatically track job lifecycle events:
277
+ # - job.enqueued
278
+ # - job.started
279
+ # - job.completed
280
+ # - job.failed
281
+ #
282
+ # @see lib/e11y/instruments/active_job.rb
283
+ class ActiveJobConfig
284
+ attr_accessor :enabled
285
+
286
+ def initialize
287
+ @enabled = false # Disabled by default, enabled by Railtie
288
+ end
289
+ end
290
+
291
+ # Sidekiq configuration
292
+ #
293
+ # Controls Sidekiq middleware integration for trace propagation and context setup.
294
+ # Automatically enabled by Railtie when Sidekiq is detected.
295
+ #
296
+ # @see ADR-008 §9 (Sidekiq Integration)
297
+ class SidekiqConfig
298
+ attr_accessor :enabled
299
+
300
+ def initialize
301
+ @enabled = false # Disabled by default, enabled by Railtie when Sidekiq is present
302
+ end
303
+ end
304
+
228
305
  # Error Handling configuration (C18 Resolution)
229
306
  #
230
307
  # Controls whether event tracking failures should raise exceptions.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: e11y
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Artur Seletskiy
@@ -9,20 +9,6 @@ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
- - !ruby/object:Gem::Dependency
13
- name: activesupport
14
- requirement: !ruby/object:Gem::Requirement
15
- requirements:
16
- - - ">="
17
- - !ruby/object:Gem::Version
18
- version: '7.0'
19
- type: :runtime
20
- prerelease: false
21
- version_requirements: !ruby/object:Gem::Requirement
22
- requirements:
23
- - - ">="
24
- - !ruby/object:Gem::Version
25
- version: '7.0'
26
12
  - !ruby/object:Gem::Dependency
27
13
  name: concurrent-ruby
28
14
  requirement: !ruby/object:Gem::Requirement
@@ -65,6 +51,20 @@ dependencies:
65
51
  - - "~>"
66
52
  - !ruby/object:Gem::Version
67
53
  version: '1.7'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rails
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '7.0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '7.0'
68
68
  - !ruby/object:Gem::Dependency
69
69
  name: zeitwerk
70
70
  requirement: !ruby/object:Gem::Requirement
@@ -80,19 +80,47 @@ dependencies:
80
80
  - !ruby/object:Gem::Version
81
81
  version: '2.6'
82
82
  - !ruby/object:Gem::Dependency
83
- name: rack
83
+ name: benchmark-ips
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '2.13'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '2.13'
96
+ - !ruby/object:Gem::Dependency
97
+ name: memory_profiler
84
98
  requirement: !ruby/object:Gem::Requirement
85
99
  requirements:
86
100
  - - "~>"
87
101
  - !ruby/object:Gem::Version
88
- version: '3.0'
102
+ version: '1.0'
89
103
  type: :development
90
104
  prerelease: false
91
105
  version_requirements: !ruby/object:Gem::Requirement
92
106
  requirements:
93
107
  - - "~>"
94
108
  - !ruby/object:Gem::Version
95
- version: '3.0'
109
+ version: '1.0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: rack
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: 2.2.4
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: 2.2.4
96
124
  - !ruby/object:Gem::Dependency
97
125
  name: rake
98
126
  requirement: !ruby/object:Gem::Requirement
@@ -247,21 +275,32 @@ dependencies:
247
275
  - - "~>"
248
276
  - !ruby/object:Gem::Version
249
277
  version: '5.15'
250
- description: "E11y (Easy Telemetry) - production-ready observability gem for Ruby
251
- on Rails applications.\n\nKEY FEATURES:\n• \U0001F4CA Zero-Config SLO Tracking -
252
- automatic Service Level Objectives for HTTP endpoints and background jobs\n• \U0001F3AF
253
- Request-Scoped Debug Buffering - buffer debug logs in memory, flush only on errors
254
- (reduce log noise by 90%)\n• \U0001F4C8 Pattern-Based Metrics - auto-generate Prometheus/Yabeda
255
- metrics from business events\n• \U0001F512 GDPR/SOC2 Compliance - built-in PII filtering
256
- and audit trails\n\U0001F50C Pluggable Adapters - send events to Loki, Sentry,
257
- OpenTelemetry, Elasticsearch, or custom backends\n• \U0001F680 High Performance
258
- - zero-allocation event tracking, lock-free ring buffers, adaptive memory limits\n•
259
- \U0001F9F5 Thread-Safe - designed for multi-threaded Rails apps and Sidekiq workers\n•
260
- \U0001F3AD Multi-Tenant Ready - trace context propagation across services with OpenTelemetry
261
- integration\n\U0001F4DD Type-Safe Events - declarative event schemas with dry-schema
262
- validation\n Rate Limiting & Sampling - protect production from metric storms
263
- and cost overruns\n\nPerfect for SuperApp architectures, microservices, and high-scale
264
- Rails applications.\nBattle-tested patterns from Devise, Sidekiq, Sentry, and Yabeda.\n"
278
+ description: |
279
+ E11y (Easy Telemetry) - Observability for Rails developers who hate noise.
280
+
281
+ UNIQUE FEATURES:
282
+ Request-scoped debug buffering - buffers debug logs in memory, flushes ONLY on errors
283
+ Zero-config SLO tracking - automatic Service Level Objectives for HTTP endpoints and jobs
284
+ Schema-validated events - catch bugs before production with dry-schema
285
+
286
+ DEVELOPER EXPERIENCE:
287
+ 5-minute setup (not 2-week migration)
288
+ Auto-metrics from events (no manual Yabeda.increment)
289
+ Rails-first design (follows Rails conventions)
290
+ Pluggable adapters (Loki, Sentry, OpenTelemetry, custom backends)
291
+
292
+ COST SAVINGS:
293
+ • Reduce log storage costs by 90% (request-scoped buffering)
294
+ • Replace expensive APM SaaS ($500-5k/month → infra costs only)
295
+ • Own your observability data (no vendor lock-in)
296
+
297
+ PRODUCTION-READY:
298
+ • Thread-safe for multi-threaded Rails + Sidekiq
299
+ • Adaptive sampling (error-based, load-based, value-based)
300
+ • PII filtering (GDPR-compliant masking/hashing)
301
+ • Performance optimized (hash-based events, minimal allocations)
302
+
303
+ Perfect for Rails 7.0+ teams who need observability without complexity or high costs.
265
304
  executables: []
266
305
  extensions: []
267
306
  extra_rdoc_files: []
@@ -272,7 +311,13 @@ files:
272
311
  - CODE_OF_CONDUCT.md
273
312
  - LICENSE.txt
274
313
  - README.md
314
+ - RELEASE.md
275
315
  - Rakefile
316
+ - benchmarks/OPTIMIZATION.md
317
+ - benchmarks/README.md
318
+ - benchmarks/allocation_profiling.rb
319
+ - benchmarks/e11y_benchmarks.rb
320
+ - benchmarks/ruby_baseline_allocations.rb
276
321
  - benchmarks/run_all.rb
277
322
  - config/README.md
278
323
  - config/loki-local-config.yaml
@@ -296,8 +341,11 @@ files:
296
341
  - docs/ADR-014-event-driven-slo.md
297
342
  - docs/ADR-015-middleware-order.md
298
343
  - docs/ADR-016-self-monitoring-slo.md
344
+ - docs/ADR-017-multi-rails-compatibility.md
345
+ - docs/ADR-INDEX.md
299
346
  - docs/API-REFERENCE-L28.md
300
347
  - docs/COMPREHENSIVE-CONFIGURATION.md
348
+ - docs/CONTRIBUTING.md
301
349
  - docs/IMPLEMENTATION_NOTES.md
302
350
  - docs/IMPLEMENTATION_PLAN.md
303
351
  - docs/IMPLEMENTATION_PLAN_ARCHITECTURE.md
@@ -328,7 +376,7 @@ files:
328
376
  - docs/use_cases/UC-016-rails-logger-migration.md
329
377
  - docs/use_cases/UC-017-local-development.md
330
378
  - docs/use_cases/UC-018-testing-events.md
331
- - docs/use_cases/UC-019-tiered-storage-migration.md
379
+ - docs/use_cases/UC-019-retention-based-routing.md
332
380
  - docs/use_cases/UC-020-event-versioning.md
333
381
  - docs/use_cases/UC-021-error-handling-retry-dlq.md
334
382
  - docs/use_cases/UC-022-event-registry.md
@@ -387,12 +435,10 @@ files:
387
435
  - lib/e11y/middleware/base.rb
388
436
  - lib/e11y/middleware/event_slo.rb
389
437
  - lib/e11y/middleware/pii_filter.rb
390
- - lib/e11y/middleware/pii_filtering.rb
391
438
  - lib/e11y/middleware/rate_limiting.rb
392
439
  - lib/e11y/middleware/request.rb
393
440
  - lib/e11y/middleware/routing.rb
394
441
  - lib/e11y/middleware/sampling.rb
395
- - lib/e11y/middleware/slo.rb
396
442
  - lib/e11y/middleware/trace_context.rb
397
443
  - lib/e11y/middleware/validation.rb
398
444
  - lib/e11y/middleware/versioning.rb
@@ -447,6 +493,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
447
493
  requirements: []
448
494
  rubygems_version: 4.0.2
449
495
  specification_version: 4
450
- summary: 'E11y - Easy Telemetry: Production-grade observability for Rails with zero-config
451
- SLO tracking'
496
+ summary: 'E11y - Easy Telemetry: Observability for Rails developers who hate noise'
452
497
  test_files: []