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
data/README.md
CHANGED
|
@@ -1,179 +1,1213 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# E11y - Easy Telemetry
|
|
4
|
+
|
|
5
|
+
**Observability for Rails developers who hate noise**
|
|
2
6
|
|
|
3
7
|
[](https://badge.fury.io/rb/e11y)
|
|
4
|
-
[](https://github.com/arturseletskiy/e11y/actions/workflows/ci.yml)
|
|
5
9
|
[](https://codecov.io/gh/arturseletskiy/e11y)
|
|
6
|
-
[](https://github.com/rubocop/rubocop)
|
|
7
10
|
|
|
8
|
-
**
|
|
11
|
+
**⚠️ Work in Progress** - Core features implemented, production validation in progress
|
|
12
|
+
|
|
13
|
+
[Quick Start](#quick-start) • [Why E11y?](#why-e11y) • [Documentation](#documentation)
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## The Problem Every Rails Developer Knows
|
|
20
|
+
|
|
21
|
+
You enable debug logs in production to catch that one weird bug.
|
|
22
|
+
**Result:** 10,000 lines of noise for every 1 error.
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
# Production logs right now:
|
|
26
|
+
[DEBUG] Cache read: session:abc123 ← 99% useless
|
|
27
|
+
[DEBUG] SQL: SELECT * FROM users WHERE... ← 99% useless
|
|
28
|
+
[DEBUG] Rendered users/show.html.erb ← 99% useless
|
|
29
|
+
[INFO] User 123 logged in ← maybe useful
|
|
30
|
+
[DEBUG] Cache read: user:123 ← 99% useless
|
|
31
|
+
[ERROR] Payment failed: Stripe timeout ← THIS is what you need!
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**The dilemma:**
|
|
35
|
+
- Turn debug ON → drown in noise, pay $$$ for log storage
|
|
36
|
+
- Turn debug OFF → fly blind when bugs happen
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## The E11y Solution
|
|
41
|
+
|
|
42
|
+
**Request-scoped debug buffering** - the only Rails observability gem that does this:
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
# E11y buffers debug logs in memory during request
|
|
46
|
+
# Flushes to storage ONLY if request fails
|
|
47
|
+
|
|
48
|
+
# Happy path (99% of requests):
|
|
49
|
+
[INFO] User 123 logged in ✅
|
|
50
|
+
# Debug logs discarded, zero noise
|
|
51
|
+
|
|
52
|
+
# Error path (1% of requests):
|
|
53
|
+
[ERROR] Payment failed: Stripe timeout
|
|
54
|
+
[DEBUG] Cache read: session:abc123 ← NOW we see the context!
|
|
55
|
+
[DEBUG] SQL: SELECT * FROM users... ← NOW we see what happened!
|
|
56
|
+
[DEBUG] Rendered users/show.html.erb ← Complete error trail!
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Result:** Debug when you need it. Silence when you don't.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## What Makes E11y Different?
|
|
64
|
+
|
|
65
|
+
### 1. Request-Scoped Debug Buffering (Unique to E11y)
|
|
66
|
+
|
|
67
|
+
**No other Rails gem does this.**
|
|
9
68
|
|
|
10
|
-
|
|
69
|
+
```ruby
|
|
70
|
+
# Traditional approach:
|
|
71
|
+
Rails.logger.debug "query: SELECT..." # → Always written to disk
|
|
72
|
+
Rails.logger.debug "cache miss" # → Always written to disk
|
|
73
|
+
Rails.logger.debug "rendering view" # → Always written to disk
|
|
74
|
+
# Cost: $$$, Noise: 99%, Value: 1%
|
|
75
|
+
|
|
76
|
+
# E11y approach:
|
|
77
|
+
E11y.configure do |config|
|
|
78
|
+
config.request_buffer.enabled = true
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Debug events buffered in memory during request
|
|
82
|
+
# Flushed to storage ONLY on errors
|
|
83
|
+
# Cost: -90%, Noise: -99%, Value: 100%
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Real-world impact:**
|
|
87
|
+
- **Storage costs:** $500/month → $50/month (Loki/CloudWatch)
|
|
88
|
+
- **Log search time:** 30 seconds → 3 seconds (90% less data)
|
|
89
|
+
- **Developer sanity:** Infinite ✨
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
### 2. Schema-Validated Business Events
|
|
94
|
+
|
|
95
|
+
Stop debugging nil values in production:
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
class OrderPaidEvent < E11y::Event::Base
|
|
99
|
+
schema do
|
|
100
|
+
required(:order_id).filled(:string)
|
|
101
|
+
required(:amount).filled(:float, gt?: 0)
|
|
102
|
+
required(:currency).filled(:string, included_in?: %w[USD EUR GBP])
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
metrics do
|
|
106
|
+
counter :orders_total, tags: [:currency]
|
|
107
|
+
histogram :order_amount, value: :amount, tags: [:currency]
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Invalid data caught BEFORE production
|
|
112
|
+
OrderPaidEvent.track(order_id: "123", amount: -10, currency: "INVALID")
|
|
113
|
+
# => E11y::ValidationError: amount must be > 0
|
|
11
114
|
|
|
12
|
-
|
|
115
|
+
# Valid data: event + metrics in one call
|
|
116
|
+
OrderPaidEvent.track(order_id: "123", amount: 99.99, currency: "USD")
|
|
117
|
+
# ✅ Event sent to Loki/Sentry
|
|
118
|
+
# ✅ Prometheus metrics updated
|
|
119
|
+
# ✅ No manual Yabeda.increment
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Developer experience:**
|
|
123
|
+
- Schema validation prevents bugs
|
|
124
|
+
- Auto-metrics eliminate boilerplate
|
|
125
|
+
- Type safety without TypeScript
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
### 3. Zero-Config SLO Tracking
|
|
130
|
+
|
|
131
|
+
Automatic Service Level Objectives for your endpoints and jobs:
|
|
13
132
|
|
|
14
133
|
```ruby
|
|
134
|
+
# Enable Rails instrumentation
|
|
135
|
+
E11y.configure do |config|
|
|
136
|
+
config.rails_instrumentation.enabled = true
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# That's it! E11y now tracks SLOs automatically:
|
|
140
|
+
# - HTTP endpoints: success rate, latency percentiles (p50, p95, p99)
|
|
141
|
+
# - Background jobs: success rate, execution time, retry rate
|
|
142
|
+
# - Database queries: slow query detection
|
|
143
|
+
# - Cache operations: hit/miss ratios
|
|
144
|
+
|
|
145
|
+
# View SLO status:
|
|
146
|
+
E11y::SLO::Tracker.status
|
|
147
|
+
# => {
|
|
148
|
+
# "POST /orders" => { success_rate: 99.8%, p95_latency: 250ms },
|
|
149
|
+
# "OrderProcessor" => { success_rate: 99.5%, avg_time: 1.2s }
|
|
150
|
+
# }
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**vs. Traditional SLO Tracking:**
|
|
154
|
+
- ❌ Manual instrumentation of every endpoint
|
|
155
|
+
- ❌ Complex SLO definitions and calculations
|
|
156
|
+
- ❌ Separate tools for different SLOs
|
|
157
|
+
- ✅ E11y: Zero config, automatic tracking, unified view
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
### 4. Rails-First Design
|
|
162
|
+
|
|
163
|
+
Built for Rails developers, not platform engineers:
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
# 5-minute setup, not 2-week OpenTelemetry migration
|
|
167
|
+
gem "e11y"
|
|
168
|
+
|
|
169
|
+
E11y.configure do |config|
|
|
170
|
+
config.adapters[:logs] = E11y::Adapters::Loki.new(url: ENV["LOKI_URL"])
|
|
171
|
+
config.adapters[:errors_tracker] = E11y::Adapters::Sentry.new(dsn: ENV["SENTRY_DSN"])
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Auto-instruments Rails (optional):
|
|
175
|
+
config.rails_instrumentation.enabled = true
|
|
176
|
+
# → HTTP requests, ActiveRecord, ActiveJob, Cache events
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**vs. Traditional Observability:**
|
|
180
|
+
- ❌ OpenTelemetry: 5 docs pages, complex setup
|
|
181
|
+
- ❌ Datadog: $10k+/year, vendor lock-in
|
|
182
|
+
- ❌ ELK Stack: DevOps team needed
|
|
183
|
+
- ✅ E11y: One gem, Rails conventions, owned data
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Who Should Use E11y?
|
|
188
|
+
|
|
189
|
+
### ✅ Perfect For
|
|
190
|
+
|
|
191
|
+
**Rails developers who:**
|
|
192
|
+
- Hate searching through 100k debug logs for 1 error
|
|
193
|
+
- Pay too much for Datadog/New Relic ($500-5k/month)
|
|
194
|
+
- Need observability but don't have a DevOps team
|
|
195
|
+
- Want type-safe events without migrating to TypeScript
|
|
196
|
+
|
|
197
|
+
**Teams that:**
|
|
198
|
+
- Run Rails 7.0+ in production (Sidekiq, PostgreSQL, Redis)
|
|
199
|
+
- Use Loki/Grafana or Sentry for monitoring
|
|
200
|
+
- Care about developer experience and code quality
|
|
201
|
+
- Prefer open-source over SaaS vendor lock-in
|
|
202
|
+
|
|
203
|
+
### ⚠️ Not For (Yet)
|
|
204
|
+
|
|
205
|
+
- **Non-Rails Ruby** - Focused on Rails conventions first
|
|
206
|
+
- **Microservices polyglot** - OpenTelemetry better for multi-language
|
|
207
|
+
- **Enterprise compliance** - E11y is WIP, audit trails coming soon
|
|
208
|
+
- **Auto-instrumentation only** - E11y requires event definitions (by design)
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Core Features
|
|
213
|
+
|
|
214
|
+
| Feature | Status | Description |
|
|
215
|
+
|---------|--------|-------------|
|
|
216
|
+
| **Request-Scoped Buffering** | ✅ Implemented | Buffer debug logs, flush only on errors (-90% noise) |
|
|
217
|
+
| **Zero-Config SLO Tracking** | ✅ Implemented | Automatic Service Level Objectives for endpoints/jobs |
|
|
218
|
+
| **Schema Validation** | ✅ Implemented | dry-schema validation before sending events |
|
|
219
|
+
| **Metrics DSL** | ✅ Implemented | Define Prometheus metrics alongside events |
|
|
220
|
+
| **Adapters** | ✅ 7 adapters | Loki, Sentry, OpenTelemetry, Yabeda, File, Stdout, InMemory |
|
|
221
|
+
| **PII Filtering** | ✅ Implemented | Configurable field masking/hashing/redaction |
|
|
222
|
+
| **Adaptive Sampling** | ✅ Implemented | Error-based, load-based, value-based strategies |
|
|
223
|
+
| **Rails Integration** | ✅ Implemented | Auto-instrument HTTP, ActiveRecord, ActiveJob, Cache |
|
|
224
|
+
| **Production Testing** | 🚧 In Progress | Validating with real workloads |
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Quick Start in 5 Minutes
|
|
229
|
+
|
|
230
|
+
### 1. Install
|
|
231
|
+
|
|
232
|
+
```bash
|
|
15
233
|
# Gemfile
|
|
16
234
|
gem "e11y"
|
|
17
235
|
|
|
18
|
-
|
|
236
|
+
bundle install
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### 2. Configure (One-Time Setup)
|
|
240
|
+
|
|
241
|
+
```ruby
|
|
242
|
+
# config/initializers/e11y.rb
|
|
243
|
+
E11y.configure do |config|
|
|
244
|
+
# Enable request-scoped debug buffering (THE killer feature)
|
|
245
|
+
config.request_buffer.enabled = true
|
|
246
|
+
|
|
247
|
+
# Configure where events go
|
|
248
|
+
config.adapters[:logs] = E11y::Adapters::Loki.new(
|
|
249
|
+
url: ENV["LOKI_URL"],
|
|
250
|
+
batch_size: 100
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
config.adapters[:errors_tracker] = E11y::Adapters::Sentry.new(
|
|
254
|
+
dsn: ENV["SENTRY_DSN"]
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# Optional: Auto-instrument Rails
|
|
258
|
+
config.rails_instrumentation.enabled = true
|
|
259
|
+
end
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### 3. Define Business Events
|
|
263
|
+
|
|
264
|
+
```ruby
|
|
265
|
+
# app/events/order_paid_event.rb
|
|
266
|
+
class OrderPaidEvent < E11y::Event::Base
|
|
267
|
+
# Schema validation (catch bugs before production)
|
|
268
|
+
schema do
|
|
269
|
+
required(:order_id).filled(:string)
|
|
270
|
+
required(:amount).filled(:float, gt?: 0)
|
|
271
|
+
required(:currency).filled(:string, included_in?: %w[USD EUR GBP])
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Auto-metrics (no manual Yabeda.increment!)
|
|
275
|
+
metrics do
|
|
276
|
+
counter :orders_total, tags: [:currency]
|
|
277
|
+
histogram :order_amount, value: :amount, tags: [:currency]
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### 4. Track Events
|
|
283
|
+
|
|
284
|
+
```ruby
|
|
285
|
+
# In controllers/services
|
|
286
|
+
class OrdersController < ApplicationController
|
|
287
|
+
def create
|
|
288
|
+
order = Order.create!(order_params)
|
|
289
|
+
|
|
290
|
+
# One method call = validation + event + metrics
|
|
291
|
+
OrderPaidEvent.track(
|
|
292
|
+
order_id: order.id,
|
|
293
|
+
amount: order.total,
|
|
294
|
+
currency: "USD"
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
# If amount is negative → E11y::ValidationError (caught before production!)
|
|
298
|
+
# If valid → event sent to Loki + orders_total metric incremented
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**That's it!** Now you have:
|
|
304
|
+
- ✅ Debug logs buffered in memory (flushed only on errors)
|
|
305
|
+
- ✅ Schema-validated business events
|
|
306
|
+
- ✅ Auto-generated Prometheus metrics
|
|
307
|
+
- ✅ Zero-config SLO tracking (success rates, latency percentiles)
|
|
308
|
+
- ✅ Events sent to Loki, Sentry, or custom adapters
|
|
309
|
+
|
|
310
|
+
**No more:**
|
|
311
|
+
- ❌ Searching through 100k debug logs
|
|
312
|
+
- ❌ nil values in production
|
|
313
|
+
- ❌ Manual `Yabeda.increment` everywhere
|
|
314
|
+
- ❌ Manual SLO definitions and calculations
|
|
315
|
+
- ❌ $500/month log storage bills
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Before and After E11y
|
|
320
|
+
|
|
321
|
+
### Before: Traditional Rails Logging
|
|
322
|
+
|
|
323
|
+
```ruby
|
|
324
|
+
# Development: Debug enabled
|
|
325
|
+
Rails.logger.debug "Cache read: session:abc"
|
|
326
|
+
Rails.logger.debug "SQL: SELECT * FROM users..."
|
|
327
|
+
Rails.logger.debug "Rendered users/show"
|
|
328
|
+
# Result: Helpful for debugging ✅
|
|
329
|
+
|
|
330
|
+
# Production: Debug disabled (too noisy)
|
|
331
|
+
Rails.logger.info "User logged in"
|
|
332
|
+
# Bug happens...
|
|
333
|
+
Rails.logger.error "Payment failed!"
|
|
334
|
+
# Result: No context, blind debugging ❌
|
|
335
|
+
|
|
336
|
+
# Production: Debug enabled (to catch bug)
|
|
337
|
+
# 99 successful requests:
|
|
338
|
+
# [DEBUG] Cache read... (297 lines)
|
|
339
|
+
# [INFO] User logged in (99 lines)
|
|
340
|
+
# 1 failed request:
|
|
341
|
+
# [DEBUG] Cache read... (3 lines)
|
|
342
|
+
# [ERROR] Payment failed (1 line)
|
|
343
|
+
# Total: 400 lines, 74% noise ❌
|
|
344
|
+
# Cost: $500/month Loki storage ❌
|
|
345
|
+
# Search time: 30 seconds ❌
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### After: E11y Request-Scoped Buffering
|
|
349
|
+
|
|
350
|
+
```ruby
|
|
351
|
+
# Production with E11y:
|
|
352
|
+
E11y.configure { |c| c.request_buffer.enabled = true }
|
|
353
|
+
|
|
354
|
+
# 99 successful requests:
|
|
355
|
+
# [INFO] User logged in (99 lines)
|
|
356
|
+
# Debug logs buffered in memory, discarded ✅
|
|
357
|
+
|
|
358
|
+
# 1 failed request:
|
|
359
|
+
# [ERROR] Payment failed (1 line)
|
|
360
|
+
# [DEBUG] Cache read... (3 lines) ← Flushed!
|
|
361
|
+
# [DEBUG] SQL: SELECT... (context!) ← Flushed!
|
|
362
|
+
# [DEBUG] Rendered view... (trail!) ← Flushed!
|
|
363
|
+
# Total: 103 lines, 0% noise ✅
|
|
364
|
+
# Cost: $50/month Loki storage ✅ (-90%)
|
|
365
|
+
# Search time: 3 seconds ✅ (-90%)
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
**Impact:**
|
|
369
|
+
- **Developer productivity:** 10x faster debugging (context when you need it)
|
|
370
|
+
- **Infrastructure cost:** -90% log storage (only relevant logs stored)
|
|
371
|
+
- **Signal-to-noise:** 100% vs 1% (every log line matters)
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## E11y vs Alternatives
|
|
376
|
+
|
|
377
|
+
### Comparison Matrix
|
|
378
|
+
|
|
379
|
+
| Solution | Setup Time | Monthly Cost | Request-Scoped Buffering | SLO Tracking | Schema Validation | Auto-Metrics | Data Ownership |
|
|
380
|
+
|----------|-----------|--------------|--------------------------|--------------|-------------------|--------------|----------------|
|
|
381
|
+
| **E11y** | **5 minutes** | **Infra costs** | **✅ Unique** | **✅ Zero-config** | **✅** | **✅** | **✅ Full** |
|
|
382
|
+
| Datadog APM | 2-4 hours | $500-5,000 | ❌ | ✅ Manual | ❌ | ✅ | ❌ SaaS lock-in |
|
|
383
|
+
| New Relic | 2-4 hours | $99-658/user | ❌ | ✅ Manual | ❌ | ✅ | ❌ SaaS lock-in |
|
|
384
|
+
| Sentry | 1 hour | $26-80/mo | ❌ | ❌ | ❌ | Partial | ❌ SaaS lock-in |
|
|
385
|
+
| Semantic Logger | 30 minutes | Infra costs | ❌ | ❌ | ❌ | ❌ | ✅ Full |
|
|
386
|
+
| OpenTelemetry | 1-2 weeks | Infra costs | ❌ | Manual setup | ❌ | ✅ | ✅ Full |
|
|
387
|
+
| Grafana + Loki | 2-3 days | Infra costs | ❌ | Manual setup | ❌ | Manual | ✅ Full |
|
|
388
|
+
| AppSignal | 1 hour | $23-499/mo | ❌ | ✅ Built-in | ❌ | ✅ | ❌ SaaS lock-in |
|
|
389
|
+
|
|
390
|
+
**Legend:**
|
|
391
|
+
- **Setup Time:** From zero to first meaningful data
|
|
392
|
+
- **Monthly Cost:** For 10-person team, medium Rails app (estimated)
|
|
393
|
+
- **Request-Scoped Buffering:** Buffer debug logs, flush only on errors
|
|
394
|
+
- **SLO Tracking:** Automatic Service Level Objectives monitoring
|
|
395
|
+
- **Schema Validation:** Type-safe event schemas
|
|
396
|
+
- **Auto-Metrics:** Metrics generated from events automatically
|
|
397
|
+
- **Data Ownership:** Can you host it yourself?
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
### Detailed Comparisons
|
|
402
|
+
|
|
403
|
+
#### vs. SaaS APM (Datadog, New Relic, Dynatrace)
|
|
404
|
+
|
|
405
|
+
**Datadog / New Relic:**
|
|
406
|
+
- ✅ **Pros:** Full-stack visibility, mature dashboards, auto-instrumentation
|
|
407
|
+
- ❌ **Cons:** $500-5k/month, vendor lock-in, no debug buffering, no schema validation
|
|
408
|
+
- **E11y advantage:** 10x cheaper, request-scoped buffering (unique), type-safe events, own your data
|
|
409
|
+
|
|
410
|
+
**When to use Datadog/New Relic instead:**
|
|
411
|
+
- You need frontend RUM (Real User Monitoring)
|
|
412
|
+
- You have polyglot microservices (not just Rails)
|
|
413
|
+
- Budget is unlimited, prefer turnkey solution
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
#### vs. Open-Source Logging (Semantic Logger, Lograge)
|
|
418
|
+
|
|
419
|
+
**Semantic Logger:**
|
|
420
|
+
- ✅ **Pros:** Structured logs (JSON), async writes, Rails integration
|
|
421
|
+
- ❌ **Cons:** No debug buffering, no schema validation, no auto-metrics, logs-only
|
|
422
|
+
- **E11y advantage:** Request-scoped buffering (unique), schema validation, auto-metrics, unified events
|
|
423
|
+
|
|
424
|
+
**Lograge:**
|
|
425
|
+
- ✅ **Pros:** Reduces Rails log noise (single-line requests)
|
|
426
|
+
- ❌ **Cons:** Filtering only, no buffering, no validation, no metrics
|
|
427
|
+
- **E11y advantage:** Request-scoped buffering (selective, not filtering), schema validation, auto-metrics
|
|
428
|
+
|
|
429
|
+
**When to use Semantic Logger instead:**
|
|
430
|
+
- You only need structured JSON logs (no events/metrics)
|
|
431
|
+
- You don't need debug buffering or schema validation
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
#### vs. OpenTelemetry
|
|
436
|
+
|
|
437
|
+
**OpenTelemetry:**
|
|
438
|
+
- ✅ **Pros:** Industry standard, polyglot, vendor-neutral, mature ecosystem
|
|
439
|
+
- ❌ **Cons:** Complex setup (1-2 weeks), no debug buffering, no schema validation, overkill for Rails monolith
|
|
440
|
+
- **E11y advantage:** 5-minute setup, Rails-first, request-scoped buffering, schema validation
|
|
441
|
+
|
|
442
|
+
**When to use OpenTelemetry instead:**
|
|
443
|
+
- You have microservices in multiple languages (Go, Java, Python, etc.)
|
|
444
|
+
- You need distributed tracing across services
|
|
445
|
+
- You have a platform team to manage complexity
|
|
446
|
+
|
|
447
|
+
**Use both:** E11y events can be sent to OpenTelemetry via `E11y::Adapters::OtelLogs`
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
#### vs. Grafana + Loki + Prometheus
|
|
452
|
+
|
|
453
|
+
**Grafana Stack:**
|
|
454
|
+
- ✅ **Pros:** Open-source, powerful visualizations, mature, self-hosted
|
|
455
|
+
- ❌ **Cons:** Complex setup (2-3 days), requires DevOps, no Rails integration, no schema validation
|
|
456
|
+
- **E11y advantage:** 5-minute setup, Rails-native, schema validation, no DevOps required
|
|
457
|
+
|
|
458
|
+
**When to use Grafana Stack instead:**
|
|
459
|
+
- You already have Grafana/Loki infrastructure
|
|
460
|
+
- You have a dedicated DevOps team
|
|
461
|
+
- You need custom dashboards across multiple systems
|
|
462
|
+
|
|
463
|
+
**Use both:** E11y can send events to Loki via `E11y::Adapters::Loki`
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
#### vs. Error Tracking (Sentry, Honeybadger, Rollbar)
|
|
468
|
+
|
|
469
|
+
**Sentry:**
|
|
470
|
+
- ✅ **Pros:** Excellent error tracking, stack traces, breadcrumbs, release tracking
|
|
471
|
+
- ❌ **Cons:** Errors-only, no debug buffering, no schema validation, $26-80/mo
|
|
472
|
+
- **E11y advantage:** Events + errors + metrics unified, request-scoped buffering, schema validation
|
|
473
|
+
|
|
474
|
+
**When to use Sentry instead:**
|
|
475
|
+
- You only need error tracking (not general observability)
|
|
476
|
+
- You need frontend JavaScript error tracking
|
|
477
|
+
|
|
478
|
+
**Use both:** E11y can send error events to Sentry via `E11y::Adapters::Sentry`
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
#### vs. Rails-First APM (AppSignal, Skylight)
|
|
483
|
+
|
|
484
|
+
**AppSignal:**
|
|
485
|
+
- ✅ **Pros:** Rails-native, beautiful UI, performance monitoring, $23/mo entry
|
|
486
|
+
- ❌ **Cons:** SaaS lock-in, no debug buffering, no schema validation, limited to supported languages
|
|
487
|
+
- **E11y advantage:** Request-scoped buffering (unique), schema validation, own your data
|
|
488
|
+
|
|
489
|
+
**Skylight:**
|
|
490
|
+
- ✅ **Pros:** Rails performance profiling, SQL query analysis
|
|
491
|
+
- ❌ **Cons:** Performance-only (no logs/events), SaaS lock-in, $20+/mo
|
|
492
|
+
- **E11y advantage:** Unified events/logs/metrics, request-scoped buffering, own your data
|
|
493
|
+
|
|
494
|
+
**When to use AppSignal/Skylight instead:**
|
|
495
|
+
- You want zero-config turnkey solution
|
|
496
|
+
- You prefer paying for hosted service over self-hosting
|
|
497
|
+
|
|
498
|
+
**Use both:** E11y for events/logs/metrics, AppSignal for performance profiling
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
### Decision Matrix
|
|
503
|
+
|
|
504
|
+
**Choose E11y if:**
|
|
505
|
+
- ✅ You're tired of noisy debug logs
|
|
506
|
+
- ✅ You want type-safe events (catch bugs before production)
|
|
507
|
+
- ✅ You prefer Rails conventions over platform engineering
|
|
508
|
+
- ✅ You want to own your observability data
|
|
509
|
+
- ✅ You want to reduce log storage costs by 90%
|
|
510
|
+
|
|
511
|
+
**Choose SaaS APM (Datadog, New Relic) if:**
|
|
512
|
+
- ✅ You need frontend RUM + backend APM
|
|
513
|
+
- ✅ You have polyglot microservices
|
|
514
|
+
- ✅ Budget unlimited, prefer turnkey solution
|
|
515
|
+
- ✅ You don't want to manage infrastructure
|
|
516
|
+
|
|
517
|
+
**Choose OpenTelemetry if:**
|
|
518
|
+
- ✅ You have microservices in multiple languages
|
|
519
|
+
- ✅ You have a platform team to manage complexity
|
|
520
|
+
- ✅ You need vendor-neutral distributed tracing
|
|
521
|
+
|
|
522
|
+
**Choose Grafana Stack if:**
|
|
523
|
+
- ✅ You already have Grafana infrastructure
|
|
524
|
+
- ✅ You have a DevOps team
|
|
525
|
+
- ✅ You need custom dashboards across systems
|
|
526
|
+
|
|
527
|
+
**Choose Semantic Logger/Lograge if:**
|
|
528
|
+
- ✅ You only need structured JSON logs (nothing else)
|
|
529
|
+
- ✅ You don't need debug buffering or schema validation
|
|
530
|
+
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
### The E11y Sweet Spot
|
|
534
|
+
|
|
535
|
+
E11y is optimized for:
|
|
536
|
+
|
|
537
|
+
**Team size:** 5-100 engineers
|
|
538
|
+
**Stack:** Rails 7.0+ monolith or modular monolith
|
|
539
|
+
**Infra:** PostgreSQL, Redis, Sidekiq, standard Rails stack
|
|
540
|
+
**Budget:** Prefer infrastructure costs over $500-5k/month SaaS
|
|
541
|
+
**Philosophy:** Developer experience > platform complexity
|
|
542
|
+
|
|
543
|
+
**Not optimized for:**
|
|
544
|
+
- Polyglot microservices (use OpenTelemetry)
|
|
545
|
+
- Frontend-heavy SPAs (use Datadog/Sentry for RUM)
|
|
546
|
+
- Enterprise compliance requirements (WIP, coming soon)
|
|
547
|
+
|
|
548
|
+
---
|
|
549
|
+
|
|
550
|
+
## Table of Contents
|
|
551
|
+
|
|
552
|
+
- [Quick Start](#quick-start-in-5-minutes)
|
|
553
|
+
- [What Makes E11y Different?](#what-makes-e11y-different)
|
|
554
|
+
- [Who Should Use E11y?](#who-should-use-e11y)
|
|
555
|
+
- [Before and After](#before-and-after-e11y)
|
|
556
|
+
- [E11y vs Alternatives](#e11y-vs-alternatives)
|
|
557
|
+
- [Schema Validation](#schema-validation)
|
|
558
|
+
- [Metrics DSL](#metrics-dsl)
|
|
559
|
+
- [Adapters](#adapters)
|
|
560
|
+
- [PII Filtering](#pii-filtering)
|
|
561
|
+
- [Adaptive Sampling](#adaptive-sampling)
|
|
562
|
+
- [Presets](#presets)
|
|
563
|
+
- [Rails Integration](#rails-integration)
|
|
564
|
+
- [Testing](#testing)
|
|
565
|
+
- [Configuration](#configuration)
|
|
566
|
+
- [Performance](#performance)
|
|
567
|
+
- [Documentation](#documentation)
|
|
568
|
+
|
|
569
|
+
---
|
|
570
|
+
|
|
571
|
+
## Schema Validation
|
|
572
|
+
|
|
573
|
+
E11y validates event data using [dry-schema](https://dry-rb.org/gems/dry-schema/).
|
|
574
|
+
|
|
575
|
+
### Basic Example
|
|
576
|
+
|
|
577
|
+
```ruby
|
|
578
|
+
class OrderCreatedEvent < E11y::Event::Base
|
|
579
|
+
schema do
|
|
580
|
+
required(:order_id).filled(:string)
|
|
581
|
+
required(:total).filled(:float, gt?: 0)
|
|
582
|
+
required(:currency).filled(:string, included_in?: %w[USD EUR GBP])
|
|
583
|
+
optional(:coupon_code).maybe(:string)
|
|
584
|
+
end
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
# Valid event
|
|
588
|
+
OrderCreatedEvent.track(order_id: "123", total: 99.99, currency: "USD")
|
|
589
|
+
|
|
590
|
+
# Invalid event raises E11y::ValidationError
|
|
591
|
+
OrderCreatedEvent.track(order_id: nil, total: -10, currency: "INVALID")
|
|
592
|
+
# => ValidationError: order_id is missing, total must be > 0
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
### Validation Modes
|
|
596
|
+
|
|
597
|
+
For high-frequency events, you can configure validation behavior:
|
|
598
|
+
|
|
599
|
+
```ruby
|
|
600
|
+
class HighFrequencyEvent < E11y::Event::Base
|
|
601
|
+
# Always validate (default)
|
|
602
|
+
validation_mode :always
|
|
603
|
+
|
|
604
|
+
# Sampled validation (validate 1% of events)
|
|
605
|
+
validation_mode :sampled, sample_rate: 0.01
|
|
606
|
+
|
|
607
|
+
# Never validate (use with caution)
|
|
608
|
+
validation_mode :never
|
|
609
|
+
end
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
Use `:always` for user input and critical events. Use `:sampled` for high-frequency internal events. Use `:never` only for trusted, typed input.
|
|
613
|
+
|
|
614
|
+
### Validation Behavior
|
|
615
|
+
|
|
616
|
+
By default, invalid events raise `E11y::ValidationError`:
|
|
617
|
+
|
|
618
|
+
```ruby
|
|
619
|
+
OrderEvent.track(order_id: nil)
|
|
620
|
+
# => E11y::ValidationError
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
To handle validation errors gracefully:
|
|
624
|
+
|
|
625
|
+
```ruby
|
|
626
|
+
begin
|
|
627
|
+
OrderEvent.track(order_id: nil)
|
|
628
|
+
rescue E11y::ValidationError => e
|
|
629
|
+
Rails.logger.warn "Invalid event: #{e.message}"
|
|
630
|
+
end
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
---
|
|
634
|
+
|
|
635
|
+
## Metrics DSL
|
|
636
|
+
|
|
637
|
+
Define Prometheus metrics alongside events.
|
|
638
|
+
|
|
639
|
+
### Basic Example
|
|
640
|
+
|
|
641
|
+
```ruby
|
|
19
642
|
class OrderPaidEvent < E11y::Event::Base
|
|
20
643
|
schema do
|
|
21
|
-
required(:order_id).filled(:
|
|
644
|
+
required(:order_id).filled(:string)
|
|
22
645
|
required(:amount).filled(:float)
|
|
646
|
+
required(:currency).filled(:string)
|
|
23
647
|
end
|
|
24
648
|
|
|
25
|
-
|
|
26
|
-
|
|
649
|
+
metrics do
|
|
650
|
+
# Counter: Track number of paid orders
|
|
651
|
+
counter :orders_total, tags: [:currency]
|
|
652
|
+
|
|
653
|
+
# Histogram: Track order amount distribution
|
|
654
|
+
histogram :order_amount,
|
|
655
|
+
value: :amount,
|
|
656
|
+
tags: [:currency],
|
|
657
|
+
buckets: [10, 50, 100, 500, 1000]
|
|
658
|
+
|
|
659
|
+
# Gauge: Track active orders
|
|
660
|
+
gauge :active_orders, value: :active_count
|
|
661
|
+
end
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
# One track() call = event + metrics
|
|
665
|
+
OrderPaidEvent.track(order_id: "123", amount: 99.99, currency: "USD")
|
|
666
|
+
# => orders_total{currency="USD"} +1
|
|
667
|
+
# => order_amount{currency="USD"} observe 99.99
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
### Metric Types
|
|
671
|
+
|
|
672
|
+
**Counter** - Monotonically increasing value:
|
|
673
|
+
```ruby
|
|
674
|
+
metrics do
|
|
675
|
+
counter :orders_total, tags: [:currency, :status]
|
|
676
|
+
end
|
|
677
|
+
# => orders_total{currency="USD", status="paid"} 42
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
**Histogram** - Distribution of values:
|
|
681
|
+
```ruby
|
|
682
|
+
metrics do
|
|
683
|
+
histogram :order_amount,
|
|
684
|
+
value: :amount,
|
|
685
|
+
tags: [:currency],
|
|
686
|
+
buckets: [10, 50, 100, 500, 1000]
|
|
27
687
|
end
|
|
688
|
+
# => order_amount_bucket{currency="USD", le="100"} 15
|
|
689
|
+
```
|
|
28
690
|
|
|
29
|
-
|
|
30
|
-
|
|
691
|
+
**Gauge** - Arbitrary value that can go up or down:
|
|
692
|
+
```ruby
|
|
693
|
+
metrics do
|
|
694
|
+
gauge :queue_depth, value: :size, tags: [:queue_name]
|
|
695
|
+
end
|
|
696
|
+
# => queue_depth{queue_name="emails"} 37
|
|
31
697
|
```
|
|
32
698
|
|
|
33
|
-
|
|
699
|
+
### How It Works
|
|
700
|
+
|
|
701
|
+
1. Define metrics in event class
|
|
702
|
+
2. Metrics registered in `E11y::Metrics::Registry` at boot time
|
|
703
|
+
3. When `track()` is called, metrics are automatically updated
|
|
704
|
+
4. Metrics exported via Yabeda adapter (Prometheus format)
|
|
34
705
|
|
|
35
|
-
|
|
36
|
-
- 📐 **Convention over Configuration** - Smart defaults from event names
|
|
37
|
-
- 📊 **Type-Safe Events** - Declarative schemas with dry-schema validation
|
|
38
|
-
- 🔄 **Event Versioning** - Built-in version support for schema evolution
|
|
39
|
-
- 🎭 **Severity Levels** - Auto-detection from event names
|
|
40
|
-
- 🔌 **Pluggable Adapters** - Loki, Sentry, OpenTelemetry, File, Stdout, Memory
|
|
41
|
-
- 📦 **Future Ready** - Architecture designed for Phase 2+ features:
|
|
42
|
-
- Request-Scoped Debug Buffering
|
|
43
|
-
- Pattern-Based Metrics (Prometheus/Yabeda)
|
|
44
|
-
- PII Filtering & Audit Trails (GDPR/SOC2)
|
|
45
|
-
- Rate Limiting & Cardinality Protection
|
|
46
|
-
- OpenTelemetry Integration
|
|
47
|
-
- Rails/Sidekiq Integration
|
|
706
|
+
---
|
|
48
707
|
|
|
49
|
-
##
|
|
708
|
+
## Adapters
|
|
50
709
|
|
|
51
|
-
|
|
52
|
-
- [Implementation Plan](docs/IMPLEMENTATION_PLAN.md)
|
|
53
|
-
- [Architecture Decisions (ADRs)](docs/)
|
|
54
|
-
- [Use Cases](docs/use_cases/)
|
|
55
|
-
- [API Reference](https://e11y.dev/api)
|
|
710
|
+
E11y supports multiple adapters for different backends.
|
|
56
711
|
|
|
57
|
-
|
|
712
|
+
| Adapter | Purpose | Batching | Use Case |
|
|
713
|
+
|---------|---------|----------|----------|
|
|
714
|
+
| **Loki** | Log aggregation (Grafana) | Yes | Production logs |
|
|
715
|
+
| **Sentry** | Error tracking | Via SDK | Error monitoring |
|
|
716
|
+
| **OpenTelemetry** | OTLP export | Yes | Distributed tracing |
|
|
717
|
+
| **Yabeda** | Prometheus metrics | N/A | Metrics export |
|
|
718
|
+
| **File** | Local logs | Yes | Development, CI |
|
|
719
|
+
| **Stdout** | Console output | No | Development |
|
|
720
|
+
| **InMemory** | Test buffer | No | Testing |
|
|
58
721
|
|
|
59
|
-
|
|
722
|
+
### Configuration
|
|
60
723
|
|
|
61
724
|
```ruby
|
|
62
|
-
|
|
725
|
+
# config/initializers/e11y.rb
|
|
726
|
+
E11y.configure do |config|
|
|
727
|
+
# Configure adapters
|
|
728
|
+
config.adapters[:logs] = E11y::Adapters::Loki.new(
|
|
729
|
+
url: ENV["LOKI_URL"],
|
|
730
|
+
batch_size: 100,
|
|
731
|
+
batch_timeout: 5,
|
|
732
|
+
compress: true
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
config.adapters[:errors_tracker] = E11y::Adapters::Sentry.new(
|
|
736
|
+
dsn: ENV["SENTRY_DSN"]
|
|
737
|
+
)
|
|
738
|
+
|
|
739
|
+
config.adapters[:stdout] = E11y::Adapters::Stdout.new(
|
|
740
|
+
format: :pretty
|
|
741
|
+
)
|
|
742
|
+
end
|
|
63
743
|
```
|
|
64
744
|
|
|
65
|
-
|
|
745
|
+
### Adapter Routing by Severity
|
|
66
746
|
|
|
67
|
-
|
|
68
|
-
|
|
747
|
+
Events are routed to adapters based on severity. The default mapping:
|
|
748
|
+
|
|
749
|
+
- `error`, `fatal` → `[:logs, :errors_tracker]`
|
|
750
|
+
- Other severities → `[:logs]`
|
|
751
|
+
|
|
752
|
+
Override routing explicitly:
|
|
753
|
+
|
|
754
|
+
```ruby
|
|
755
|
+
class CustomEvent < E11y::Event::Base
|
|
756
|
+
adapters :logs, :stdout # Explicit routing
|
|
757
|
+
end
|
|
69
758
|
```
|
|
70
759
|
|
|
71
|
-
|
|
760
|
+
### Custom Adapters
|
|
72
761
|
|
|
73
|
-
|
|
74
|
-
|
|
762
|
+
Implement the `write` method:
|
|
763
|
+
|
|
764
|
+
```ruby
|
|
765
|
+
class MyBackendAdapter < E11y::Adapters::Base
|
|
766
|
+
def write(event_data)
|
|
767
|
+
# event_data contains event_name, payload, severity, timestamp, etc.
|
|
768
|
+
MyBackend.send_event(event_data)
|
|
769
|
+
end
|
|
770
|
+
end
|
|
771
|
+
|
|
772
|
+
E11y.configure do |config|
|
|
773
|
+
config.adapters[:my_backend] = MyBackendAdapter.new
|
|
774
|
+
end
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
---
|
|
778
|
+
|
|
779
|
+
## PII Filtering
|
|
780
|
+
|
|
781
|
+
E11y provides PII filtering capabilities for sensitive data.
|
|
782
|
+
|
|
783
|
+
### Rails Integration
|
|
784
|
+
|
|
785
|
+
E11y can respect `Rails.application.config.filter_parameters` when configured:
|
|
786
|
+
|
|
787
|
+
```ruby
|
|
788
|
+
# config/application.rb
|
|
789
|
+
config.filter_parameters += [:password, :email, :ssn, :credit_card]
|
|
790
|
+
|
|
791
|
+
# E11y will filter these fields when PII filtering middleware is enabled
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
### Explicit PII Strategies
|
|
795
|
+
|
|
796
|
+
Configure PII filtering per event:
|
|
797
|
+
|
|
798
|
+
```ruby
|
|
799
|
+
class PaymentEvent < E11y::Event::Base
|
|
800
|
+
contains_pii true
|
|
801
|
+
|
|
802
|
+
pii_filtering do
|
|
803
|
+
masks :card_number # Replace with "[FILTERED]"
|
|
804
|
+
hashes :user_email # SHA256 hash (searchable)
|
|
805
|
+
allows :amount # No filtering
|
|
806
|
+
end
|
|
807
|
+
end
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
Available strategies:
|
|
811
|
+
- `masks` - Replace with "[FILTERED]"
|
|
812
|
+
- `hashes` - SHA256 hash (preserves searchability)
|
|
813
|
+
- `partials` - Show first/last characters
|
|
814
|
+
- `redacts` - Remove completely
|
|
815
|
+
- `allows` - No filtering
|
|
816
|
+
|
|
817
|
+
---
|
|
818
|
+
|
|
819
|
+
## Adaptive Sampling
|
|
820
|
+
|
|
821
|
+
E11y supports adaptive sampling to reduce event volume during high load.
|
|
822
|
+
|
|
823
|
+
Sampling strategies:
|
|
824
|
+
1. **Error-based** - Increase sampling during error spikes
|
|
825
|
+
2. **Load-based** - Reduce sampling under high throughput
|
|
826
|
+
3. **Value-based** - Always sample high-value events
|
|
827
|
+
|
|
828
|
+
### Configuration
|
|
829
|
+
|
|
830
|
+
```ruby
|
|
831
|
+
E11y.configure do |config|
|
|
832
|
+
config.pipeline.use E11y::Middleware::Sampling,
|
|
833
|
+
default_sample_rate: 0.1,
|
|
834
|
+
|
|
835
|
+
# Error-based sampling
|
|
836
|
+
error_based_adaptive: true,
|
|
837
|
+
error_spike_config: {
|
|
838
|
+
window: 60,
|
|
839
|
+
absolute_threshold: 100,
|
|
840
|
+
relative_threshold: 3.0,
|
|
841
|
+
spike_duration: 300
|
|
842
|
+
},
|
|
843
|
+
|
|
844
|
+
# Load-based sampling
|
|
845
|
+
load_based_adaptive: true,
|
|
846
|
+
load_monitor_config: {
|
|
847
|
+
window: 60,
|
|
848
|
+
thresholds: {
|
|
849
|
+
normal: 1_000,
|
|
850
|
+
high: 10_000,
|
|
851
|
+
very_high: 50_000,
|
|
852
|
+
overload: 100_000
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
end
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
### Value-Based Sampling
|
|
859
|
+
|
|
860
|
+
Sample events based on payload values:
|
|
861
|
+
|
|
862
|
+
```ruby
|
|
863
|
+
class PaymentEvent < E11y::Event::Base
|
|
864
|
+
sample_by_value :amount, greater_than: 1000 # Always sample large payments
|
|
865
|
+
end
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
---
|
|
869
|
+
|
|
870
|
+
## Presets
|
|
871
|
+
|
|
872
|
+
E11y provides presets for common event types.
|
|
873
|
+
|
|
874
|
+
### HighValueEvent
|
|
875
|
+
|
|
876
|
+
For financial transactions and critical business events:
|
|
877
|
+
|
|
878
|
+
```ruby
|
|
879
|
+
class PaymentProcessedEvent < E11y::Event::Base
|
|
880
|
+
include E11y::Presets::HighValueEvent
|
|
881
|
+
|
|
882
|
+
schema do
|
|
883
|
+
required(:transaction_id).filled(:string)
|
|
884
|
+
required(:amount).filled(:decimal)
|
|
885
|
+
end
|
|
886
|
+
end
|
|
887
|
+
|
|
888
|
+
# Configured with:
|
|
889
|
+
# - severity: :success
|
|
890
|
+
# - sample_rate: 1.0 (always sampled)
|
|
891
|
+
# - adapters: [:logs, :errors_tracker]
|
|
892
|
+
# - rate_limit: unlimited
|
|
75
893
|
```
|
|
76
894
|
|
|
77
|
-
|
|
895
|
+
### AuditEvent
|
|
78
896
|
|
|
79
|
-
|
|
897
|
+
For compliance and audit trails:
|
|
80
898
|
|
|
81
899
|
```ruby
|
|
82
|
-
|
|
83
|
-
|
|
900
|
+
class UserDeletedEvent < E11y::Event::Base
|
|
901
|
+
include E11y::Presets::AuditEvent
|
|
902
|
+
|
|
84
903
|
schema do
|
|
85
|
-
required(:user_id).filled(:
|
|
86
|
-
required(:
|
|
904
|
+
required(:user_id).filled(:string)
|
|
905
|
+
required(:deleted_by).filled(:string)
|
|
87
906
|
end
|
|
88
|
-
# Severity auto-detected: :info
|
|
89
|
-
# Adapters auto-selected: [:loki]
|
|
90
907
|
end
|
|
91
908
|
|
|
92
|
-
#
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
909
|
+
# Configured with:
|
|
910
|
+
# - sample_rate: 1.0 (never sampled)
|
|
911
|
+
# - rate_limit: unlimited
|
|
912
|
+
# Note: Set severity based on event criticality
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
### DebugEvent
|
|
916
|
+
|
|
917
|
+
For development and troubleshooting:
|
|
918
|
+
|
|
919
|
+
```ruby
|
|
920
|
+
class SlowQueryEvent < E11y::Event::Base
|
|
921
|
+
include E11y::Presets::DebugEvent
|
|
97
922
|
|
|
98
923
|
schema do
|
|
99
|
-
required(:
|
|
100
|
-
required(:
|
|
924
|
+
required(:query).filled(:string)
|
|
925
|
+
required(:duration_ms).filled(:integer)
|
|
926
|
+
end
|
|
927
|
+
end
|
|
928
|
+
|
|
929
|
+
# Configured with:
|
|
930
|
+
# - severity: :debug
|
|
931
|
+
# - adapters: [:logs]
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
---
|
|
935
|
+
|
|
936
|
+
## Rails Integration
|
|
937
|
+
|
|
938
|
+
E11y integrates with Rails via Railtie.
|
|
939
|
+
|
|
940
|
+
### Auto-Instrumented Components
|
|
941
|
+
|
|
942
|
+
E11y includes event definitions for common Rails components:
|
|
943
|
+
|
|
944
|
+
| Component | Event Classes | Location |
|
|
945
|
+
|-----------|--------------|----------|
|
|
946
|
+
| **HTTP Requests** | Request, StartProcessing, Redirect, SendFile | `lib/e11y/events/rails/http/` |
|
|
947
|
+
| **ActiveRecord** | Query | `lib/e11y/events/rails/database/` |
|
|
948
|
+
| **ActiveJob** | Enqueued, Started, Completed, Failed, Scheduled | `lib/e11y/events/rails/job/` |
|
|
949
|
+
| **Cache** | Read, Write, Delete | `lib/e11y/events/rails/cache/` |
|
|
950
|
+
| **View** | Render | `lib/e11y/events/rails/view/` |
|
|
951
|
+
|
|
952
|
+
Enable instrumentation in your configuration as needed.
|
|
953
|
+
|
|
954
|
+
### Sidekiq Integration
|
|
955
|
+
|
|
956
|
+
E11y includes Sidekiq instrumentation support. Configure in your initializer:
|
|
957
|
+
|
|
958
|
+
```ruby
|
|
959
|
+
E11y.configure do |config|
|
|
960
|
+
config.rails_instrumentation.enabled = true
|
|
961
|
+
end
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
---
|
|
965
|
+
|
|
966
|
+
## Testing
|
|
967
|
+
|
|
968
|
+
Use the InMemory adapter for testing.
|
|
969
|
+
|
|
970
|
+
### Setup
|
|
971
|
+
|
|
972
|
+
```ruby
|
|
973
|
+
# spec/rails_helper.rb or spec/spec_helper.rb
|
|
974
|
+
RSpec.configure do |config|
|
|
975
|
+
config.before(:each) do
|
|
976
|
+
# Configure InMemory adapter for tests
|
|
977
|
+
E11y.configure do |e11y_config|
|
|
978
|
+
e11y_config.adapters[:test] = E11y::Adapters::InMemory.new
|
|
979
|
+
end
|
|
980
|
+
end
|
|
981
|
+
|
|
982
|
+
config.after(:each) do
|
|
983
|
+
# Clear events after each test
|
|
984
|
+
E11y.configuration.adapters[:test]&.clear!
|
|
101
985
|
end
|
|
102
986
|
end
|
|
987
|
+
```
|
|
103
988
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
989
|
+
### Test Events
|
|
990
|
+
|
|
991
|
+
```ruby
|
|
992
|
+
RSpec.describe OrdersController do
|
|
993
|
+
let(:test_adapter) { E11y.configuration.adapters[:test] }
|
|
994
|
+
|
|
995
|
+
it "tracks order creation" do
|
|
996
|
+
post :create, params: { item: "Book", price: 29.99 }
|
|
997
|
+
|
|
998
|
+
events = test_adapter.events
|
|
999
|
+
expect(events).to include(
|
|
1000
|
+
a_hash_including(
|
|
1001
|
+
event_name: "OrderCreatedEvent",
|
|
1002
|
+
payload: hash_including(item: "Book", price: 29.99)
|
|
1003
|
+
)
|
|
1004
|
+
)
|
|
1005
|
+
end
|
|
1006
|
+
|
|
1007
|
+
it "does not track payment for free orders" do
|
|
1008
|
+
post :create, params: { item: "Free Book", price: 0 }
|
|
1009
|
+
|
|
1010
|
+
payment_events = test_adapter.events.select { |e| e[:event_name] == "PaymentProcessedEvent" }
|
|
1011
|
+
expect(payment_events).to be_empty
|
|
1012
|
+
end
|
|
1013
|
+
end
|
|
107
1014
|
```
|
|
108
1015
|
|
|
109
|
-
###
|
|
1016
|
+
### InMemory Adapter API
|
|
1017
|
+
|
|
1018
|
+
```ruby
|
|
1019
|
+
test_adapter = E11y::Adapters::InMemory.new
|
|
1020
|
+
|
|
1021
|
+
# Get all events
|
|
1022
|
+
test_adapter.events # => Array<Hash>
|
|
110
1023
|
|
|
111
|
-
|
|
1024
|
+
# Count events
|
|
1025
|
+
test_adapter.event_count # => Integer
|
|
112
1026
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
- `*Paid*`, `*Success*`, `*Completed*` → `:success`
|
|
116
|
-
- `*Warn*`, `*Warning*` → `:warn`
|
|
117
|
-
- Default → `:info`
|
|
1027
|
+
# Find last event
|
|
1028
|
+
test_adapter.last_event # => Hash
|
|
118
1029
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
1030
|
+
# Clear all events
|
|
1031
|
+
test_adapter.clear!
|
|
1032
|
+
```
|
|
122
1033
|
|
|
123
|
-
|
|
1034
|
+
---
|
|
124
1035
|
|
|
125
|
-
##
|
|
1036
|
+
## Configuration
|
|
126
1037
|
|
|
127
|
-
|
|
1038
|
+
### Basic Configuration
|
|
1039
|
+
|
|
1040
|
+
```ruby
|
|
1041
|
+
# config/initializers/e11y.rb
|
|
1042
|
+
E11y.configure do |config|
|
|
1043
|
+
# Service identification
|
|
1044
|
+
config.service_name = "myapp"
|
|
1045
|
+
config.environment = Rails.env
|
|
1046
|
+
|
|
1047
|
+
# Configure adapters
|
|
1048
|
+
config.adapters[:logs] = E11y::Adapters::Loki.new(
|
|
1049
|
+
url: ENV["LOKI_URL"],
|
|
1050
|
+
batch_size: 100,
|
|
1051
|
+
batch_timeout: 5,
|
|
1052
|
+
compress: true
|
|
1053
|
+
)
|
|
1054
|
+
|
|
1055
|
+
config.adapters[:errors_tracker] = E11y::Adapters::Sentry.new(
|
|
1056
|
+
dsn: ENV["SENTRY_DSN"]
|
|
1057
|
+
)
|
|
1058
|
+
|
|
1059
|
+
# Default retention period
|
|
1060
|
+
config.default_retention_period = 30.days
|
|
1061
|
+
end
|
|
1062
|
+
```
|
|
1063
|
+
|
|
1064
|
+
### Middleware Pipeline
|
|
1065
|
+
|
|
1066
|
+
Configure middleware for sampling, PII filtering, and more:
|
|
1067
|
+
|
|
1068
|
+
```ruby
|
|
1069
|
+
E11y.configure do |config|
|
|
1070
|
+
# Sampling middleware
|
|
1071
|
+
config.pipeline.use E11y::Middleware::Sampling,
|
|
1072
|
+
default_sample_rate: 0.1,
|
|
1073
|
+
error_based_adaptive: true,
|
|
1074
|
+
load_based_adaptive: true
|
|
1075
|
+
|
|
1076
|
+
# PII filtering middleware
|
|
1077
|
+
config.pipeline.use E11y::Middleware::PIIFilter
|
|
1078
|
+
|
|
1079
|
+
# Trace context middleware
|
|
1080
|
+
config.pipeline.use E11y::Middleware::TraceContext
|
|
1081
|
+
end
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
---
|
|
1085
|
+
|
|
1086
|
+
## Performance
|
|
1087
|
+
|
|
1088
|
+
### Design Principles
|
|
1089
|
+
|
|
1090
|
+
E11y is designed for performance:
|
|
1091
|
+
|
|
1092
|
+
- **Hash-based events** - Events are Hashes, not objects, minimizing allocations
|
|
1093
|
+
- **Configurable validation** - Choose validation mode based on performance needs
|
|
1094
|
+
- **Batching** - Loki and other adapters support batching to reduce network overhead
|
|
1095
|
+
- **Sampling** - Adaptive sampling reduces event volume under high load
|
|
1096
|
+
|
|
1097
|
+
See `benchmarks/` directory for detailed performance tests.
|
|
1098
|
+
|
|
1099
|
+
### Cardinality Protection
|
|
1100
|
+
|
|
1101
|
+
Optional cardinality protection prevents high-cardinality labels from overwhelming metrics systems:
|
|
1102
|
+
|
|
1103
|
+
```ruby
|
|
1104
|
+
E11y::Adapters::Loki.new(
|
|
1105
|
+
url: "http://loki:3100",
|
|
1106
|
+
enable_cardinality_protection: true,
|
|
1107
|
+
max_label_cardinality: 100
|
|
1108
|
+
)
|
|
1109
|
+
```
|
|
1110
|
+
|
|
1111
|
+
When enabled, high-cardinality labels (e.g., `user_id`, `order_id`) are filtered from metric tags.
|
|
1112
|
+
|
|
1113
|
+
---
|
|
1114
|
+
|
|
1115
|
+
## Documentation
|
|
1116
|
+
|
|
1117
|
+
Additional documentation is available in the `docs/` directory:
|
|
1118
|
+
|
|
1119
|
+
- Architecture Decision Records (ADRs)
|
|
1120
|
+
- Use Cases
|
|
1121
|
+
- Configuration guides
|
|
1122
|
+
- Performance benchmarks
|
|
1123
|
+
|
|
1124
|
+
---
|
|
1125
|
+
|
|
1126
|
+
## Development
|
|
1127
|
+
|
|
1128
|
+
### Running Tests
|
|
1129
|
+
|
|
1130
|
+
E11y has three test suites with different requirements:
|
|
1131
|
+
|
|
1132
|
+
#### Quick Commands (recommended)
|
|
1133
|
+
|
|
1134
|
+
```bash
|
|
1135
|
+
# Using rake tasks
|
|
1136
|
+
rake spec:unit # Unit tests (~1672 examples, includes all e11y tests)
|
|
1137
|
+
rake spec:integration # Integration tests (~36 examples, requires Rails)
|
|
1138
|
+
rake spec:railtie # Railtie tests (~21 examples, Rails initialization)
|
|
1139
|
+
rake spec:all # All tests (~1729 examples, unit + integration + railtie)
|
|
1140
|
+
rake spec:benchmark # Benchmark tests (~44 examples, slow)
|
|
1141
|
+
rake spec:coverage # With coverage
|
|
1142
|
+
```
|
|
1143
|
+
|
|
1144
|
+
#### Manual Commands
|
|
128
1145
|
|
|
129
1146
|
```bash
|
|
130
|
-
#
|
|
131
|
-
|
|
1147
|
+
# Unit tests (fast, no Rails required)
|
|
1148
|
+
bundle exec rspec --exclude-pattern 'spec/{integration,e11y/railtie_integration_spec.rb}/**/*_spec.rb'
|
|
132
1149
|
|
|
133
|
-
#
|
|
1150
|
+
# Integration tests (requires: bundle install --with integration)
|
|
1151
|
+
INTEGRATION=true bundle exec rspec spec/integration/
|
|
1152
|
+
|
|
1153
|
+
# Railtie integration tests
|
|
1154
|
+
bundle exec rspec spec/e11y/railtie_integration_spec.rb --tag railtie_integration
|
|
1155
|
+
|
|
1156
|
+
# All tests
|
|
134
1157
|
bundle exec rspec
|
|
135
1158
|
|
|
136
|
-
#
|
|
1159
|
+
# Benchmarks (optional)
|
|
1160
|
+
bundle exec rspec --tag benchmark
|
|
1161
|
+
```
|
|
1162
|
+
|
|
1163
|
+
### Test Suite Overview
|
|
1164
|
+
|
|
1165
|
+
- **Unit tests** (~1672 examples, ~30s): Core logic, all e11y/* tests
|
|
1166
|
+
- **Integration tests** (~36 examples, ~5s): Rails, ActiveJob, Sidekiq integration
|
|
1167
|
+
- **Railtie tests** (~21 examples, ~2s): Rails initialization and configuration
|
|
1168
|
+
- **Benchmark tests** (~44 examples, ~30s): Performance tests (run with `rake spec:benchmark`)
|
|
1169
|
+
|
|
1170
|
+
### Other Development Commands
|
|
1171
|
+
|
|
1172
|
+
```bash
|
|
1173
|
+
# Linting
|
|
137
1174
|
bundle exec rubocop
|
|
138
1175
|
|
|
139
|
-
#
|
|
140
|
-
bundle exec
|
|
141
|
-
|
|
1176
|
+
# Auto-fix linting issues
|
|
1177
|
+
bundle exec rubocop -a
|
|
1178
|
+
|
|
1179
|
+
# Interactive console
|
|
1180
|
+
rake e11y:console
|
|
142
1181
|
|
|
143
1182
|
# Generate documentation
|
|
144
|
-
|
|
145
|
-
```
|
|
1183
|
+
rake e11y:docs
|
|
146
1184
|
|
|
147
|
-
|
|
1185
|
+
# Security audit
|
|
1186
|
+
rake e11y:audit
|
|
148
1187
|
|
|
149
|
-
|
|
1188
|
+
# Run benchmarks
|
|
1189
|
+
rake e11y:benchmark
|
|
1190
|
+
```
|
|
150
1191
|
|
|
151
|
-
|
|
1192
|
+
---
|
|
152
1193
|
|
|
153
|
-
|
|
1194
|
+
## Contributing
|
|
154
1195
|
|
|
155
|
-
|
|
1196
|
+
Bug reports and pull requests are welcome at https://github.com/arturseletskiy/e11y.
|
|
156
1197
|
|
|
157
|
-
|
|
1198
|
+
Contributing workflow:
|
|
1199
|
+
1. Fork the repository
|
|
1200
|
+
2. Create a feature branch
|
|
1201
|
+
3. Run tests: `rake spec:all`
|
|
1202
|
+
4. Run linter: `bundle exec rubocop`
|
|
1203
|
+
5. Submit a pull request
|
|
158
1204
|
|
|
159
|
-
|
|
1205
|
+
**Note:** Performance benchmarks are excluded from default test runs due to CI environment variability. Run them explicitly with `--tag benchmark` when needed.
|
|
160
1206
|
|
|
161
|
-
|
|
1207
|
+
---
|
|
162
1208
|
|
|
163
|
-
|
|
164
|
-
- ✅ Phase 0: Gem Setup & Best Practices
|
|
165
|
-
- 🔄 Phase 1: Core Foundation (In Progress)
|
|
166
|
-
- ✅ Event::Base with zero-allocation pattern
|
|
167
|
-
- ✅ Convention-based configuration
|
|
168
|
-
- ✅ Schema validation (dry-schema)
|
|
169
|
-
- 🔄 Adaptive Buffer implementation
|
|
170
|
-
- ⏳ Middleware Pipeline
|
|
171
|
-
- ⏳ track() method with pipeline
|
|
1209
|
+
## License
|
|
172
1210
|
|
|
173
|
-
|
|
174
|
-
- ⏳ Phase 2: Core Features (PII, Adapters, Metrics)
|
|
175
|
-
- ⏳ Phase 3: Rails Integration
|
|
176
|
-
- ⏳ Phase 4: Production Hardening
|
|
177
|
-
- ⏳ Phase 5: Scale & Optimization
|
|
1211
|
+
MIT License. See [LICENSE.txt](LICENSE.txt) for details.
|
|
178
1212
|
|
|
179
|
-
|
|
1213
|
+
---
|