e11y 0.1.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 +7 -0
- data/.rspec +4 -0
- data/.rubocop.yml +69 -0
- data/CHANGELOG.md +26 -0
- data/CODE_OF_CONDUCT.md +64 -0
- data/LICENSE.txt +21 -0
- data/README.md +179 -0
- data/Rakefile +37 -0
- data/benchmarks/run_all.rb +33 -0
- data/config/README.md +83 -0
- data/config/loki-local-config.yaml +35 -0
- data/config/prometheus.yml +15 -0
- data/docker-compose.yml +78 -0
- data/docs/00-ICP-AND-TIMELINE.md +483 -0
- data/docs/01-SCALE-REQUIREMENTS.md +858 -0
- data/docs/ADR-001-architecture.md +2617 -0
- data/docs/ADR-002-metrics-yabeda.md +1395 -0
- data/docs/ADR-003-slo-observability.md +3337 -0
- data/docs/ADR-004-adapter-architecture.md +2385 -0
- data/docs/ADR-005-tracing-context.md +1372 -0
- data/docs/ADR-006-security-compliance.md +4143 -0
- data/docs/ADR-007-opentelemetry-integration.md +1385 -0
- data/docs/ADR-008-rails-integration.md +1911 -0
- data/docs/ADR-009-cost-optimization.md +2993 -0
- data/docs/ADR-010-developer-experience.md +2166 -0
- data/docs/ADR-011-testing-strategy.md +1836 -0
- data/docs/ADR-012-event-evolution.md +958 -0
- data/docs/ADR-013-reliability-error-handling.md +2750 -0
- data/docs/ADR-014-event-driven-slo.md +1533 -0
- data/docs/ADR-015-middleware-order.md +1061 -0
- data/docs/ADR-016-self-monitoring-slo.md +1234 -0
- data/docs/API-REFERENCE-L28.md +914 -0
- data/docs/COMPREHENSIVE-CONFIGURATION.md +2366 -0
- data/docs/IMPLEMENTATION_NOTES.md +2804 -0
- data/docs/IMPLEMENTATION_PLAN.md +1971 -0
- data/docs/IMPLEMENTATION_PLAN_ARCHITECTURE.md +586 -0
- data/docs/PLAN.md +148 -0
- data/docs/QUICK-START.md +934 -0
- data/docs/README.md +296 -0
- data/docs/design/00-memory-optimization.md +593 -0
- data/docs/guides/MIGRATION-L27-L28.md +692 -0
- data/docs/guides/PERFORMANCE-BENCHMARKS.md +434 -0
- data/docs/guides/README.md +44 -0
- data/docs/prd/01-overview-vision.md +440 -0
- data/docs/use_cases/README.md +119 -0
- data/docs/use_cases/UC-001-request-scoped-debug-buffering.md +813 -0
- data/docs/use_cases/UC-002-business-event-tracking.md +1953 -0
- data/docs/use_cases/UC-003-pattern-based-metrics.md +1627 -0
- data/docs/use_cases/UC-004-zero-config-slo-tracking.md +728 -0
- data/docs/use_cases/UC-005-sentry-integration.md +759 -0
- data/docs/use_cases/UC-006-trace-context-management.md +905 -0
- data/docs/use_cases/UC-007-pii-filtering.md +2648 -0
- data/docs/use_cases/UC-008-opentelemetry-integration.md +1153 -0
- data/docs/use_cases/UC-009-multi-service-tracing.md +1043 -0
- data/docs/use_cases/UC-010-background-job-tracking.md +1018 -0
- data/docs/use_cases/UC-011-rate-limiting.md +1906 -0
- data/docs/use_cases/UC-012-audit-trail.md +2301 -0
- data/docs/use_cases/UC-013-high-cardinality-protection.md +2127 -0
- data/docs/use_cases/UC-014-adaptive-sampling.md +1940 -0
- data/docs/use_cases/UC-015-cost-optimization.md +735 -0
- data/docs/use_cases/UC-016-rails-logger-migration.md +785 -0
- data/docs/use_cases/UC-017-local-development.md +867 -0
- data/docs/use_cases/UC-018-testing-events.md +1081 -0
- data/docs/use_cases/UC-019-tiered-storage-migration.md +562 -0
- data/docs/use_cases/UC-020-event-versioning.md +708 -0
- data/docs/use_cases/UC-021-error-handling-retry-dlq.md +956 -0
- data/docs/use_cases/UC-022-event-registry.md +648 -0
- data/docs/use_cases/backlog.md +226 -0
- data/e11y.gemspec +76 -0
- data/lib/e11y/adapters/adaptive_batcher.rb +207 -0
- data/lib/e11y/adapters/audit_encrypted.rb +239 -0
- data/lib/e11y/adapters/base.rb +580 -0
- data/lib/e11y/adapters/file.rb +224 -0
- data/lib/e11y/adapters/in_memory.rb +216 -0
- data/lib/e11y/adapters/loki.rb +333 -0
- data/lib/e11y/adapters/otel_logs.rb +203 -0
- data/lib/e11y/adapters/registry.rb +141 -0
- data/lib/e11y/adapters/sentry.rb +230 -0
- data/lib/e11y/adapters/stdout.rb +108 -0
- data/lib/e11y/adapters/yabeda.rb +370 -0
- data/lib/e11y/buffers/adaptive_buffer.rb +339 -0
- data/lib/e11y/buffers/base_buffer.rb +40 -0
- data/lib/e11y/buffers/request_scoped_buffer.rb +246 -0
- data/lib/e11y/buffers/ring_buffer.rb +267 -0
- data/lib/e11y/buffers.rb +14 -0
- data/lib/e11y/console.rb +122 -0
- data/lib/e11y/current.rb +48 -0
- data/lib/e11y/event/base.rb +894 -0
- data/lib/e11y/event/value_sampling_config.rb +84 -0
- data/lib/e11y/events/base_audit_event.rb +43 -0
- data/lib/e11y/events/base_payment_event.rb +33 -0
- data/lib/e11y/events/rails/cache/delete.rb +21 -0
- data/lib/e11y/events/rails/cache/read.rb +23 -0
- data/lib/e11y/events/rails/cache/write.rb +22 -0
- data/lib/e11y/events/rails/database/query.rb +45 -0
- data/lib/e11y/events/rails/http/redirect.rb +21 -0
- data/lib/e11y/events/rails/http/request.rb +26 -0
- data/lib/e11y/events/rails/http/send_file.rb +21 -0
- data/lib/e11y/events/rails/http/start_processing.rb +26 -0
- data/lib/e11y/events/rails/job/completed.rb +22 -0
- data/lib/e11y/events/rails/job/enqueued.rb +22 -0
- data/lib/e11y/events/rails/job/failed.rb +22 -0
- data/lib/e11y/events/rails/job/scheduled.rb +23 -0
- data/lib/e11y/events/rails/job/started.rb +22 -0
- data/lib/e11y/events/rails/log.rb +56 -0
- data/lib/e11y/events/rails/view/render.rb +23 -0
- data/lib/e11y/events.rb +18 -0
- data/lib/e11y/instruments/active_job.rb +201 -0
- data/lib/e11y/instruments/rails_instrumentation.rb +141 -0
- data/lib/e11y/instruments/sidekiq.rb +175 -0
- data/lib/e11y/logger/bridge.rb +205 -0
- data/lib/e11y/metrics/cardinality_protection.rb +172 -0
- data/lib/e11y/metrics/cardinality_tracker.rb +134 -0
- data/lib/e11y/metrics/registry.rb +234 -0
- data/lib/e11y/metrics/relabeling.rb +226 -0
- data/lib/e11y/metrics.rb +102 -0
- data/lib/e11y/middleware/audit_signing.rb +174 -0
- data/lib/e11y/middleware/base.rb +140 -0
- data/lib/e11y/middleware/event_slo.rb +167 -0
- data/lib/e11y/middleware/pii_filter.rb +266 -0
- data/lib/e11y/middleware/pii_filtering.rb +280 -0
- data/lib/e11y/middleware/rate_limiting.rb +214 -0
- data/lib/e11y/middleware/request.rb +163 -0
- data/lib/e11y/middleware/routing.rb +157 -0
- data/lib/e11y/middleware/sampling.rb +254 -0
- data/lib/e11y/middleware/slo.rb +168 -0
- data/lib/e11y/middleware/trace_context.rb +131 -0
- data/lib/e11y/middleware/validation.rb +118 -0
- data/lib/e11y/middleware/versioning.rb +132 -0
- data/lib/e11y/middleware.rb +12 -0
- data/lib/e11y/pii/patterns.rb +90 -0
- data/lib/e11y/pii.rb +13 -0
- data/lib/e11y/pipeline/builder.rb +155 -0
- data/lib/e11y/pipeline/zone_validator.rb +110 -0
- data/lib/e11y/pipeline.rb +12 -0
- data/lib/e11y/presets/audit_event.rb +65 -0
- data/lib/e11y/presets/debug_event.rb +34 -0
- data/lib/e11y/presets/high_value_event.rb +51 -0
- data/lib/e11y/presets.rb +19 -0
- data/lib/e11y/railtie.rb +138 -0
- data/lib/e11y/reliability/circuit_breaker.rb +216 -0
- data/lib/e11y/reliability/dlq/file_storage.rb +277 -0
- data/lib/e11y/reliability/dlq/filter.rb +117 -0
- data/lib/e11y/reliability/retry_handler.rb +207 -0
- data/lib/e11y/reliability/retry_rate_limiter.rb +117 -0
- data/lib/e11y/sampling/error_spike_detector.rb +225 -0
- data/lib/e11y/sampling/load_monitor.rb +161 -0
- data/lib/e11y/sampling/stratified_tracker.rb +92 -0
- data/lib/e11y/sampling/value_extractor.rb +82 -0
- data/lib/e11y/self_monitoring/buffer_monitor.rb +79 -0
- data/lib/e11y/self_monitoring/performance_monitor.rb +97 -0
- data/lib/e11y/self_monitoring/reliability_monitor.rb +146 -0
- data/lib/e11y/slo/event_driven.rb +150 -0
- data/lib/e11y/slo/tracker.rb +119 -0
- data/lib/e11y/version.rb +9 -0
- data/lib/e11y.rb +283 -0
- metadata +452 -0
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
# UC-016: Rails Logger Migration
|
|
2
|
+
|
|
3
|
+
**Status:** MVP Feature
|
|
4
|
+
**Complexity:** Beginner
|
|
5
|
+
**Setup Time:** 15-20 minutes
|
|
6
|
+
**Target Users:** All Developers, DevOps Teams
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 📋 Overview
|
|
11
|
+
|
|
12
|
+
### Problem Statement
|
|
13
|
+
|
|
14
|
+
**The migration challenge:**
|
|
15
|
+
```ruby
|
|
16
|
+
# ❌ BEFORE: Existing Rails.logger usage everywhere
|
|
17
|
+
# controllers/orders_controller.rb
|
|
18
|
+
Rails.logger.info "Order #{order.id} created by user #{current_user.id}"
|
|
19
|
+
|
|
20
|
+
# services/payment_service.rb
|
|
21
|
+
Rails.logger.debug "Charging card: #{card.last4}"
|
|
22
|
+
Rails.logger.error "Payment failed: #{error.message}"
|
|
23
|
+
|
|
24
|
+
# jobs/process_order_job.rb
|
|
25
|
+
Rails.logger.info "Processing order #{order_id}"
|
|
26
|
+
|
|
27
|
+
# Problems:
|
|
28
|
+
# 1. 1000+ Rails.logger calls across codebase
|
|
29
|
+
# 2. Can't just replace all at once (risky!)
|
|
30
|
+
# 3. Need gradual migration path
|
|
31
|
+
# 4. Must support both systems during transition
|
|
32
|
+
# 5. Don't want to lose existing logs
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### E11y Solution
|
|
36
|
+
|
|
37
|
+
**Gradual, safe migration strategy:**
|
|
38
|
+
```ruby
|
|
39
|
+
# ✅ AFTER: Coexistence mode (Phase 1)
|
|
40
|
+
E11y.configure do |config|
|
|
41
|
+
config.rails_logger do
|
|
42
|
+
# Intercept Rails.logger calls
|
|
43
|
+
intercept_rails_logger true
|
|
44
|
+
|
|
45
|
+
# Mirror to both systems during migration
|
|
46
|
+
mirror_to_rails_logger true
|
|
47
|
+
|
|
48
|
+
# Convert to structured events
|
|
49
|
+
auto_convert_to_events true
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Existing code works unchanged!
|
|
54
|
+
Rails.logger.info "Order created"
|
|
55
|
+
# → Sent to both Rails.logger AND E11y ✅
|
|
56
|
+
|
|
57
|
+
# New code uses E11y directly
|
|
58
|
+
Events::OrderCreated.track(order_id: order.id)
|
|
59
|
+
# → Only E11y (no duplication) ✅
|
|
60
|
+
|
|
61
|
+
# Phase 2: Turn off mirroring
|
|
62
|
+
config.mirror_to_rails_logger = false
|
|
63
|
+
# → All logs now go to E11y only ✅
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 🎯 Migration Strategy
|
|
69
|
+
|
|
70
|
+
> **Implementation:** See [ADR-008 Section 7: Rails Logger Bridge](../ADR-008-rails-integration.md#7-rails-logger-bridge) for Logger::Bridge architecture, dual logging, and 3-phase migration strategy (shadow → conversion → full).
|
|
71
|
+
|
|
72
|
+
### Phase 1: Shadow Mode (Week 1-2)
|
|
73
|
+
|
|
74
|
+
**E11y runs alongside Rails.logger, doesn't break anything:**
|
|
75
|
+
```ruby
|
|
76
|
+
# config/initializers/e11y.rb
|
|
77
|
+
E11y.configure do |config|
|
|
78
|
+
# Phase 1: Shadow mode
|
|
79
|
+
config.rails_logger do
|
|
80
|
+
# Intercept Rails.logger (but keep original too!)
|
|
81
|
+
intercept_rails_logger true
|
|
82
|
+
mirror_to_rails_logger true # ← Keep Rails.logger working!
|
|
83
|
+
|
|
84
|
+
# Auto-convert to E11y events
|
|
85
|
+
auto_convert_to_events true
|
|
86
|
+
|
|
87
|
+
# Map severity levels
|
|
88
|
+
severity_mapping do
|
|
89
|
+
debug -> :debug
|
|
90
|
+
info -> :info
|
|
91
|
+
warn -> :warn
|
|
92
|
+
error -> :error
|
|
93
|
+
fatal -> :fatal
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Send to both Stdout (Rails.logger) and Loki (E11y)
|
|
98
|
+
config.adapters = [
|
|
99
|
+
E11y::Adapters::StdoutAdapter.new, # Development
|
|
100
|
+
E11y::Adapters::LokiAdapter.new(...) # E11y backend
|
|
101
|
+
]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Existing code continues to work!
|
|
105
|
+
Rails.logger.info "User logged in"
|
|
106
|
+
# → Goes to BOTH:
|
|
107
|
+
# 1. Rails.logger (stdout, as before)
|
|
108
|
+
# 2. E11y (Loki, new!)
|
|
109
|
+
|
|
110
|
+
# Verification:
|
|
111
|
+
# - Rails logs still appear in stdout ✅
|
|
112
|
+
# - E11y logs appear in Grafana ✅
|
|
113
|
+
# - No errors, no breakage ✅
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### Phase 2: Gradual Conversion (Week 3-6)
|
|
119
|
+
|
|
120
|
+
**Replace Rails.logger with E11y events, one feature at a time:**
|
|
121
|
+
```ruby
|
|
122
|
+
# Step 1: Start with new features (safe!)
|
|
123
|
+
class OrdersController < ApplicationController
|
|
124
|
+
def create
|
|
125
|
+
order = Order.create!(order_params)
|
|
126
|
+
|
|
127
|
+
# ✅ NEW: Use E11y for new code
|
|
128
|
+
Events::OrderCreated.track(
|
|
129
|
+
order_id: order.id,
|
|
130
|
+
user_id: current_user.id,
|
|
131
|
+
amount: order.total
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
render json: order
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Step 2: Replace high-value areas (authentication, payments)
|
|
139
|
+
class SessionsController < ApplicationController
|
|
140
|
+
def create
|
|
141
|
+
# ❌ OLD: Rails.logger
|
|
142
|
+
# Rails.logger.info "User #{user.id} logged in from #{request.ip}"
|
|
143
|
+
|
|
144
|
+
# ✅ NEW: E11y structured event
|
|
145
|
+
Events::UserLoggedIn.track(
|
|
146
|
+
user_id: user.id,
|
|
147
|
+
ip_address: request.ip,
|
|
148
|
+
user_agent: request.user_agent
|
|
149
|
+
)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Step 3: Leave low-value areas as-is (for now)
|
|
154
|
+
class HealthController < ApplicationController
|
|
155
|
+
def show
|
|
156
|
+
# Keep Rails.logger for simple health checks (low priority)
|
|
157
|
+
Rails.logger.debug "Health check"
|
|
158
|
+
render json: { status: 'ok' }
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Progress tracking:
|
|
163
|
+
# - Week 3: Authentication (5 controllers) ✅
|
|
164
|
+
# - Week 4: Orders & Payments (10 controllers) ✅
|
|
165
|
+
# - Week 5: Background Jobs (15 jobs) ✅
|
|
166
|
+
# - Week 6: Core Services (20 services) ✅
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
### Phase 3: Full Migration (Week 7+)
|
|
172
|
+
|
|
173
|
+
**Turn off Rails.logger mirroring, E11y only:**
|
|
174
|
+
```ruby
|
|
175
|
+
# config/initializers/e11y.rb
|
|
176
|
+
E11y.configure do |config|
|
|
177
|
+
config.rails_logger do
|
|
178
|
+
intercept_rails_logger true
|
|
179
|
+
mirror_to_rails_logger false # ← Turn off mirroring!
|
|
180
|
+
|
|
181
|
+
# Auto-convert remaining Rails.logger calls
|
|
182
|
+
auto_convert_to_events true
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Now:
|
|
187
|
+
# - E11y events → E11y only ✅
|
|
188
|
+
# - Rails.logger calls → Auto-converted to E11y ✅
|
|
189
|
+
# - No more duplication ✅
|
|
190
|
+
|
|
191
|
+
# Optional: Deprecation warnings for remaining Rails.logger
|
|
192
|
+
config.rails_logger do
|
|
193
|
+
warn_on_rails_logger_usage true
|
|
194
|
+
# → Logs warning when Rails.logger is used
|
|
195
|
+
# "DEPRECATION: Rails.logger at app/controllers/users_controller.rb:42"
|
|
196
|
+
end
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## 💻 Implementation Examples
|
|
202
|
+
|
|
203
|
+
### Example 1: Auto-Conversion (Quick Start)
|
|
204
|
+
|
|
205
|
+
```ruby
|
|
206
|
+
# config/initializers/e11y.rb
|
|
207
|
+
E11y.configure do |config|
|
|
208
|
+
config.rails_logger do
|
|
209
|
+
# Intercept ALL Rails.logger calls
|
|
210
|
+
intercept_rails_logger true
|
|
211
|
+
|
|
212
|
+
# Auto-convert to E11y events
|
|
213
|
+
auto_convert_to_events true
|
|
214
|
+
|
|
215
|
+
# Extract structured data from log messages
|
|
216
|
+
extract_structured_data do
|
|
217
|
+
# Pattern: "Order 123 created by user 456"
|
|
218
|
+
pattern /Order (\d+) created by user (\d+)/ do |match|
|
|
219
|
+
{
|
|
220
|
+
event_name: 'order.created',
|
|
221
|
+
order_id: match[1],
|
|
222
|
+
user_id: match[2]
|
|
223
|
+
}
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Pattern: "Payment failed: Card declined"
|
|
227
|
+
pattern /Payment failed: (.+)/ do |match|
|
|
228
|
+
{
|
|
229
|
+
event_name: 'payment.failed',
|
|
230
|
+
error: match[1],
|
|
231
|
+
severity: :error
|
|
232
|
+
}
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Default: Generic log event
|
|
236
|
+
fallback do |message, severity|
|
|
237
|
+
{
|
|
238
|
+
event_name: 'rails.log',
|
|
239
|
+
message: message,
|
|
240
|
+
severity: severity
|
|
241
|
+
}
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Existing code (unchanged):
|
|
248
|
+
Rails.logger.info "Order 123 created by user 456"
|
|
249
|
+
|
|
250
|
+
# Auto-converted to:
|
|
251
|
+
Events::OrderCreated.track(
|
|
252
|
+
order_id: '123',
|
|
253
|
+
user_id: '456'
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# In Grafana:
|
|
257
|
+
# {event_name="order.created",order_id="123",user_id="456"}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
### Example 2: Manual Migration (Controllers)
|
|
263
|
+
|
|
264
|
+
```ruby
|
|
265
|
+
# === BEFORE ===
|
|
266
|
+
# app/controllers/orders_controller.rb
|
|
267
|
+
class OrdersController < ApplicationController
|
|
268
|
+
def create
|
|
269
|
+
Rails.logger.info "Creating order for user #{current_user.id}"
|
|
270
|
+
|
|
271
|
+
order = Order.create!(order_params)
|
|
272
|
+
|
|
273
|
+
Rails.logger.info "Order #{order.id} created with total #{order.total}"
|
|
274
|
+
|
|
275
|
+
render json: order
|
|
276
|
+
rescue => e
|
|
277
|
+
Rails.logger.error "Failed to create order: #{e.message}"
|
|
278
|
+
render json: { error: e.message }, status: :unprocessable_entity
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# === AFTER ===
|
|
283
|
+
# app/events/order_creation_started.rb
|
|
284
|
+
module Events
|
|
285
|
+
class OrderCreationStarted < E11y::Event::Base
|
|
286
|
+
schema do
|
|
287
|
+
required(:user_id).filled(:string)
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# app/events/order_created.rb
|
|
293
|
+
module Events
|
|
294
|
+
class OrderCreated < E11y::Event::Base
|
|
295
|
+
severity :success
|
|
296
|
+
|
|
297
|
+
schema do
|
|
298
|
+
required(:order_id).filled(:string)
|
|
299
|
+
required(:user_id).filled(:string)
|
|
300
|
+
required(:total).filled(:decimal)
|
|
301
|
+
required(:items_count).filled(:integer)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
metric :counter, name: 'orders.created.total', tags: [:user_segment]
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# app/events/order_creation_failed.rb
|
|
309
|
+
module Events
|
|
310
|
+
class OrderCreationFailed < E11y::Event::Base
|
|
311
|
+
severity :error
|
|
312
|
+
|
|
313
|
+
schema do
|
|
314
|
+
required(:user_id).filled(:string)
|
|
315
|
+
required(:error).filled(:string)
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# app/controllers/orders_controller.rb
|
|
321
|
+
class OrdersController < ApplicationController
|
|
322
|
+
def create
|
|
323
|
+
# ✅ Structured event (better than Rails.logger!)
|
|
324
|
+
Events::OrderCreationStarted.track(user_id: current_user.id)
|
|
325
|
+
|
|
326
|
+
order = Order.create!(order_params)
|
|
327
|
+
|
|
328
|
+
# ✅ Rich structured data + automatic metrics
|
|
329
|
+
Events::OrderCreated.track(
|
|
330
|
+
order_id: order.id,
|
|
331
|
+
user_id: current_user.id,
|
|
332
|
+
total: order.total,
|
|
333
|
+
items_count: order.items.count
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
render json: order
|
|
337
|
+
rescue => e
|
|
338
|
+
# ✅ Error tracking with context
|
|
339
|
+
Events::OrderCreationFailed.track(
|
|
340
|
+
user_id: current_user.id,
|
|
341
|
+
error: e.message
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
render json: { error: e.message }, status: :unprocessable_entity
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# Benefits:
|
|
349
|
+
# ✅ Structured data (can query by order_id, user_id)
|
|
350
|
+
# ✅ Automatic metrics (orders.created.total counter)
|
|
351
|
+
# ✅ Type-safe (schema validation)
|
|
352
|
+
# ✅ Searchable in Grafana
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
### Example 3: Background Jobs Migration
|
|
358
|
+
|
|
359
|
+
```ruby
|
|
360
|
+
# === BEFORE ===
|
|
361
|
+
# app/jobs/process_order_job.rb
|
|
362
|
+
class ProcessOrderJob < ApplicationJob
|
|
363
|
+
def perform(order_id)
|
|
364
|
+
Rails.logger.info "Starting order processing: #{order_id}"
|
|
365
|
+
|
|
366
|
+
order = Order.find(order_id)
|
|
367
|
+
|
|
368
|
+
Rails.logger.debug "Checking inventory for order #{order_id}"
|
|
369
|
+
check_inventory(order)
|
|
370
|
+
|
|
371
|
+
Rails.logger.debug "Capturing payment for order #{order_id}"
|
|
372
|
+
capture_payment(order)
|
|
373
|
+
|
|
374
|
+
Rails.logger.info "Order #{order_id} processed successfully"
|
|
375
|
+
rescue => e
|
|
376
|
+
Rails.logger.error "Order processing failed: #{order_id} - #{e.message}"
|
|
377
|
+
raise
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
# === AFTER ===
|
|
382
|
+
# app/jobs/process_order_job.rb
|
|
383
|
+
class ProcessOrderJob < ApplicationJob
|
|
384
|
+
def perform(order_id)
|
|
385
|
+
# E11y auto-tracks job start/end (UC-010)
|
|
386
|
+
# Just track business events!
|
|
387
|
+
|
|
388
|
+
order = Order.find(order_id)
|
|
389
|
+
|
|
390
|
+
Events::InventoryCheckStarted.track(order_id: order.id)
|
|
391
|
+
check_inventory(order)
|
|
392
|
+
Events::InventoryCheckCompleted.track(
|
|
393
|
+
order_id: order.id,
|
|
394
|
+
items_available: true
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
Events::PaymentCaptureStarted.track(order_id: order.id)
|
|
398
|
+
capture_payment(order)
|
|
399
|
+
Events::PaymentCaptured.track(
|
|
400
|
+
order_id: order.id,
|
|
401
|
+
amount: order.total,
|
|
402
|
+
severity: :success
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
rescue => e
|
|
406
|
+
Events::OrderProcessingFailed.track(
|
|
407
|
+
order_id: order_id,
|
|
408
|
+
error: e.message,
|
|
409
|
+
severity: :error
|
|
410
|
+
)
|
|
411
|
+
raise
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
# Benefits:
|
|
416
|
+
# ✅ Job lifecycle auto-tracked (start, end, retries)
|
|
417
|
+
# ✅ Trace ID preserved from enqueue
|
|
418
|
+
# ✅ Business events clearly separated
|
|
419
|
+
# ✅ Can build metrics/dashboards easily
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
### Example 4: Service Objects Migration
|
|
425
|
+
|
|
426
|
+
```ruby
|
|
427
|
+
# === BEFORE ===
|
|
428
|
+
# app/services/payment_service.rb
|
|
429
|
+
class PaymentService
|
|
430
|
+
def call(order)
|
|
431
|
+
Rails.logger.info "Processing payment for order #{order.id}"
|
|
432
|
+
|
|
433
|
+
Rails.logger.debug "Card: #{order.card.last4}"
|
|
434
|
+
Rails.logger.debug "Amount: #{order.total}"
|
|
435
|
+
|
|
436
|
+
result = StripeGateway.charge(
|
|
437
|
+
amount: order.total,
|
|
438
|
+
card: order.card.token
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
Rails.logger.info "Payment succeeded: #{result.id}"
|
|
442
|
+
|
|
443
|
+
result
|
|
444
|
+
rescue StripeGateway::Error => e
|
|
445
|
+
Rails.logger.error "Payment failed: #{e.message}"
|
|
446
|
+
raise
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
# === AFTER ===
|
|
451
|
+
# app/services/payment_service.rb
|
|
452
|
+
class PaymentService
|
|
453
|
+
def call(order)
|
|
454
|
+
Events::PaymentProcessingStarted.track(
|
|
455
|
+
order_id: order.id,
|
|
456
|
+
amount: order.total,
|
|
457
|
+
payment_method: 'stripe'
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
result = StripeGateway.charge(
|
|
461
|
+
amount: order.total,
|
|
462
|
+
card: order.card.token
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
Events::PaymentSucceeded.track(
|
|
466
|
+
order_id: order.id,
|
|
467
|
+
transaction_id: result.id,
|
|
468
|
+
amount: order.total,
|
|
469
|
+
card_last4: order.card.last4,
|
|
470
|
+
severity: :success
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
result
|
|
474
|
+
rescue StripeGateway::Error => e
|
|
475
|
+
Events::PaymentFailed.track(
|
|
476
|
+
order_id: order.id,
|
|
477
|
+
amount: order.total,
|
|
478
|
+
error_code: e.code,
|
|
479
|
+
error_message: e.message,
|
|
480
|
+
severity: :error
|
|
481
|
+
)
|
|
482
|
+
raise
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
# Benefits:
|
|
487
|
+
# ✅ No sensitive data logged (card details filtered)
|
|
488
|
+
# ✅ Structured data (can aggregate by error_code)
|
|
489
|
+
# ✅ Success tracking (severity: :success)
|
|
490
|
+
# ✅ Automatic metrics
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## 🔧 Configuration
|
|
496
|
+
|
|
497
|
+
### Migration Configuration
|
|
498
|
+
|
|
499
|
+
```ruby
|
|
500
|
+
# config/initializers/e11y.rb
|
|
501
|
+
E11y.configure do |config|
|
|
502
|
+
config.rails_logger do
|
|
503
|
+
# === PHASE 1: SHADOW MODE ===
|
|
504
|
+
# Intercept Rails.logger but keep original behavior
|
|
505
|
+
intercept_rails_logger true
|
|
506
|
+
mirror_to_rails_logger true # ← Both systems!
|
|
507
|
+
|
|
508
|
+
# === PHASE 2: GRADUAL CONVERSION ===
|
|
509
|
+
# Auto-convert Rails.logger to E11y events
|
|
510
|
+
auto_convert_to_events true
|
|
511
|
+
|
|
512
|
+
# Pattern extraction (parse log messages)
|
|
513
|
+
extract_patterns do
|
|
514
|
+
# Order events
|
|
515
|
+
pattern /Order (\d+) created/, event: 'order.created' do |match|
|
|
516
|
+
{ order_id: match[1] }
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
# User events
|
|
520
|
+
pattern /User (\d+) logged in/, event: 'user.logged_in' do |match|
|
|
521
|
+
{ user_id: match[1] }
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
# Payment events
|
|
525
|
+
pattern /Payment (\w+) for order (\d+)/, event: 'payment.status' do |match|
|
|
526
|
+
{ status: match[1], order_id: match[2] }
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
# Generic fallback
|
|
530
|
+
fallback event: 'rails.log' do |message, severity|
|
|
531
|
+
{ message: message, original_severity: severity }
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
# Severity mapping
|
|
536
|
+
severity_mapping do
|
|
537
|
+
debug -> :debug
|
|
538
|
+
info -> :info
|
|
539
|
+
warn -> :warn
|
|
540
|
+
error -> :error
|
|
541
|
+
fatal -> :fatal
|
|
542
|
+
unknown -> :warn
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
# === PHASE 3: FULL MIGRATION ===
|
|
546
|
+
# Turn off mirroring (E11y only!)
|
|
547
|
+
# mirror_to_rails_logger false
|
|
548
|
+
|
|
549
|
+
# Deprecation warnings
|
|
550
|
+
warn_on_rails_logger_usage true
|
|
551
|
+
deprecation_message "Please use E11y events instead of Rails.logger"
|
|
552
|
+
|
|
553
|
+
# Exceptions (still use Rails.logger)
|
|
554
|
+
ignore_callers [
|
|
555
|
+
/vendor\/bundle/, # Gems
|
|
556
|
+
/config\/initializers/, # Initializers
|
|
557
|
+
/health_check/ # Health checks
|
|
558
|
+
]
|
|
559
|
+
end
|
|
560
|
+
end
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
---
|
|
564
|
+
|
|
565
|
+
## 📊 Migration Progress Tracking
|
|
566
|
+
|
|
567
|
+
### Built-in Metrics
|
|
568
|
+
|
|
569
|
+
```ruby
|
|
570
|
+
# Automatic metrics for migration progress
|
|
571
|
+
# e11y_rails_logger_intercepted_total{severity} - Rails.logger calls intercepted
|
|
572
|
+
# e11y_rails_logger_converted_total{pattern} - Auto-converted to events
|
|
573
|
+
# e11y_rails_logger_fallback_total - Calls using fallback (not matched)
|
|
574
|
+
# e11y_direct_events_total{event_name} - Direct E11y.track calls
|
|
575
|
+
|
|
576
|
+
# Grafana Dashboard:
|
|
577
|
+
# Panel 1: Migration Progress
|
|
578
|
+
# (e11y_direct_events_total / (e11y_direct_events_total + e11y_rails_logger_intercepted_total)) * 100
|
|
579
|
+
|
|
580
|
+
# Panel 2: Rails.logger Usage (should decrease over time)
|
|
581
|
+
# sum(rate(e11y_rails_logger_intercepted_total[1h]))
|
|
582
|
+
|
|
583
|
+
# Panel 3: Pattern Coverage (how many logs are structured?)
|
|
584
|
+
# e11y_rails_logger_converted_total / e11y_rails_logger_intercepted_total
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
---
|
|
588
|
+
|
|
589
|
+
## 🧪 Testing
|
|
590
|
+
|
|
591
|
+
```ruby
|
|
592
|
+
# spec/support/e11y_migration_helper.rb
|
|
593
|
+
RSpec.configure do |config|
|
|
594
|
+
config.around(:each, :e11y_migration) do |example|
|
|
595
|
+
# Test both modes
|
|
596
|
+
|
|
597
|
+
# Test 1: Shadow mode (both systems)
|
|
598
|
+
E11y.configure do |c|
|
|
599
|
+
c.rails_logger.mirror_to_rails_logger = true
|
|
600
|
+
end
|
|
601
|
+
example.run
|
|
602
|
+
|
|
603
|
+
# Test 2: E11y only mode
|
|
604
|
+
E11y.configure do |c|
|
|
605
|
+
c.rails_logger.mirror_to_rails_logger = false
|
|
606
|
+
end
|
|
607
|
+
example.run
|
|
608
|
+
end
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
# spec/controllers/orders_controller_spec.rb
|
|
612
|
+
RSpec.describe OrdersController, :e11y_migration do
|
|
613
|
+
describe 'POST #create' do
|
|
614
|
+
it 'tracks order creation' do
|
|
615
|
+
# Works with both Rails.logger and E11y
|
|
616
|
+
expect {
|
|
617
|
+
post :create, params: { order: order_params }
|
|
618
|
+
}.to track_event('order.created')
|
|
619
|
+
end
|
|
620
|
+
end
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
# spec/migration/rails_logger_coverage_spec.rb
|
|
624
|
+
RSpec.describe 'Rails.logger migration coverage' do
|
|
625
|
+
it 'has converted all critical paths' do
|
|
626
|
+
# Check that critical areas don't use Rails.logger
|
|
627
|
+
critical_files = [
|
|
628
|
+
'app/controllers/orders_controller.rb',
|
|
629
|
+
'app/services/payment_service.rb',
|
|
630
|
+
'app/jobs/process_order_job.rb'
|
|
631
|
+
]
|
|
632
|
+
|
|
633
|
+
critical_files.each do |file|
|
|
634
|
+
content = File.read(Rails.root.join(file))
|
|
635
|
+
expect(content).not_to match(/Rails\.logger/)
|
|
636
|
+
end
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
it 'tracks migration progress' do
|
|
640
|
+
# Count Rails.logger usage
|
|
641
|
+
rails_logger_count = 0
|
|
642
|
+
e11y_track_count = 0
|
|
643
|
+
|
|
644
|
+
Dir['app/**/*.rb'].each do |file|
|
|
645
|
+
content = File.read(file)
|
|
646
|
+
rails_logger_count += content.scan(/Rails\.logger/).count
|
|
647
|
+
e11y_track_count += content.scan(/Events::\w+\.track/).count
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
# Expect at least 80% migrated
|
|
651
|
+
migration_pct = (e11y_track_count.to_f / (rails_logger_count + e11y_track_count)) * 100
|
|
652
|
+
expect(migration_pct).to be >= 80
|
|
653
|
+
end
|
|
654
|
+
end
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
---
|
|
658
|
+
|
|
659
|
+
## 💡 Best Practices
|
|
660
|
+
|
|
661
|
+
### ✅ DO
|
|
662
|
+
|
|
663
|
+
**1. Migrate in phases (safe!)**
|
|
664
|
+
```ruby
|
|
665
|
+
# ✅ GOOD: Gradual migration
|
|
666
|
+
# Week 1-2: Shadow mode (both systems)
|
|
667
|
+
# Week 3-6: Convert high-value areas
|
|
668
|
+
# Week 7+: Turn off mirroring
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
**2. Start with new features**
|
|
672
|
+
```ruby
|
|
673
|
+
# ✅ GOOD: New code uses E11y from day 1
|
|
674
|
+
class NewFeatureController < ApplicationController
|
|
675
|
+
def action
|
|
676
|
+
Events::NewFeatureUsed.track(...) # ← E11y!
|
|
677
|
+
end
|
|
678
|
+
end
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
**3. Convert high-value areas first**
|
|
682
|
+
```ruby
|
|
683
|
+
# ✅ GOOD: Priority order
|
|
684
|
+
# 1. Authentication (security)
|
|
685
|
+
# 2. Payments (money!)
|
|
686
|
+
# 3. Orders (business critical)
|
|
687
|
+
# 4. Background jobs (async visibility)
|
|
688
|
+
# 5. Everything else
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
**4. Use auto-conversion for long tail**
|
|
692
|
+
```ruby
|
|
693
|
+
# ✅ GOOD: Auto-convert remaining Rails.logger
|
|
694
|
+
config.rails_logger do
|
|
695
|
+
auto_convert_to_events true # Handles stragglers
|
|
696
|
+
end
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
---
|
|
700
|
+
|
|
701
|
+
### ❌ DON'T
|
|
702
|
+
|
|
703
|
+
**1. Don't migrate everything at once**
|
|
704
|
+
```ruby
|
|
705
|
+
# ❌ BAD: Big bang migration (risky!)
|
|
706
|
+
# - Replace all 1000+ Rails.logger calls in one PR
|
|
707
|
+
# - Deploy to production
|
|
708
|
+
# - Hope nothing breaks 🤞
|
|
709
|
+
|
|
710
|
+
# ✅ GOOD: Incremental migration
|
|
711
|
+
# - Week 1: Shadow mode
|
|
712
|
+
# - Week 2: 10 controllers
|
|
713
|
+
# - Week 3: 15 jobs
|
|
714
|
+
# - etc.
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
**2. Don't break existing functionality**
|
|
718
|
+
```ruby
|
|
719
|
+
# ❌ BAD: Turn off Rails.logger immediately
|
|
720
|
+
config.rails_logger.mirror_to_rails_logger = false
|
|
721
|
+
# → Existing code breaks! 💥
|
|
722
|
+
|
|
723
|
+
# ✅ GOOD: Keep mirroring during migration
|
|
724
|
+
config.rails_logger.mirror_to_rails_logger = true
|
|
725
|
+
# → Both systems work ✅
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
**3. Don't lose log context**
|
|
729
|
+
```ruby
|
|
730
|
+
# ❌ BAD: Unstructured conversion
|
|
731
|
+
Rails.logger.info "Order 123 created by user 456"
|
|
732
|
+
# → Events::RailsLog.track(message: "Order 123 created by user 456")
|
|
733
|
+
# Still unstructured! 😞
|
|
734
|
+
|
|
735
|
+
# ✅ GOOD: Extract structure
|
|
736
|
+
Events::OrderCreated.track(
|
|
737
|
+
order_id: '123',
|
|
738
|
+
user_id: '456'
|
|
739
|
+
)
|
|
740
|
+
# Queryable, structured! 🎉
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
---
|
|
744
|
+
|
|
745
|
+
## 📚 Related Use Cases
|
|
746
|
+
|
|
747
|
+
- **[UC-002: Business Event Tracking](./UC-002-business-event-tracking.md)** - E11y events basics
|
|
748
|
+
- **[UC-017: Local Development](./UC-017-local-development.md)** - Development setup
|
|
749
|
+
- **[UC-018: Testing Events](./UC-018-testing-events.md)** - Testing strategies
|
|
750
|
+
|
|
751
|
+
---
|
|
752
|
+
|
|
753
|
+
## 🎯 Summary
|
|
754
|
+
|
|
755
|
+
### Migration Timeline
|
|
756
|
+
|
|
757
|
+
| Phase | Duration | Risk | Status |
|
|
758
|
+
|-------|----------|------|--------|
|
|
759
|
+
| **Phase 1: Shadow Mode** | 1-2 weeks | Low (no changes) | Both systems run |
|
|
760
|
+
| **Phase 2: Gradual Conversion** | 4-6 weeks | Low (incremental) | Convert high-value areas |
|
|
761
|
+
| **Phase 3: Full Migration** | 1+ weeks | Medium (turn off mirror) | E11y only |
|
|
762
|
+
| **TOTAL** | **6-9 weeks** | **Low overall** | Gradual, safe |
|
|
763
|
+
|
|
764
|
+
### Benefits After Migration
|
|
765
|
+
|
|
766
|
+
| Before (Rails.logger) | After (E11y) |
|
|
767
|
+
|----------------------|--------------|
|
|
768
|
+
| Unstructured text | Structured events |
|
|
769
|
+
| Hard to search | Easy queries (Grafana) |
|
|
770
|
+
| No metrics | Automatic metrics |
|
|
771
|
+
| No correlation | Trace ID everywhere |
|
|
772
|
+
| Manual parsing | Type-safe schemas |
|
|
773
|
+
| No validation | Schema validation |
|
|
774
|
+
|
|
775
|
+
**Developer Experience:**
|
|
776
|
+
- Migration: 6-9 weeks for typical Rails app
|
|
777
|
+
- Per feature: 15-30 min to convert
|
|
778
|
+
- Testing: Works with both systems
|
|
779
|
+
- Risk: Low (gradual, reversible)
|
|
780
|
+
|
|
781
|
+
---
|
|
782
|
+
|
|
783
|
+
**Document Version:** 1.0
|
|
784
|
+
**Last Updated:** January 12, 2026
|
|
785
|
+
**Status:** ✅ Complete
|