e11y 0.2.0 → 1.0.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/.rubocop.yml +130 -10
- data/CHANGELOG.md +56 -1
- data/CLAUDE.md +168 -0
- data/CONTRIBUTING.md +640 -0
- data/README.md +134 -702
- data/RELEASE.md +18 -3
- data/Rakefile +108 -29
- data/config/README.md +1 -1
- data/config/loki-local-config.yaml +12 -0
- data/config/otel-collector-config.yaml +44 -0
- data/cucumber.yml +1 -0
- data/docker-compose.yml +18 -2
- data/docs/ADAPTERS.md +76 -0
- data/docs/ADAPTIVE_SAMPLING.md +59 -0
- data/docs/COMPARISON.md +104 -0
- data/docs/CONFIGURATION.md +52 -0
- data/docs/DISTRIBUTED_TRACING.md +44 -0
- data/docs/LIMITATIONS.md +13 -0
- data/docs/METRICS_DSL.md +84 -0
- data/docs/PERFORMANCE.md +60 -0
- data/docs/PII_FILTERING.md +40 -0
- data/docs/PRESETS.md +65 -0
- data/docs/QUICK-START.md +546 -587
- data/docs/RAILS_INTEGRATION.md +29 -0
- data/docs/SCHEMA_VALIDATION.md +63 -0
- data/docs/SLO-PROMQL-ALERTS.md +161 -0
- data/docs/TESTING.md +69 -0
- data/docs/{ADR-001-architecture.md → architecture/ADR-001-architecture.md} +35 -64
- data/docs/{ADR-002-metrics-yabeda.md → architecture/ADR-002-metrics-yabeda.md} +62 -236
- data/docs/{ADR-003-slo-observability.md → architecture/ADR-003-slo-observability.md} +27 -466
- data/docs/{ADR-004-adapter-architecture.md → architecture/ADR-004-adapter-architecture.md} +163 -146
- data/docs/{ADR-005-tracing-context.md → architecture/ADR-005-tracing-context.md} +10 -9
- data/docs/{ADR-006-security-compliance.md → architecture/ADR-006-security-compliance.md} +184 -191
- data/docs/{ADR-007-opentelemetry-integration.md → architecture/ADR-007-opentelemetry-integration.md} +3 -21
- data/docs/{ADR-008-rails-integration.md → architecture/ADR-008-rails-integration.md} +209 -339
- data/docs/{ADR-009-cost-optimization.md → architecture/ADR-009-cost-optimization.md} +45 -54
- data/docs/architecture/ADR-010-developer-experience.md +522 -0
- data/docs/{ADR-011-testing-strategy.md → architecture/ADR-011-testing-strategy.md} +41 -83
- data/docs/{ADR-013-reliability-error-handling.md → architecture/ADR-013-reliability-error-handling.md} +37 -12
- data/docs/{ADR-014-event-driven-slo.md → architecture/ADR-014-event-driven-slo.md} +12 -24
- data/docs/{ADR-015-middleware-order.md → architecture/ADR-015-middleware-order.md} +23 -41
- data/docs/{ADR-016-self-monitoring-slo.md → architecture/ADR-016-self-monitoring-slo.md} +52 -349
- data/docs/{ADR-017-multi-rails-compatibility.md → architecture/ADR-017-multi-rails-compatibility.md} +4 -11
- data/docs/architecture/ADR-018-memory-optimization.md +366 -0
- data/docs/{ADR-INDEX.md → architecture/ADR-INDEX.md} +11 -6
- data/docs/{00-ICP-AND-TIMELINE.md → prd/00-ICP-AND-TIMELINE.md} +6 -6
- data/docs/{01-SCALE-REQUIREMENTS.md → prd/01-SCALE-REQUIREMENTS.md} +6 -6
- data/docs/prd/01-overview-vision.md +19 -14
- data/docs/use_cases/README.md +22 -23
- data/docs/use_cases/UC-001-request-scoped-debug-buffering.md +50 -44
- data/docs/use_cases/UC-002-business-event-tracking.md +26 -95
- data/docs/use_cases/UC-003-event-metrics.md +66 -0
- data/docs/use_cases/UC-004-zero-config-slo-tracking.md +42 -101
- data/docs/use_cases/UC-005-sentry-integration.md +13 -15
- data/docs/use_cases/UC-006-trace-context-management.md +30 -28
- data/docs/use_cases/UC-007-pii-filtering.md +35 -87
- data/docs/use_cases/UC-008-opentelemetry-integration.md +51 -89
- data/docs/use_cases/UC-009-multi-service-tracing.md +4 -4
- data/docs/use_cases/UC-010-background-job-tracking.md +5 -5
- data/docs/use_cases/UC-011-rate-limiting.md +95 -168
- data/docs/use_cases/UC-012-audit-trail.md +21 -46
- data/docs/use_cases/UC-013-high-cardinality-protection.md +29 -167
- data/docs/use_cases/UC-014-adaptive-sampling.md +2 -2
- data/docs/use_cases/UC-015-cost-optimization.md +46 -99
- data/docs/use_cases/UC-016-rails-logger-migration.md +39 -213
- data/docs/use_cases/UC-017-local-development.md +203 -777
- data/docs/use_cases/UC-018-testing-events.md +3 -3
- data/docs/use_cases/UC-019-retention-based-routing.md +53 -106
- data/docs/use_cases/UC-020-event-versioning.md +8 -9
- data/docs/use_cases/UC-021-error-handling-retry-dlq.md +18 -22
- data/docs/use_cases/UC-022-event-registry.md +15 -21
- data/docs/use_cases/backlog.md +119 -87
- data/e11y.gemspec +2 -2
- data/gems/e11y-devtools/README.md +136 -0
- data/gems/e11y-devtools/config/routes.rb +8 -0
- data/gems/e11y-devtools/e11y-devtools.gemspec +25 -0
- data/gems/e11y-devtools/exe/e11y +34 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/server.rb +96 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tool_base.rb +25 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/clear.rb +31 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/errors.rb +35 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/event_detail.rb +33 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/events_by_trace.rb +33 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/interactions.rb +40 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/recent_events.rb +34 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/search.rb +34 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/stats.rb +30 -0
- data/gems/e11y-devtools/lib/e11y/devtools/overlay/assets/overlay.js +115 -0
- data/gems/e11y-devtools/lib/e11y/devtools/overlay/controller.rb +54 -0
- data/gems/e11y-devtools/lib/e11y/devtools/overlay/engine.rb +26 -0
- data/gems/e11y-devtools/lib/e11y/devtools/overlay/middleware.rb +80 -0
- data/gems/e11y-devtools/lib/e11y/devtools/overlay/rails_controller.rb +42 -0
- data/gems/e11y-devtools/lib/e11y/devtools/tui/app.rb +262 -0
- data/gems/e11y-devtools/lib/e11y/devtools/tui/grouping.rb +66 -0
- data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/event_detail.rb +62 -0
- data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/event_list.rb +70 -0
- data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/interaction_list.rb +47 -0
- data/gems/e11y-devtools/lib/e11y/devtools/version.rb +8 -0
- data/gems/e11y-devtools/lib/e11y/devtools.rb +13 -0
- data/gems/e11y-devtools/spec/e11y/devtools/mcp/tools_spec.rb +107 -0
- data/gems/e11y-devtools/spec/e11y/devtools/overlay/controller_spec.rb +58 -0
- data/gems/e11y-devtools/spec/e11y/devtools/overlay/middleware_spec.rb +46 -0
- data/gems/e11y-devtools/spec/e11y/devtools/tui/app_spec.rb +85 -0
- data/gems/e11y-devtools/spec/e11y/devtools/tui/grouping_spec.rb +64 -0
- data/gems/e11y-devtools/spec/spec_helper.rb +5 -0
- data/gems/e11y-devtools/spec/tui/widgets/event_list_spec.rb +44 -0
- data/gems/e11y-devtools/spec/tui/widgets/interaction_list_spec.rb +62 -0
- data/lib/e11y/adapters/audit_encrypted.rb +53 -11
- data/lib/e11y/adapters/base.rb +33 -34
- data/lib/e11y/adapters/dev_log/file_store.rb +143 -0
- data/lib/e11y/adapters/dev_log/query.rb +219 -0
- data/lib/e11y/adapters/dev_log.rb +118 -0
- data/lib/e11y/adapters/file.rb +3 -6
- data/lib/e11y/adapters/in_memory.rb +52 -5
- data/lib/e11y/adapters/in_memory_test.rb +29 -0
- data/lib/e11y/adapters/loki.rb +58 -23
- data/lib/e11y/adapters/null.rb +82 -0
- data/lib/e11y/adapters/opentelemetry_collector.rb +183 -0
- data/lib/e11y/adapters/otel_logs.rb +136 -23
- data/lib/e11y/adapters/sentry.rb +4 -7
- data/lib/e11y/adapters/stdout.rb +73 -7
- data/lib/e11y/adapters/yabeda.rb +153 -29
- data/lib/e11y/buffers/adaptive_buffer.rb +3 -17
- data/lib/e11y/buffers/{request_scoped_buffer.rb → ephemeral_buffer.rb} +72 -58
- data/lib/e11y/buffers/ring_buffer.rb +3 -16
- data/lib/e11y/configuration.rb +272 -0
- data/lib/e11y/console.rb +10 -17
- data/lib/e11y/current.rb +53 -1
- data/lib/e11y/debug/pipeline_inspector.rb +96 -0
- data/lib/e11y/documentation/generator.rb +48 -0
- data/lib/e11y/event/base.rb +176 -82
- data/lib/e11y/event/value_sampling_config.rb +1 -5
- data/lib/e11y/events/rails/database/query.rb +1 -4
- data/lib/e11y/events/rails/job/failed.rb +2 -0
- data/lib/e11y/instruments/active_job.rb +46 -12
- data/lib/e11y/instruments/rails_instrumentation.rb +49 -24
- data/lib/e11y/instruments/sidekiq.rb +137 -31
- data/lib/e11y/linters/base.rb +11 -0
- data/lib/e11y/linters/pii/pii_declaration_linter.rb +120 -0
- data/lib/e11y/linters/slo/config_consistency_linter.rb +76 -0
- data/lib/e11y/linters/slo/explicit_declaration_linter.rb +36 -0
- data/lib/e11y/linters/slo/slo_status_from_linter.rb +41 -0
- data/lib/e11y/logger/bridge.rb +26 -7
- data/lib/e11y/metrics/cardinality_protection.rb +10 -15
- data/lib/e11y/metrics/cardinality_tracker.rb +16 -6
- data/lib/e11y/metrics/registry.rb +3 -5
- data/lib/e11y/metrics/test_backend.rb +62 -0
- data/lib/e11y/metrics.rb +56 -10
- data/lib/e11y/middleware/adapter_resolver.rb +40 -0
- data/lib/e11y/middleware/audit_signing.rb +43 -6
- data/lib/e11y/middleware/baggage_protection.rb +75 -0
- data/lib/e11y/middleware/dev_log_source.rb +24 -0
- data/lib/e11y/middleware/event_slo.rb +23 -9
- data/lib/e11y/middleware/otel_span.rb +23 -0
- data/lib/e11y/middleware/pii_filter.rb +104 -75
- data/lib/e11y/middleware/rate_limiting.rb +54 -27
- data/lib/e11y/middleware/request.rb +70 -23
- data/lib/e11y/middleware/routing.rb +78 -21
- data/lib/e11y/middleware/sampling.rb +66 -17
- data/lib/e11y/middleware/self_monitoring_emit.rb +39 -0
- data/lib/e11y/middleware/trace_context.rb +45 -10
- data/lib/e11y/middleware/track_latency.rb +34 -0
- data/lib/e11y/middleware/validation.rb +7 -16
- data/lib/e11y/middleware/versioning.rb +26 -22
- data/lib/e11y/opentelemetry/semantic_conventions.rb +109 -0
- data/lib/e11y/opentelemetry/span_creator.rb +142 -0
- data/lib/e11y/pii/patterns.rb +12 -1
- data/lib/e11y/pipeline/builder.rb +1 -1
- data/lib/e11y/presets/audit_event.rb +13 -2
- data/lib/e11y/railtie.rb +52 -15
- data/lib/e11y/registry.rb +306 -0
- data/lib/e11y/reliability/circuit_breaker.rb +19 -21
- data/lib/e11y/reliability/dlq/base.rb +71 -0
- data/lib/e11y/reliability/dlq/file_adapter.rb +301 -0
- data/lib/e11y/reliability/dlq/file_storage.rb +63 -34
- data/lib/e11y/reliability/dlq/filter.rb +37 -54
- data/lib/e11y/reliability/retry_handler.rb +26 -29
- data/lib/e11y/reliability/retry_rate_limiter.rb +3 -11
- data/lib/e11y/sampling/error_spike_detector.rb +0 -2
- data/lib/e11y/sampling/load_monitor.rb +5 -9
- data/lib/e11y/sampling/stratified_tracker.rb +18 -0
- data/lib/e11y/self_monitoring/buffer_monitor.rb +2 -0
- data/lib/e11y/self_monitoring/performance_monitor.rb +19 -61
- data/lib/e11y/self_monitoring/reliability_monitor.rb +4 -74
- data/lib/e11y/slo/config_loader.rb +40 -0
- data/lib/e11y/slo/config_validator.rb +58 -0
- data/lib/e11y/slo/dashboard_generator.rb +122 -0
- data/lib/e11y/slo/event_driven.rb +8 -0
- data/lib/e11y/slo/tracker.rb +31 -4
- data/lib/e11y/testing/have_tracked_event_matcher.rb +190 -0
- data/lib/e11y/testing/rspec_matchers.rb +21 -0
- data/lib/e11y/testing/snapshot_matcher.rb +86 -0
- data/lib/e11y/trace_context/sampler.rb +35 -0
- data/lib/e11y/tracing/faraday_middleware.rb +31 -0
- data/lib/e11y/tracing/net_http_patch.rb +33 -0
- data/lib/e11y/tracing/propagator.rb +116 -0
- data/lib/e11y/tracing.rb +47 -0
- data/lib/e11y/version.rb +1 -1
- data/lib/e11y/versioning/version_extractor.rb +32 -0
- data/lib/e11y.rb +141 -265
- data/lib/generators/e11y/event/event_generator.rb +22 -0
- data/lib/generators/e11y/event/templates/event.rb.tt +16 -0
- data/lib/generators/e11y/grafana_dashboard/grafana_dashboard_generator.rb +30 -0
- data/lib/generators/e11y/grafana_dashboard/templates/e11y_dashboard.json +81 -0
- data/lib/generators/e11y/install/install_generator.rb +34 -0
- data/lib/generators/e11y/install/templates/e11y.rb +239 -0
- data/lib/generators/e11y/prometheus_alerts/prometheus_alerts_generator.rb +29 -0
- data/lib/generators/e11y/prometheus_alerts/templates/e11y_alerts.yml +28 -0
- data/lib/tasks/e11y_docs.rake +30 -0
- data/lib/tasks/e11y_events.rake +71 -0
- data/lib/tasks/e11y_lint.rake +91 -0
- data/lib/tasks/e11y_slo.rake +29 -0
- metadata +129 -39
- data/docs/ADR-010-developer-experience.md +0 -2166
- data/docs/API-REFERENCE-L28.md +0 -914
- data/docs/COMPREHENSIVE-CONFIGURATION.md +0 -2366
- data/docs/CONTRIBUTING.md +0 -312
- data/docs/IMPLEMENTATION_NOTES.md +0 -2804
- data/docs/IMPLEMENTATION_PLAN.md +0 -1971
- data/docs/IMPLEMENTATION_PLAN_ARCHITECTURE.md +0 -586
- data/docs/PLAN.md +0 -148
- data/docs/README.md +0 -296
- data/docs/design/00-memory-optimization.md +0 -593
- data/docs/guides/MIGRATION-L27-L28.md +0 -692
- data/docs/guides/PERFORMANCE-BENCHMARKS.md +0 -434
- data/docs/guides/README.md +0 -44
- data/docs/use_cases/UC-003-pattern-based-metrics.md +0 -1627
- data/lib/e11y/adapters/registry.rb +0 -141
- /data/docs/{ADR-012-event-evolution.md → architecture/ADR-012-event-evolution.md} +0 -0
|
@@ -52,7 +52,7 @@ end
|
|
|
52
52
|
|
|
53
53
|
## 🎯 Features
|
|
54
54
|
|
|
55
|
-
> **Implementation:** See [ADR-011: Testing Strategy](../ADR-011-testing-strategy.md) for complete testing architecture, including [Section 3: RSpec Matchers](../ADR-011-testing-strategy.md#3-rspec-matchers), [Section 4: Test Adapters](../ADR-011-testing-strategy.md#4-test-adapters), [Section 6: Snapshot Testing](../ADR-011-testing-strategy.md#6-snapshot-testing), and [Section 8: Integration Testing](../ADR-011-testing-strategy.md#8-integration-testing).
|
|
55
|
+
> **Implementation:** See [ADR-011: Testing Strategy](../architecture/ADR-011-testing-strategy.md) for complete testing architecture, including [Section 3: RSpec Matchers](../architecture/ADR-011-testing-strategy.md#3-rspec-matchers), [Section 4: Test Adapters](../architecture/ADR-011-testing-strategy.md#4-test-adapters), [Section 6: Snapshot Testing](../architecture/ADR-011-testing-strategy.md#6-snapshot-testing), and [Section 8: Integration Testing](../architecture/ADR-011-testing-strategy.md#8-integration-testing).
|
|
56
56
|
|
|
57
57
|
### 1. RSpec Matchers
|
|
58
58
|
|
|
@@ -325,7 +325,7 @@ end
|
|
|
325
325
|
|
|
326
326
|
### 6. Test Environment Configuration (C13)
|
|
327
327
|
|
|
328
|
-
> **Implementation:** See [ADR-011 Section 10: Test Environment Configuration](../ADR-011-testing-strategy.md#10-test-environment-configuration) for detailed rationale on disabling production features in tests.
|
|
328
|
+
> **Implementation:** See [ADR-011 Section 10: Test Environment Configuration](../architecture/ADR-011-testing-strategy.md#10-test-environment-configuration) for detailed rationale on disabling production features in tests.
|
|
329
329
|
|
|
330
330
|
**Critical: Production features MUST be disabled in tests for predictability.**
|
|
331
331
|
|
|
@@ -387,7 +387,7 @@ RSpec.configure do |config|
|
|
|
387
387
|
]
|
|
388
388
|
|
|
389
389
|
# === OPTIONAL: Disable rate limiting ===
|
|
390
|
-
config.
|
|
390
|
+
config.rate_limiting_enabled = false
|
|
391
391
|
|
|
392
392
|
# === OPTIONAL: Disable PII filtering (test data is fake) ===
|
|
393
393
|
config.pii_filtering.enabled = false
|
|
@@ -53,24 +53,26 @@ class AuditEvent < E11y::Event::Base
|
|
|
53
53
|
end
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
**
|
|
56
|
+
**Routing at collection** (where to write now):
|
|
57
57
|
```ruby
|
|
58
58
|
E11y.configure do |config|
|
|
59
59
|
config.routing_rules = [
|
|
60
60
|
->(event) { :audit_encrypted if event[:audit_event] },
|
|
61
61
|
->(event) {
|
|
62
62
|
days = (Time.parse(event[:retention_until]) - Time.now) / 86400
|
|
63
|
-
days
|
|
63
|
+
days <= 7 ? :stdout : :loki # Short retention → cheap storage at collection
|
|
64
64
|
}
|
|
65
65
|
]
|
|
66
66
|
end
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
**Result:**
|
|
70
|
-
- ✅ **
|
|
71
|
-
- ✅ **Compliance
|
|
70
|
+
- ✅ **Cost savings** (short retention → stdout, not Loki)
|
|
71
|
+
- ✅ **Compliance** (audit → encrypted storage)
|
|
72
72
|
- ✅ **Developer experience** (declare intent, routing handles rest)
|
|
73
73
|
|
|
74
|
+
> **Archival is a separate moment.** E11y collects events in real time and writes to Loki (hot storage). Each event carries `retention_until` (ISO8601) in its payload. **Archival** (hot → warm → cold) is done by a **separate job** (cron, Loki compaction, export script) — not at collection time. The job filters logs by `retention_until`: `WHERE retention_until > ?` — simple, no custom logic, easy cost control.
|
|
75
|
+
|
|
74
76
|
---
|
|
75
77
|
|
|
76
78
|
## 🎯 Use Case Scenarios
|
|
@@ -140,11 +142,8 @@ E11y.configure do |config|
|
|
|
140
142
|
# Priority 1: Audit events always to encrypted storage
|
|
141
143
|
->(event) { :audit_encrypted if event[:audit_event] },
|
|
142
144
|
|
|
143
|
-
#
|
|
144
|
-
->(event) {
|
|
145
|
-
days = (Time.parse(event[:retention_until]) - Time.now) / 86400
|
|
146
|
-
:s3_glacier if days > 90 && !event[:audit_event]
|
|
147
|
-
}
|
|
145
|
+
# Other events → Loki (archival job handles cold tier later)
|
|
146
|
+
->(event) { :loki }
|
|
148
147
|
]
|
|
149
148
|
end
|
|
150
149
|
|
|
@@ -170,8 +169,8 @@ UserDeletedEvent.track(
|
|
|
170
169
|
- ✅ **Immutable** → audit trail tamper-proof
|
|
171
170
|
|
|
172
171
|
**Cost Impact:**
|
|
173
|
-
- **Before:** Loki storage (30 days,
|
|
174
|
-
- **After:** Audit-encrypted
|
|
172
|
+
- **Before:** Loki storage (30 days, manual export) = $5000/month
|
|
173
|
+
- **After:** Audit-encrypted at collection; archival job (separate) exports to cold tier by `retention_until` = $50/month
|
|
175
174
|
- **Savings:** 99% ($4950/month)
|
|
176
175
|
|
|
177
176
|
---
|
|
@@ -192,37 +191,21 @@ class OrderPlacedEvent < E11y::Event::Base
|
|
|
192
191
|
end
|
|
193
192
|
end
|
|
194
193
|
|
|
195
|
-
#
|
|
194
|
+
# At collection: all events → Loki. retention_until in payload.
|
|
195
|
+
# Archival job (cron, separate): filters Loki by retention_until, exports to warm/cold.
|
|
196
196
|
E11y.configure do |config|
|
|
197
|
-
config.routing_rules = [
|
|
198
|
-
->(event) {
|
|
199
|
-
days = (Time.parse(event[:retention_until]) - Time.now) / 86400
|
|
200
|
-
case days
|
|
201
|
-
when 0..30 then :loki # Hot storage
|
|
202
|
-
when 31..90 then :s3_standard # Warm storage
|
|
203
|
-
else :s3_glacier # Cold storage
|
|
204
|
-
end
|
|
205
|
-
}
|
|
206
|
-
]
|
|
197
|
+
config.routing_rules = [->(_event) { :loki }]
|
|
207
198
|
end
|
|
208
199
|
|
|
209
200
|
# Usage
|
|
210
|
-
OrderPlacedEvent.track(
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
currency: "USD"
|
|
214
|
-
)
|
|
215
|
-
# ↓
|
|
216
|
-
# retention_until: "2026-04-21T10:30:00Z" (90 days from now)
|
|
217
|
-
# ↓
|
|
218
|
-
# Routing: days = 90 → :s3_standard adapter (warm storage)
|
|
219
|
-
# ↓
|
|
220
|
-
# Event written to S3 Standard (cost-optimized)
|
|
201
|
+
OrderPlacedEvent.track(order_id: "ORD-123", amount: 10000, currency: "USD")
|
|
202
|
+
# → Written to Loki with retention_until: "2026-04-21T..."
|
|
203
|
+
# → Archival job (runs later): WHERE retention_until > now + 30d → export to cold
|
|
221
204
|
```
|
|
222
205
|
|
|
223
206
|
**Cost Impact:**
|
|
224
|
-
- **Before:** Loki only = $200/month
|
|
225
|
-
- **After:** Loki
|
|
207
|
+
- **Before:** Loki only, no tiering = $200/month
|
|
208
|
+
- **After:** Loki at collection; archival job exports by `retention_until` to cheaper storage = $120/month
|
|
226
209
|
- **Savings:** 40% ($80/month)
|
|
227
210
|
|
|
228
211
|
---
|
|
@@ -243,17 +226,11 @@ class PaymentFailedEvent < E11y::Event::Base
|
|
|
243
226
|
end
|
|
244
227
|
end
|
|
245
228
|
|
|
246
|
-
#
|
|
229
|
+
# At collection: errors → Sentry + Loki. retention_until in payload for archival.
|
|
247
230
|
E11y.configure do |config|
|
|
248
231
|
config.routing_rules = [
|
|
249
|
-
|
|
250
|
-
->(
|
|
251
|
-
|
|
252
|
-
# Rule 2: Retention-based storage
|
|
253
|
-
->(event) {
|
|
254
|
-
days = (Time.parse(event[:retention_until]) - Time.now) / 86400
|
|
255
|
-
days > 30 ? :s3_standard : :loki
|
|
256
|
-
}
|
|
232
|
+
->(event) { [:sentry, :loki] if event[:severity] == :error },
|
|
233
|
+
->(_event) { :loki }
|
|
257
234
|
]
|
|
258
235
|
end
|
|
259
236
|
|
|
@@ -268,14 +245,14 @@ PaymentFailedEvent.track(
|
|
|
268
245
|
# ↓
|
|
269
246
|
# Routing:
|
|
270
247
|
# Rule 1: :sentry (error alerting)
|
|
271
|
-
# Rule 2: :
|
|
248
|
+
# Rule 2: :loki (archival job handles tiers later)
|
|
272
249
|
# ↓
|
|
273
250
|
# Event written to BOTH adapters
|
|
274
251
|
```
|
|
275
252
|
|
|
276
253
|
**Benefits:**
|
|
277
254
|
- ✅ **Alerting:** Sentry catches errors immediately
|
|
278
|
-
- ✅ **Storage:**
|
|
255
|
+
- ✅ **Storage:** Loki at collection; archival job (separate) exports by `retention_until`
|
|
279
256
|
- ✅ **Cost:** No duplicate Loki storage ($100/month savings)
|
|
280
257
|
|
|
281
258
|
---
|
|
@@ -345,17 +322,24 @@ CriticalPaymentEvent.track(amount: 100000, user_id: 789)
|
|
|
345
322
|
│ │
|
|
346
323
|
│ Apply routing rules: │
|
|
347
324
|
│ - Rule 1: audit → encrypted │
|
|
348
|
-
│ - Rule 2:
|
|
349
|
-
│ - Rule 3:
|
|
325
|
+
│ - Rule 2: errors → Sentry + Loki │
|
|
326
|
+
│ - Rule 3: default → Loki │
|
|
350
327
|
└─────────────────────────────────┘
|
|
351
328
|
│
|
|
352
329
|
▼
|
|
353
330
|
┌───────┴───────┐
|
|
354
331
|
│ │
|
|
355
332
|
┌─────▼─────┐ ┌─────▼─────┐
|
|
356
|
-
│
|
|
357
|
-
│ Loki │ │ S3 │
|
|
333
|
+
│ Loki │ │ Sentry │ (at collection)
|
|
358
334
|
└───────────┘ └───────────┘
|
|
335
|
+
│
|
|
336
|
+
│ retention_until in payload
|
|
337
|
+
▼
|
|
338
|
+
┌─────────────────────────────┐
|
|
339
|
+
│ Archival job (separate) │
|
|
340
|
+
│ Filters by retention_until │
|
|
341
|
+
│ → export to cold storage │
|
|
342
|
+
└─────────────────────────────┘
|
|
359
343
|
```
|
|
360
344
|
|
|
361
345
|
### Component Responsibilities
|
|
@@ -365,7 +349,7 @@ CriticalPaymentEvent.track(amount: 100000, user_id: 789)
|
|
|
365
349
|
| **Event::Base** | Declare `retention_period`, calculate `retention_until` |
|
|
366
350
|
| **Configuration** | Define `routing_rules` (lambdas), `default_retention_period` |
|
|
367
351
|
| **Routing Middleware** | Apply rules, select adapters, write events |
|
|
368
|
-
| **Adapters** | Write events to storage (Loki,
|
|
352
|
+
| **Adapters** | Write events to storage (Loki, File, Sentry, etc.) |
|
|
369
353
|
|
|
370
354
|
---
|
|
371
355
|
|
|
@@ -400,73 +384,36 @@ end
|
|
|
400
384
|
|
|
401
385
|
```ruby
|
|
402
386
|
# config/initializers/e11y.rb
|
|
387
|
+
# At collection: route to Loki (or stdout for very short retention).
|
|
388
|
+
# retention_until is in every event payload — archival job (separate) uses it later.
|
|
403
389
|
E11y.configure do |config|
|
|
404
|
-
# Default retention (fallback)
|
|
405
390
|
config.default_retention_period = 30.days
|
|
406
391
|
|
|
407
|
-
# Routing rules (evaluated in order)
|
|
408
392
|
config.routing_rules = [
|
|
409
|
-
|
|
410
|
-
->(event) {
|
|
411
|
-
:audit_encrypted if event[:audit_event]
|
|
412
|
-
},
|
|
413
|
-
|
|
414
|
-
# Priority 2: Errors → Sentry + storage
|
|
415
|
-
->(event) {
|
|
416
|
-
[:sentry, :loki] if event[:severity] == :error
|
|
417
|
-
},
|
|
418
|
-
|
|
419
|
-
# Priority 3: Retention-based tiering
|
|
393
|
+
->(event) { :audit_encrypted if event[:audit_event] },
|
|
394
|
+
->(event) { [:sentry, :loki] if event[:severity] == :error },
|
|
420
395
|
->(event) {
|
|
421
396
|
days = (Time.parse(event[:retention_until]) - Time.now) / 86400
|
|
422
|
-
|
|
423
|
-
when 0..7 then :stdout # Very short → console
|
|
424
|
-
when 8..30 then :loki # Short → hot storage
|
|
425
|
-
when 31..90 then :s3_standard # Medium → warm storage
|
|
426
|
-
else :s3_glacier # Long → cold storage
|
|
427
|
-
end
|
|
397
|
+
days <= 7 ? :stdout : :loki # Short → cheap, long → Loki
|
|
428
398
|
}
|
|
429
399
|
]
|
|
430
400
|
|
|
431
|
-
# Fallback if no rule matches
|
|
432
401
|
config.fallback_adapters = [:stdout]
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
config.
|
|
436
|
-
config.add_adapter :s3_standard, E11y::Adapters::File.new(path: 's3://bucket/warm/')
|
|
437
|
-
config.add_adapter :s3_glacier, E11y::Adapters::File.new(path: 's3://bucket/cold/')
|
|
438
|
-
config.add_adapter :audit_encrypted, E11y::Adapters::AuditEncrypted.new(...)
|
|
439
|
-
config.add_adapter :sentry, E11y::Adapters::Sentry.new(...)
|
|
402
|
+
config.adapters[:loki] = E11y::Adapters::Loki.new(...)
|
|
403
|
+
config.adapters[:audit_encrypted] = E11y::Adapters::AuditEncrypted.new(...)
|
|
404
|
+
config.adapters[:sentry] = E11y::Adapters::Sentry.new(...)
|
|
440
405
|
end
|
|
441
406
|
```
|
|
442
407
|
|
|
443
|
-
### Step 3:
|
|
408
|
+
### Step 3: Archival Job (Separate Process)
|
|
409
|
+
|
|
410
|
+
Archival runs **later**, not at collection. Example cron job:
|
|
444
411
|
|
|
445
412
|
```ruby
|
|
446
|
-
#
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
expect(event[:retention_until]).to eq(7.days.from_now.iso8601)
|
|
452
|
-
expect(E11y.configuration.adapters[:stdout]).to have_received(:write)
|
|
453
|
-
end
|
|
454
|
-
|
|
455
|
-
it "routes audit events to encrypted storage" do
|
|
456
|
-
event = UserDeletedEvent.track(user_id: 123, deleted_by: 456)
|
|
457
|
-
|
|
458
|
-
expect(event[:retention_until]).to eq(7.years.from_now.iso8601)
|
|
459
|
-
expect(event[:audit_event]).to be true
|
|
460
|
-
expect(E11y.configuration.adapters[:audit_encrypted]).to have_received(:write)
|
|
461
|
-
end
|
|
462
|
-
|
|
463
|
-
it "routes long retention to cold storage" do
|
|
464
|
-
event = BusinessEvent.track(data: "...")
|
|
465
|
-
allow(event).to receive(:[]).with(:retention_until).and_return(365.days.from_now.iso8601)
|
|
466
|
-
|
|
467
|
-
expect(E11y.configuration.adapters[:s3_glacier]).to have_received(:write)
|
|
468
|
-
end
|
|
469
|
-
end
|
|
413
|
+
# lib/tasks/archival.rake or separate service
|
|
414
|
+
# Runs daily: reads Loki, filters by retention_until, exports to cold storage
|
|
415
|
+
# SELECT * FROM logs WHERE retention_until > ? AND timestamp < ?
|
|
416
|
+
# → export to S3 / object storage
|
|
470
417
|
```
|
|
471
418
|
|
|
472
419
|
---
|
|
@@ -506,8 +453,8 @@ end
|
|
|
506
453
|
|
|
507
454
|
**Monthly Costs:**
|
|
508
455
|
- Debug logs (7d in stdout): **$0** ✅
|
|
509
|
-
- Business events (
|
|
510
|
-
- Audit logs (
|
|
456
|
+
- Business events (Loki + archival job exports by retention_until): **$120** ✅
|
|
457
|
+
- Audit logs (audit_encrypted + archival job): **$50** ✅
|
|
511
458
|
- **Total: $170/month**
|
|
512
459
|
|
|
513
460
|
**Savings: 97% ($5,530/month)**
|
|
@@ -573,7 +520,7 @@ end
|
|
|
573
520
|
|
|
574
521
|
- ✅ **100% of audit events** go to `audit_encrypted` adapter
|
|
575
522
|
- ✅ **Debug logs** (7d retention) → stdout (free)
|
|
576
|
-
- ✅ **Business events** (90d retention) →
|
|
523
|
+
- ✅ **Business events** (90d retention) → Loki at collection; archival job exports by `retention_until`
|
|
577
524
|
- ✅ **Cost reduction** of 80%+ compared to manual adapter selection
|
|
578
525
|
- ✅ **Zero manual intervention** (routing is automatic)
|
|
579
526
|
|
|
@@ -186,7 +186,7 @@ end
|
|
|
186
186
|
|
|
187
187
|
## 🏗️ Architecture
|
|
188
188
|
|
|
189
|
-
> **Implementation:** See [ADR-012: Event Evolution](../ADR-012-event-evolution.md) for complete versioning architecture, including [Section 3: Naming Convention](../ADR-012-event-evolution.md#3-naming-convention), [Section 4: Version in Payload](../ADR-012-event-evolution.md#4-version-in-payload), [Section 6: Event Registry Integration](../ADR-012-event-evolution.md#6-event-registry-integration), and [Section 7: Migration Strategy](../ADR-012-event-evolution.md#7-migration-strategy).
|
|
189
|
+
> **Implementation:** See [ADR-012: Event Evolution](../architecture/ADR-012-event-evolution.md) for complete versioning architecture, including [Section 3: Naming Convention](../architecture/ADR-012-event-evolution.md#3-naming-convention), [Section 4: Version in Payload](../architecture/ADR-012-event-evolution.md#4-version-in-payload), [Section 6: Event Registry Integration](../architecture/ADR-012-event-evolution.md#6-event-registry-integration), and [Section 7: Migration Strategy](../architecture/ADR-012-event-evolution.md#7-migration-strategy).
|
|
190
190
|
|
|
191
191
|
### Version Management
|
|
192
192
|
|
|
@@ -195,7 +195,7 @@ end
|
|
|
195
195
|
│ Event Registry (tracks all versions) │
|
|
196
196
|
│ │
|
|
197
197
|
│ event_name: 'order.paid' │
|
|
198
|
-
│ ├─ V1:
|
|
198
|
+
│ ├─ V1: OrderPaid (no suffix per ADR-012) │
|
|
199
199
|
│ ├─ V2: OrderPaidV2 (current) │
|
|
200
200
|
│ └─ V3: OrderPaidV3 (future) │
|
|
201
201
|
│ │
|
|
@@ -209,7 +209,7 @@ end
|
|
|
209
209
|
│ { │
|
|
210
210
|
│ "@timestamp": "2026-01-12T10:30:00Z", │
|
|
211
211
|
│ "event_name": "order.paid", │
|
|
212
|
-
│ "
|
|
212
|
+
│ "v": 2, ← Version (ADR-012 uses v: for V2+) │
|
|
213
213
|
│ "payload": { │
|
|
214
214
|
│ "order_id": "123", │
|
|
215
215
|
│ "amount": 99.99, │
|
|
@@ -231,9 +231,8 @@ E11y.configure do |config|
|
|
|
231
231
|
config.versioning do
|
|
232
232
|
enabled true
|
|
233
233
|
|
|
234
|
-
# Include version in event payload
|
|
234
|
+
# Include version in event payload (ADR-012: field name is v: for V2+)
|
|
235
235
|
include_version_in_payload true
|
|
236
|
-
version_field :event_version # Field name
|
|
237
236
|
|
|
238
237
|
# Deprecation warnings
|
|
239
238
|
warn_on_deprecated_version true
|
|
@@ -475,9 +474,9 @@ end
|
|
|
475
474
|
|
|
476
475
|
**4. Use semantic versioning for major changes**
|
|
477
476
|
```ruby
|
|
478
|
-
# ✅ GOOD:
|
|
479
|
-
class
|
|
480
|
-
version 1 # Initial version
|
|
477
|
+
# ✅ GOOD: V1 no suffix (ADR-012), V2+ with suffix
|
|
478
|
+
class OrderPaid < E11y::Event::Base
|
|
479
|
+
version 1 # Initial version (no V1 suffix)
|
|
481
480
|
end
|
|
482
481
|
|
|
483
482
|
class OrderPaidV2 < E11y::Event::Base
|
|
@@ -599,7 +598,7 @@ class OrderPaid < E11y::Event::Base # V1
|
|
|
599
598
|
# Track deprecation usage
|
|
600
599
|
Events::DeprecatedEventUsed.track(
|
|
601
600
|
event_class: 'OrderPaid',
|
|
602
|
-
|
|
601
|
+
version: 1,
|
|
603
602
|
service: ENV['SERVICE_NAME']
|
|
604
603
|
)
|
|
605
604
|
end
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
|
|
35
35
|
- **Retry Policy:** Exponential backoff with jitter
|
|
36
36
|
- **Dead Letter Queue:** Failed events stored for later analysis/replay
|
|
37
|
-
- **Circuit Breaker:** Prevent cascading failures (
|
|
37
|
+
- **Circuit Breaker:** Prevent cascading failures (see [ADR-013 §5](../architecture/ADR-013-reliability-error-handling.md#5-circuit-breaker))
|
|
38
38
|
- **Observability:** Metrics for failures, retries, DLQ size
|
|
39
39
|
|
|
40
40
|
**Result:** Zero data loss, resilient to transient failures.
|
|
@@ -150,21 +150,17 @@ E11y::DeadLetterQueue.size # => 1000 (mostly garbage)
|
|
|
150
150
|
|
|
151
151
|
**With DLQ filter (GOOD!):**
|
|
152
152
|
```ruby
|
|
153
|
-
#
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
# Always save payments
|
|
162
|
-
always_save do
|
|
163
|
-
event_patterns ['payment.*', 'order.*']
|
|
164
|
-
end
|
|
165
|
-
end
|
|
153
|
+
# Event DSL: declare DLQ behavior per event class
|
|
154
|
+
class Events::HealthCheck < E11y::Event::Base
|
|
155
|
+
use_dlq false # Never save to DLQ
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
class Events::PaymentFailed < E11y::Event::Base
|
|
159
|
+
use_dlq true # Always save to DLQ
|
|
166
160
|
end
|
|
167
161
|
|
|
162
|
+
# Audit events (Presets::AuditEvent) have use_dlq true by default
|
|
163
|
+
|
|
168
164
|
# Health checks (not saved to DLQ):
|
|
169
165
|
1000.times do
|
|
170
166
|
Events::HealthCheck.track(status: 'ok')
|
|
@@ -183,7 +179,7 @@ E11y::DeadLetterQueue.size # => 1 (only payment)
|
|
|
183
179
|
|
|
184
180
|
## 🏗️ Architecture
|
|
185
181
|
|
|
186
|
-
> **Implementation:** See [ADR-013: Reliability & Error Handling](../ADR-013-reliability-error-handling.md) for complete error handling architecture, including retry policy with exponential backoff and jitter, circuit breaker pattern, Dead Letter Queue (DLQ) storage strategies, and self-monitoring metrics.
|
|
182
|
+
> **Implementation:** See [ADR-013: Reliability & Error Handling](../architecture/ADR-013-reliability-error-handling.md) for complete error handling architecture, including retry policy with exponential backoff and jitter, circuit breaker pattern, Dead Letter Queue (DLQ) storage strategies, and self-monitoring metrics.
|
|
187
183
|
|
|
188
184
|
### Retry Pipeline
|
|
189
185
|
|
|
@@ -445,7 +441,7 @@ E11y::DeadLetterQueue.replay(
|
|
|
445
441
|
### DLQ Replay with PII & Schema Considerations (C07, C15)
|
|
446
442
|
|
|
447
443
|
> **⚠️ CRITICAL:** DLQ replay requires special handling for PII filtering and schema migrations.
|
|
448
|
-
> **See:** [ADR-006 Section 5.6](../ADR-006-security-compliance.md#56-pii-handling-for-event-replay-from-dlq-c07-resolution) for C07 (PII double-hashing), [ADR-012 Section 8](../ADR-012-event-evolution.md#8-schema-migrations-and-dlq-replay-c15-resolution--critical) for C15 (schema migrations).
|
|
444
|
+
> **See:** [ADR-006 Section 5.6](../architecture/ADR-006-security-compliance.md#56-pii-handling-for-event-replay-from-dlq-c07-resolution) for C07 (PII double-hashing), [ADR-012 Section 8](../architecture/ADR-012-event-evolution.md#8-schema-migrations-and-dlq-replay-c15-resolution--critical) for C15 (schema migrations).
|
|
449
445
|
|
|
450
446
|
**Problem 1: PII Double-Hashing on Replay (C07)**
|
|
451
447
|
|
|
@@ -490,7 +486,7 @@ E11y.configure do |config|
|
|
|
490
486
|
|
|
491
487
|
# === CRITICAL: Enable replay metadata (C07) ===
|
|
492
488
|
# Replay service automatically adds flags:
|
|
493
|
-
# - :
|
|
489
|
+
# - :dlq_replayed => true (skip transformations)
|
|
494
490
|
# - :pii_filtered => true (already filtered)
|
|
495
491
|
mark_replayed_events true # ← Default: true
|
|
496
492
|
end
|
|
@@ -505,13 +501,13 @@ module E11y
|
|
|
505
501
|
|
|
506
502
|
# ✅ CRITICAL: Add replay metadata flags
|
|
507
503
|
event_data[:metadata] ||= {}
|
|
508
|
-
event_data[:metadata][:
|
|
504
|
+
event_data[:metadata][:dlq_replayed] = true
|
|
509
505
|
event_data[:metadata][:pii_filtered] = true # Already filtered!
|
|
510
506
|
event_data[:metadata][:replayed_at] = Time.now.utc.iso8601
|
|
511
507
|
event_data[:metadata][:original_event_id] = event_data[:event_id]
|
|
512
508
|
|
|
513
509
|
# Send through pipeline
|
|
514
|
-
# PII filter middleware will skip (checks :
|
|
510
|
+
# PII filter middleware will skip (checks :dlq_replayed flag)
|
|
515
511
|
E11y::Pipeline.process(event_data)
|
|
516
512
|
end
|
|
517
513
|
end
|
|
@@ -535,7 +531,7 @@ class PiiFilter < Base
|
|
|
535
531
|
|
|
536
532
|
def already_filtered?(event_data)
|
|
537
533
|
metadata = event_data[:metadata] || {}
|
|
538
|
-
metadata[:
|
|
534
|
+
metadata[:dlq_replayed] || metadata[:pii_filtered]
|
|
539
535
|
end
|
|
540
536
|
end
|
|
541
537
|
|
|
@@ -603,7 +599,7 @@ E11y::DeadLetterQueue.replay_all
|
|
|
603
599
|
# Lenient validation for replayed events
|
|
604
600
|
# (user chooses to allow old schema)
|
|
605
601
|
lenient_mode_if do |event_data|
|
|
606
|
-
event_data.dig(:metadata, :
|
|
602
|
+
event_data.dig(:metadata, :dlq_replayed) == true
|
|
607
603
|
end
|
|
608
604
|
end
|
|
609
605
|
end
|
|
@@ -637,7 +633,7 @@ E11y::DeadLetterQueue.replay_all
|
|
|
637
633
|
| Decision | Pro | Con | Mitigation |
|
|
638
634
|
|----------|-----|-----|------------|
|
|
639
635
|
| **Metadata flags** | Simple, automatic | Metadata size +24 bytes | Acceptable overhead |
|
|
640
|
-
| **`:
|
|
636
|
+
| **`:dlq_replayed` flag** | Clear intent | None | ✅ Best practice |
|
|
641
637
|
| **Skip PII filter** | Prevents double-hashing | Must trust DLQ integrity | DLQ stored securely (encrypted) |
|
|
642
638
|
|
|
643
639
|
**Trade-offs (C15):**
|
|
@@ -18,10 +18,9 @@
|
|
|
18
18
|
- Need to grep codebase to find event classes
|
|
19
19
|
- Hard to document all events
|
|
20
20
|
|
|
21
|
-
2. **No runtime
|
|
21
|
+
2. **No runtime discovery**
|
|
22
22
|
- Can't list all registered events at runtime
|
|
23
23
|
- Can't find event class by name
|
|
24
|
-
- Can't inspect event schema programmatically
|
|
25
24
|
|
|
26
25
|
3. **Hard to build tooling**
|
|
27
26
|
- Can't build event explorer UI (no registry)
|
|
@@ -34,7 +33,7 @@
|
|
|
34
33
|
|
|
35
34
|
- Automatic registration of all event classes
|
|
36
35
|
- Query registry by event name, version, adapter
|
|
37
|
-
-
|
|
36
|
+
- Query by name, severity, adapter (find, where)
|
|
38
37
|
- Build developer tools (event explorer, documentation generator)
|
|
39
38
|
|
|
40
39
|
**Result:** Full visibility into all events in the system.
|
|
@@ -53,7 +52,7 @@ $ grep -r "class.*< E11y::Event::Base" app/events/
|
|
|
53
52
|
# → Manual, error-prone, outdated
|
|
54
53
|
|
|
55
54
|
# With registry (AUTOMATIC):
|
|
56
|
-
E11y::Registry.
|
|
55
|
+
E11y::Registry.event_classes
|
|
57
56
|
# => [
|
|
58
57
|
# Events::OrderCreated,
|
|
59
58
|
# Events::OrderPaid,
|
|
@@ -63,7 +62,7 @@ E11y::Registry.all_events
|
|
|
63
62
|
# ]
|
|
64
63
|
|
|
65
64
|
# Generate documentation:
|
|
66
|
-
E11y::Registry.
|
|
65
|
+
E11y::Registry.event_classes.each do |event_class|
|
|
67
66
|
puts "## #{event_class.event_name}"
|
|
68
67
|
puts "Version: #{event_class.version}"
|
|
69
68
|
puts "Schema: #{event_class.schema_definition}"
|
|
@@ -135,7 +134,7 @@ event.severity_level
|
|
|
135
134
|
# Rails controller for event explorer
|
|
136
135
|
class EventExplorerController < ApplicationController
|
|
137
136
|
def index
|
|
138
|
-
@events = E11y::Registry.
|
|
137
|
+
@events = E11y::Registry.event_classes.map do |event_class|
|
|
139
138
|
{
|
|
140
139
|
name: event_class.event_name,
|
|
141
140
|
version: event_class.version,
|
|
@@ -193,8 +192,8 @@ end
|
|
|
193
192
|
│ │
|
|
194
193
|
│ Indexes: │
|
|
195
194
|
│ - by_name: 'order.created' → Events::OrderCreatedV2 │
|
|
196
|
-
│ -
|
|
197
|
-
│ -
|
|
195
|
+
│ - where(adapter: :sentry) → [Events::PaymentFailed, ...] │
|
|
196
|
+
│ - where(severity: :error) → [Events::SystemError, ...] │
|
|
198
197
|
│ - by_version: 2 → [Events::OrderCreatedV2, ...] │
|
|
199
198
|
└─────────────────────────────────────────────────────────────────┘
|
|
200
199
|
```
|
|
@@ -239,7 +238,6 @@ E11y.configure do |config|
|
|
|
239
238
|
]
|
|
240
239
|
|
|
241
240
|
# Registry features
|
|
242
|
-
enable_introspection true
|
|
243
241
|
enable_event_explorer true # Web UI at /e11y/events
|
|
244
242
|
end
|
|
245
243
|
end
|
|
@@ -249,13 +247,13 @@ end
|
|
|
249
247
|
|
|
250
248
|
## 📝 Registry API
|
|
251
249
|
|
|
252
|
-
> **Implementation:** See [ADR-010 Section 5: Event Registry](../ADR-010-developer-experience.md#5-event-registry) for full registry architecture
|
|
250
|
+
> **Implementation:** See [ADR-010 Section 5: Event Registry](../architecture/ADR-010-developer-experience.md#5-event-registry) for full registry architecture (find, event_classes, where, to_documentation).
|
|
253
251
|
|
|
254
252
|
### Query Events
|
|
255
253
|
|
|
256
254
|
```ruby
|
|
257
255
|
# === List All Events ===
|
|
258
|
-
E11y::Registry.
|
|
256
|
+
E11y::Registry.event_classes
|
|
259
257
|
# => [Events::OrderCreated, Events::OrderPaid, ...]
|
|
260
258
|
|
|
261
259
|
E11y::Registry.count
|
|
@@ -278,10 +276,6 @@ E11y::Registry.where(severity: :error)
|
|
|
278
276
|
E11y::Registry.where(version: 2)
|
|
279
277
|
# => [Events::OrderCreatedV2, Events::OrderPaidV2, ...]
|
|
280
278
|
|
|
281
|
-
# === Search ===
|
|
282
|
-
E11y::Registry.search('payment')
|
|
283
|
-
# => [Events::PaymentProcessed, Events::PaymentFailed, ...]
|
|
284
|
-
|
|
285
279
|
# === Filtering ===
|
|
286
280
|
E11y::Registry.filter do |event_class|
|
|
287
281
|
event_class.adapters.include?(:sentry) &&
|
|
@@ -427,7 +421,7 @@ namespace :e11y do
|
|
|
427
421
|
output.puts "Total events: #{E11y::Registry.count}"
|
|
428
422
|
output.puts
|
|
429
423
|
|
|
430
|
-
E11y::Registry.
|
|
424
|
+
E11y::Registry.event_classes.each do |event_class|
|
|
431
425
|
output.puts "## #{event_class.event_name}"
|
|
432
426
|
output.puts
|
|
433
427
|
output.puts "**Version:** #{event_class.version}"
|
|
@@ -475,7 +469,7 @@ namespace :e11y do
|
|
|
475
469
|
task validate: :environment do
|
|
476
470
|
errors = []
|
|
477
471
|
|
|
478
|
-
E11y::Registry.
|
|
472
|
+
E11y::Registry.event_classes.each do |event_class|
|
|
479
473
|
# Check: has example payload
|
|
480
474
|
if event_class.example_payloads.empty?
|
|
481
475
|
errors << "#{event_class.name} has no example payloads"
|
|
@@ -522,7 +516,7 @@ namespace :e11y do
|
|
|
522
516
|
paths: {}
|
|
523
517
|
}
|
|
524
518
|
|
|
525
|
-
E11y::Registry.
|
|
519
|
+
E11y::Registry.event_classes.each do |event_class|
|
|
526
520
|
spec[:paths]["/events/#{event_class.event_name}"] = {
|
|
527
521
|
post: {
|
|
528
522
|
summary: "Track #{event_class.event_name} event",
|
|
@@ -557,9 +551,9 @@ end
|
|
|
557
551
|
|
|
558
552
|
```ruby
|
|
559
553
|
RSpec.describe E11y::Registry do
|
|
560
|
-
describe '.
|
|
554
|
+
describe '.event_classes' do
|
|
561
555
|
it 'returns all registered events' do
|
|
562
|
-
events = E11y::Registry.
|
|
556
|
+
events = E11y::Registry.event_classes
|
|
563
557
|
|
|
564
558
|
expect(events).to include(Events::OrderCreated)
|
|
565
559
|
expect(events).to include(Events::OrderPaid)
|
|
@@ -636,7 +630,7 @@ end
|
|
|
636
630
|
|
|
637
631
|
- [ ] Enable registry in config
|
|
638
632
|
- [ ] Enable eager loading of event classes
|
|
639
|
-
- [ ] Access registry: `E11y::Registry.
|
|
633
|
+
- [ ] Access registry: `E11y::Registry.event_classes`
|
|
640
634
|
- [ ] Enable event explorer UI (development only)
|
|
641
635
|
- [ ] Set up documentation generator rake task
|
|
642
636
|
- [ ] Run validation in CI: `rake e11y:validate`
|