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
data/README.md
CHANGED
|
@@ -2,18 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
# E11y - Easy Telemetry
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**Debug production issues in seconds. Zero setup overhead. Own your data.**
|
|
6
6
|
|
|
7
7
|
[](https://badge.fury.io/rb/e11y)
|
|
8
8
|
[](https://github.com/arturseletskiy/e11y/actions/workflows/ci.yml)
|
|
9
9
|
[](https://codecov.io/gh/arturseletskiy/e11y)
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
[Quick Start](#quick-start) • [How it works](#the-e11y-solution) • [Docs](#documentation)
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
> v0.2.0 · Actively developed · Production feedback welcome → [open an issue](https://github.com/arturseletskiy/e11y/issues)
|
|
14
14
|
|
|
15
15
|
</div>
|
|
16
16
|
|
|
17
|
+
**Contents:** [Quick Look](#quick-look-2-minutes) · [Quick Start](#quick-start) · [Features](#what-makes-e11y-different) · [vs Alternatives](#e11y-vs-alternatives) · [Docs](#documentation)
|
|
18
|
+
|
|
17
19
|
---
|
|
18
20
|
|
|
19
21
|
## The Problem Every Rails Developer Knows
|
|
@@ -60,6 +62,43 @@ You enable debug logs in production to catch that one weird bug.
|
|
|
60
62
|
|
|
61
63
|
---
|
|
62
64
|
|
|
65
|
+
## Quick Look (2 minutes)
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
# 1. Configure once
|
|
69
|
+
E11y.configure do |config|
|
|
70
|
+
config.ephemeral_buffer_enabled = true
|
|
71
|
+
config.adapters[:logs] = E11y::Adapters::Loki.new(url: ENV["LOKI_URL"])
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# 2. Define a business event
|
|
75
|
+
class OrderPaidEvent < E11y::Event::Base
|
|
76
|
+
schema do
|
|
77
|
+
required(:order_id).filled(:string)
|
|
78
|
+
required(:amount).filled(:float, gt?: 0)
|
|
79
|
+
required(:currency).filled(:string, included_in?: %w[USD EUR GBP])
|
|
80
|
+
optional(:user_email).maybe(:string)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
validation_mode :sampled, sample_rate: 0.01 # 1% validation for hot path
|
|
84
|
+
contains_pii true
|
|
85
|
+
pii_filtering { hashes :user_email }
|
|
86
|
+
sample_by_value :amount, greater_than: 1000 # Always sample large orders
|
|
87
|
+
|
|
88
|
+
metrics do
|
|
89
|
+
counter :orders_total, tags: [:currency]
|
|
90
|
+
histogram :order_amount, value: :amount, tags: [:currency]
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# 3. Track it
|
|
95
|
+
OrderPaidEvent.track(order_id: "123", amount: 99.99, currency: "USD")
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
→ [Full Quick Start guide (5 min)](#quick-start)
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
63
102
|
## What Makes E11y Different?
|
|
64
103
|
|
|
65
104
|
### 1. Request-Scoped Debug Buffering (Unique to E11y)
|
|
@@ -75,22 +114,40 @@ Rails.logger.debug "rendering view" # → Always written to disk
|
|
|
75
114
|
|
|
76
115
|
# E11y approach:
|
|
77
116
|
E11y.configure do |config|
|
|
78
|
-
config.
|
|
117
|
+
config.ephemeral_buffer_enabled = true
|
|
79
118
|
end
|
|
80
119
|
|
|
81
120
|
# Debug events buffered in memory during request
|
|
82
|
-
# Flushed to storage ONLY on errors
|
|
121
|
+
# Flushed to storage ONLY on 5xx server errors
|
|
83
122
|
# Cost: -90%, Noise: -99%, Value: 100%
|
|
84
123
|
```
|
|
85
124
|
|
|
125
|
+
> **Note:** By default the buffer flushes only on **5xx server errors** (`flush_on_error = true`).
|
|
126
|
+
> On 4xx responses the buffer is discarded. Two independent knobs control this:
|
|
127
|
+
>
|
|
128
|
+
> ```ruby
|
|
129
|
+
> # flush_on_error (default: true) — controls 5xx behaviour
|
|
130
|
+
> config.ephemeral_buffer_flush_on_error = false # disable 5xx flush
|
|
131
|
+
>
|
|
132
|
+
> # flush_on_statuses (default: []) — extra statuses, independent of flush_on_error
|
|
133
|
+
> config.ephemeral_buffer_flush_on_statuses = [403] # also flush on 403 Forbidden
|
|
134
|
+
> config.ephemeral_buffer_flush_on_statuses = [401, 403] # multiple codes
|
|
135
|
+
> ```
|
|
136
|
+
|
|
86
137
|
**Real-world impact:**
|
|
87
|
-
- **Storage costs:**
|
|
138
|
+
- **Storage costs:** Up to -90% log volume → proportional Loki storage savings
|
|
88
139
|
- **Log search time:** 30 seconds → 3 seconds (90% less data)
|
|
89
140
|
- **Developer sanity:** Infinite ✨
|
|
90
141
|
|
|
91
142
|
---
|
|
92
143
|
|
|
93
|
-
### 2.
|
|
144
|
+
### 2. retention_until — Simple Archival
|
|
145
|
+
|
|
146
|
+
Events carry `retention_until` (ISO8601) in their payload. **Archival happens later** — a separate job (cron, Loki compaction) filters logs by this field. No custom logic: `WHERE retention_until > ?`. Cost savings (export to cheap cold storage) and simplicity (one field to filter).
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
### 3. Schema-Validated Business Events
|
|
94
151
|
|
|
95
152
|
Stop debugging nil values in production:
|
|
96
153
|
|
|
@@ -126,44 +183,39 @@ OrderPaidEvent.track(order_id: "123", amount: 99.99, currency: "USD")
|
|
|
126
183
|
|
|
127
184
|
---
|
|
128
185
|
|
|
129
|
-
###
|
|
186
|
+
### 4. Zero-Config SLO Tracking
|
|
130
187
|
|
|
131
188
|
Automatic Service Level Objectives for your endpoints and jobs:
|
|
132
189
|
|
|
133
190
|
```ruby
|
|
134
191
|
# Enable Rails instrumentation
|
|
135
192
|
E11y.configure do |config|
|
|
136
|
-
config.
|
|
193
|
+
config.rails_instrumentation_enabled = true
|
|
137
194
|
end
|
|
138
195
|
|
|
139
|
-
# That's it! E11y now
|
|
196
|
+
# That's it! E11y now emits SLO metrics automatically:
|
|
140
197
|
# - HTTP endpoints: success rate, latency percentiles (p50, p95, p99)
|
|
141
198
|
# - Background jobs: success rate, execution time, retry rate
|
|
142
199
|
# - Database queries: slow query detection
|
|
143
200
|
# - Cache operations: hit/miss ratios
|
|
144
|
-
|
|
145
|
-
#
|
|
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
|
-
# }
|
|
201
|
+
#
|
|
202
|
+
# SLO metrics are collected via Prometheus/Yabeda; calculate SLOs from those metrics.
|
|
151
203
|
```
|
|
152
204
|
|
|
153
205
|
**vs. Traditional SLO Tracking:**
|
|
154
206
|
- ❌ Manual instrumentation of every endpoint
|
|
155
207
|
- ❌ Complex SLO definitions and calculations
|
|
156
208
|
- ❌ Separate tools for different SLOs
|
|
157
|
-
- ✅ E11y: Zero config, automatic tracking,
|
|
209
|
+
- ✅ E11y: Zero config, automatic tracking, metrics for SLO calculation
|
|
158
210
|
|
|
159
211
|
---
|
|
160
212
|
|
|
161
|
-
###
|
|
213
|
+
### 5. Rails-First Design
|
|
162
214
|
|
|
163
215
|
Built for Rails developers, not platform engineers:
|
|
164
216
|
|
|
165
217
|
```ruby
|
|
166
|
-
#
|
|
218
|
+
# Fast setup, not 2-week OpenTelemetry migration
|
|
167
219
|
gem "e11y"
|
|
168
220
|
|
|
169
221
|
E11y.configure do |config|
|
|
@@ -172,7 +224,7 @@ E11y.configure do |config|
|
|
|
172
224
|
end
|
|
173
225
|
|
|
174
226
|
# Auto-instruments Rails (optional):
|
|
175
|
-
config.
|
|
227
|
+
config.rails_instrumentation_enabled = true
|
|
176
228
|
# → HTTP requests, ActiveRecord, ActiveJob, Cache events
|
|
177
229
|
```
|
|
178
230
|
|
|
@@ -184,6 +236,19 @@ config.rails_instrumentation.enabled = true
|
|
|
184
236
|
|
|
185
237
|
---
|
|
186
238
|
|
|
239
|
+
### 6. Built-in PII Filtering
|
|
240
|
+
|
|
241
|
+
**Built-in PII filtering** — mask, hash, or redact sensitive fields per event class. No other Ruby observability gem provides this out of the box.
|
|
242
|
+
|
|
243
|
+
```ruby
|
|
244
|
+
class Events::UserSignedIn < E11y::Event::Base
|
|
245
|
+
contains_pii :email, strategy: :hash
|
|
246
|
+
contains_pii :ip_address, strategy: :mask
|
|
247
|
+
end
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
187
252
|
## Who Should Use E11y?
|
|
188
253
|
|
|
189
254
|
### ✅ Perfect For
|
|
@@ -204,7 +269,7 @@ config.rails_instrumentation.enabled = true
|
|
|
204
269
|
|
|
205
270
|
- **Non-Rails Ruby** - Focused on Rails conventions first
|
|
206
271
|
- **Microservices polyglot** - OpenTelemetry better for multi-language
|
|
207
|
-
- **Enterprise compliance**
|
|
272
|
+
- **Enterprise compliance requirements** — audit trails and compliance reports are not yet available
|
|
208
273
|
- **Auto-instrumentation only** - E11y requires event definitions (by design)
|
|
209
274
|
|
|
210
275
|
---
|
|
@@ -218,14 +283,15 @@ config.rails_instrumentation.enabled = true
|
|
|
218
283
|
| **Schema Validation** | ✅ Implemented | dry-schema validation before sending events |
|
|
219
284
|
| **Metrics DSL** | ✅ Implemented | Define Prometheus metrics alongside events |
|
|
220
285
|
| **Adapters** | ✅ 7 adapters | Loki, Sentry, OpenTelemetry, Yabeda, File, Stdout, InMemory |
|
|
221
|
-
| **PII Filtering** | ✅ Implemented | Configurable field masking/hashing/redaction |
|
|
286
|
+
| **PII Filtering** | ✅ Implemented | Configurable field masking/hashing/redaction (event-level DSL) |
|
|
222
287
|
| **Adaptive Sampling** | ✅ Implemented | Error-based, load-based, value-based strategies |
|
|
288
|
+
| **Rate Limiting** | ✅ Implemented | Opt-in — requires `config.pipeline.use E11y::Middleware::RateLimiting` |
|
|
223
289
|
| **Rails Integration** | ✅ Implemented | Auto-instrument HTTP, ActiveRecord, ActiveJob, Cache |
|
|
224
290
|
| **Production Testing** | 🚧 In Progress | Validating with real workloads |
|
|
225
291
|
|
|
226
292
|
---
|
|
227
293
|
|
|
228
|
-
## Quick Start
|
|
294
|
+
## Quick Start
|
|
229
295
|
|
|
230
296
|
### 1. Install
|
|
231
297
|
|
|
@@ -242,7 +308,9 @@ bundle install
|
|
|
242
308
|
# config/initializers/e11y.rb
|
|
243
309
|
E11y.configure do |config|
|
|
244
310
|
# Enable request-scoped debug buffering (THE killer feature)
|
|
245
|
-
config.
|
|
311
|
+
config.ephemeral_buffer_enabled = true
|
|
312
|
+
# config.ephemeral_buffer_flush_on_error = true # default: flush on 5xx
|
|
313
|
+
# config.ephemeral_buffer_flush_on_statuses = [403] # also flush on 403
|
|
246
314
|
|
|
247
315
|
# Configure where events go
|
|
248
316
|
config.adapters[:logs] = E11y::Adapters::Loki.new(
|
|
@@ -255,7 +323,7 @@ E11y.configure do |config|
|
|
|
255
323
|
)
|
|
256
324
|
|
|
257
325
|
# Optional: Auto-instrument Rails
|
|
258
|
-
config.
|
|
326
|
+
config.rails_instrumentation_enabled = true
|
|
259
327
|
end
|
|
260
328
|
```
|
|
261
329
|
|
|
@@ -312,7 +380,7 @@ end
|
|
|
312
380
|
- ❌ nil values in production
|
|
313
381
|
- ❌ Manual `Yabeda.increment` everywhere
|
|
314
382
|
- ❌ Manual SLO definitions and calculations
|
|
315
|
-
- ❌
|
|
383
|
+
- ❌ High log storage bills from storing everything
|
|
316
384
|
|
|
317
385
|
---
|
|
318
386
|
|
|
@@ -341,7 +409,7 @@ Rails.logger.error "Payment failed!"
|
|
|
341
409
|
# [DEBUG] Cache read... (3 lines)
|
|
342
410
|
# [ERROR] Payment failed (1 line)
|
|
343
411
|
# Total: 400 lines, 74% noise ❌
|
|
344
|
-
# Cost:
|
|
412
|
+
# Cost: High (all logs stored) ❌
|
|
345
413
|
# Search time: 30 seconds ❌
|
|
346
414
|
```
|
|
347
415
|
|
|
@@ -349,7 +417,7 @@ Rails.logger.error "Payment failed!"
|
|
|
349
417
|
|
|
350
418
|
```ruby
|
|
351
419
|
# Production with E11y:
|
|
352
|
-
E11y.configure { |c| c.
|
|
420
|
+
E11y.configure { |c| c.ephemeral_buffer_enabled = true }
|
|
353
421
|
|
|
354
422
|
# 99 successful requests:
|
|
355
423
|
# [INFO] User logged in (99 lines)
|
|
@@ -361,7 +429,7 @@ E11y.configure { |c| c.request_buffer.enabled = true }
|
|
|
361
429
|
# [DEBUG] SQL: SELECT... (context!) ← Flushed!
|
|
362
430
|
# [DEBUG] Rendered view... (trail!) ← Flushed!
|
|
363
431
|
# Total: 103 lines, 0% noise ✅
|
|
364
|
-
# Cost:
|
|
432
|
+
# Cost: Low (-90% log volume) ✅
|
|
365
433
|
# Search time: 3 seconds ✅ (-90%)
|
|
366
434
|
```
|
|
367
435
|
|
|
@@ -376,16 +444,16 @@ E11y.configure { |c| c.request_buffer.enabled = true }
|
|
|
376
444
|
|
|
377
445
|
### Comparison Matrix
|
|
378
446
|
|
|
379
|
-
| Solution | Setup Time | Monthly Cost | Request-Scoped Buffering | SLO Tracking | Schema Validation | Auto-Metrics | Data Ownership |
|
|
380
|
-
|
|
381
|
-
| **E11y** | **5
|
|
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 |
|
|
447
|
+
| Solution | Setup Time | Monthly Cost | Request-Scoped Buffering | SLO Tracking | Schema Validation | Auto-Metrics | Built-in PII Filtering | Data Ownership | Ecosystem / Managed Infra |
|
|
448
|
+
|----------|-----------|--------------|--------------------------|--------------|-------------------|--------------|------------------------|----------------|--------------------------|
|
|
449
|
+
| **E11y** | **5–30 min*** | **Infra costs** | **✅ Unique** | **✅ Zero-config** | **✅** | **✅** | **✅ Field masking, hashing, redaction** | **✅ Full** | ⚠️ Ruby/Rails only |
|
|
450
|
+
| Datadog APM | 2-4 hours | $500-5,000 | ❌ | ✅ Manual | ❌ | ✅ | ⚠️ Via agent config (limited) | ❌ SaaS lock-in | ✅ Extensive + fully managed |
|
|
451
|
+
| New Relic | 2-4 hours | $99-658/user | ❌ | ✅ Manual | ❌ | ✅ | ⚠️ Via obfuscation rules | ❌ SaaS lock-in | ✅ Extensive + fully managed |
|
|
452
|
+
| Sentry | 1 hour | $26-80/mo | ❌ | ❌ | ❌ | Partial | ⚠️ Data scrubbing rules | ❌ SaaS lock-in | ✅ Managed (error-focused) |
|
|
453
|
+
| Semantic Logger | 30 minutes | Infra costs | ❌ | ❌ | ❌ | ❌ | ❌ None | ✅ Full | ⚠️ Ruby only, self-hosted |
|
|
454
|
+
| OpenTelemetry | 1-2 weeks | Infra costs | ❌ | Manual setup | ❌ | ✅ | ❌ Manual implementation required | ✅ Full | ✅ Polyglot, vendor-neutral |
|
|
455
|
+
| Grafana + Loki | 2-3 days | Infra costs | ❌ | Manual setup | ❌ | Manual | ❌ None | ✅ Full | ✅ Mature, DevOps-friendly |
|
|
456
|
+
| AppSignal | 1 hour | $23-499/mo | ❌ | ✅ Built-in | ❌ | ✅ | ⚠️ Parameter filtering only | ❌ SaaS lock-in | ✅ Managed (Rails-friendly) |
|
|
389
457
|
|
|
390
458
|
**Legend:**
|
|
391
459
|
- **Setup Time:** From zero to first meaningful data
|
|
@@ -394,108 +462,19 @@ E11y.configure { |c| c.request_buffer.enabled = true }
|
|
|
394
462
|
- **SLO Tracking:** Automatic Service Level Objectives monitoring
|
|
395
463
|
- **Schema Validation:** Type-safe event schemas
|
|
396
464
|
- **Auto-Metrics:** Metrics generated from events automatically
|
|
465
|
+
- **Built-in PII Filtering:** Automatic masking/hashing of sensitive fields (emails, IPs, credit cards, etc.) — no other Ruby observability gem provides this out of the box
|
|
397
466
|
- **Data Ownership:** Can you host it yourself?
|
|
467
|
+
- **Ecosystem / Managed Infra:** Integration breadth and whether infrastructure is managed for you
|
|
398
468
|
|
|
399
|
-
|
|
469
|
+
*\* 5 min for gem + stdout; 30 min if adding self-hosted Loki/Grafana stack.
|
|
400
470
|
|
|
401
|
-
|
|
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`
|
|
471
|
+
> Cost estimates assume migration from verbose SaaS logging (Datadog/CloudWatch) to self-hosted Loki. Actual savings depend on your current setup.
|
|
448
472
|
|
|
449
473
|
---
|
|
450
474
|
|
|
451
|
-
|
|
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
|
|
475
|
+
### Detailed Comparisons
|
|
497
476
|
|
|
498
|
-
|
|
477
|
+
See [docs/COMPARISON.md](docs/COMPARISON.md) for detailed per-tool comparisons.
|
|
499
478
|
|
|
500
479
|
---
|
|
501
480
|
|
|
@@ -543,583 +522,36 @@ E11y is optimized for:
|
|
|
543
522
|
**Not optimized for:**
|
|
544
523
|
- Polyglot microservices (use OpenTelemetry)
|
|
545
524
|
- Frontend-heavy SPAs (use Datadog/Sentry for RUM)
|
|
546
|
-
- Enterprise compliance requirements (
|
|
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
|
|
642
|
-
class OrderPaidEvent < E11y::Event::Base
|
|
643
|
-
schema do
|
|
644
|
-
required(:order_id).filled(:string)
|
|
645
|
-
required(:amount).filled(:float)
|
|
646
|
-
required(:currency).filled(:string)
|
|
647
|
-
end
|
|
648
|
-
|
|
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]
|
|
687
|
-
end
|
|
688
|
-
# => order_amount_bucket{currency="USD", le="100"} 15
|
|
689
|
-
```
|
|
690
|
-
|
|
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
|
|
697
|
-
```
|
|
698
|
-
|
|
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)
|
|
705
|
-
|
|
706
|
-
---
|
|
707
|
-
|
|
708
|
-
## Adapters
|
|
709
|
-
|
|
710
|
-
E11y supports multiple adapters for different backends.
|
|
711
|
-
|
|
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 |
|
|
721
|
-
|
|
722
|
-
### Configuration
|
|
723
|
-
|
|
724
|
-
```ruby
|
|
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
|
|
743
|
-
```
|
|
744
|
-
|
|
745
|
-
### Adapter Routing by Severity
|
|
746
|
-
|
|
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
|
|
758
|
-
```
|
|
759
|
-
|
|
760
|
-
### Custom Adapters
|
|
761
|
-
|
|
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
|
|
893
|
-
```
|
|
894
|
-
|
|
895
|
-
### AuditEvent
|
|
896
|
-
|
|
897
|
-
For compliance and audit trails:
|
|
898
|
-
|
|
899
|
-
```ruby
|
|
900
|
-
class UserDeletedEvent < E11y::Event::Base
|
|
901
|
-
include E11y::Presets::AuditEvent
|
|
902
|
-
|
|
903
|
-
schema do
|
|
904
|
-
required(:user_id).filled(:string)
|
|
905
|
-
required(:deleted_by).filled(:string)
|
|
906
|
-
end
|
|
907
|
-
end
|
|
908
|
-
|
|
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
|
|
922
|
-
|
|
923
|
-
schema do
|
|
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!
|
|
985
|
-
end
|
|
986
|
-
end
|
|
987
|
-
```
|
|
988
|
-
|
|
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
|
|
1014
|
-
```
|
|
1015
|
-
|
|
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>
|
|
1023
|
-
|
|
1024
|
-
# Count events
|
|
1025
|
-
test_adapter.event_count # => Integer
|
|
1026
|
-
|
|
1027
|
-
# Find last event
|
|
1028
|
-
test_adapter.last_event # => Hash
|
|
1029
|
-
|
|
1030
|
-
# Clear all events
|
|
1031
|
-
test_adapter.clear!
|
|
1032
|
-
```
|
|
1033
|
-
|
|
1034
|
-
---
|
|
1035
|
-
|
|
1036
|
-
## Configuration
|
|
1037
|
-
|
|
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
|
-
```
|
|
525
|
+
- Enterprise compliance requirements (not yet available)
|
|
1083
526
|
|
|
1084
527
|
---
|
|
1085
528
|
|
|
1086
529
|
## Performance
|
|
1087
530
|
|
|
1088
|
-
|
|
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.
|
|
531
|
+
p99 latency <70µs (`:always`), <10µs (`:sampled`), <50µs (`:never`). Full benchmarks → [docs/PERFORMANCE.md](docs/PERFORMANCE.md)
|
|
1112
532
|
|
|
1113
533
|
---
|
|
1114
534
|
|
|
1115
535
|
## Documentation
|
|
1116
536
|
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
537
|
+
| Topic | Doc |
|
|
538
|
+
|-------|-----|
|
|
539
|
+
| [Schema Validation](docs/SCHEMA_VALIDATION.md) | dry-schema validation, modes, error handling |
|
|
540
|
+
| [Metrics DSL](docs/METRICS_DSL.md) | Counters, histograms, gauges, Yabeda integration |
|
|
541
|
+
| [Adapters](docs/ADAPTERS.md) | Loki, Sentry, OTel, Yabeda, File, Stdout, InMemory |
|
|
542
|
+
| [PII Filtering](docs/PII_FILTERING.md) | Mask, hash, redact sensitive fields |
|
|
543
|
+
| [Adaptive Sampling](docs/ADAPTIVE_SAMPLING.md) | Error-based, load-based, value-based |
|
|
544
|
+
| [Presets](docs/PRESETS.md) | HighValueEvent, AuditEvent, DebugEvent |
|
|
545
|
+
| [Distributed Tracing](docs/DISTRIBUTED_TRACING.md) | W3C Trace Context, manual propagation |
|
|
546
|
+
| [Rails Integration](docs/RAILS_INTEGRATION.md) | Auto-instrumentation, Sidekiq |
|
|
547
|
+
| [Testing](docs/TESTING.md) | InMemoryTest adapter, RSpec setup |
|
|
548
|
+
| [Configuration](docs/CONFIGURATION.md) | Basic config, middleware pipeline |
|
|
549
|
+
| [SLO PromQL & Alerts](docs/SLO-PROMQL-ALERTS.md) | PromQL queries, Prometheus alert rules |
|
|
550
|
+
| [Performance](docs/PERFORMANCE.md) | Benchmarks, validation modes, cardinality |
|
|
551
|
+
| [Limitations](docs/LIMITATIONS.md) | Rails only, Ruby 3.2+, tradeoffs |
|
|
552
|
+
| [Comparison](docs/COMPARISON.md) | vs Datadog, OTel, Sentry, AppSignal, etc. |
|
|
553
|
+
|
|
554
|
+
Also: [ADRs](docs/architecture/ADR-INDEX.md), [Use Cases](docs/use_cases/README.md), [QUICK-START](docs/QUICK-START.md)
|
|
1123
555
|
|
|
1124
556
|
---
|
|
1125
557
|
|