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
|
@@ -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
|
-
|
|
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 (
|
|
111
|
-
|
|
115
|
+
# Cleanup old events periodically (every 50 errors) instead of on every error
|
|
116
|
+
# This reduces O(n²) 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
|
-
|
|
131
|
+
cutoff = now - @window
|
|
126
132
|
|
|
127
133
|
events = event_name ? @error_events[event_name] : @all_errors
|
|
128
|
-
|
|
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
|
-
|
|
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 (
|
|
69
|
-
|
|
68
|
+
# Cleanup old events periodically (every 100 events) instead of on every event
|
|
69
|
+
# This reduces O(n²) 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
|
-
|
|
80
|
+
cutoff = now - @window
|
|
80
81
|
|
|
81
|
-
|
|
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
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
|
-
|
|
108
|
-
|
|
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
|
|
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.
|
|
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:
|
|
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: '
|
|
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: '
|
|
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:
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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-
|
|
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:
|
|
451
|
-
SLO tracking'
|
|
496
|
+
summary: 'E11y - Easy Telemetry: Observability for Rails developers who hate noise'
|
|
452
497
|
test_files: []
|