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.
Files changed (230) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +130 -10
  3. data/CHANGELOG.md +56 -1
  4. data/CLAUDE.md +168 -0
  5. data/CONTRIBUTING.md +640 -0
  6. data/README.md +134 -702
  7. data/RELEASE.md +18 -3
  8. data/Rakefile +108 -29
  9. data/config/README.md +1 -1
  10. data/config/loki-local-config.yaml +12 -0
  11. data/config/otel-collector-config.yaml +44 -0
  12. data/cucumber.yml +1 -0
  13. data/docker-compose.yml +18 -2
  14. data/docs/ADAPTERS.md +76 -0
  15. data/docs/ADAPTIVE_SAMPLING.md +59 -0
  16. data/docs/COMPARISON.md +104 -0
  17. data/docs/CONFIGURATION.md +52 -0
  18. data/docs/DISTRIBUTED_TRACING.md +44 -0
  19. data/docs/LIMITATIONS.md +13 -0
  20. data/docs/METRICS_DSL.md +84 -0
  21. data/docs/PERFORMANCE.md +60 -0
  22. data/docs/PII_FILTERING.md +40 -0
  23. data/docs/PRESETS.md +65 -0
  24. data/docs/QUICK-START.md +546 -587
  25. data/docs/RAILS_INTEGRATION.md +29 -0
  26. data/docs/SCHEMA_VALIDATION.md +63 -0
  27. data/docs/SLO-PROMQL-ALERTS.md +161 -0
  28. data/docs/TESTING.md +69 -0
  29. data/docs/{ADR-001-architecture.md → architecture/ADR-001-architecture.md} +35 -64
  30. data/docs/{ADR-002-metrics-yabeda.md → architecture/ADR-002-metrics-yabeda.md} +62 -236
  31. data/docs/{ADR-003-slo-observability.md → architecture/ADR-003-slo-observability.md} +27 -466
  32. data/docs/{ADR-004-adapter-architecture.md → architecture/ADR-004-adapter-architecture.md} +163 -146
  33. data/docs/{ADR-005-tracing-context.md → architecture/ADR-005-tracing-context.md} +10 -9
  34. data/docs/{ADR-006-security-compliance.md → architecture/ADR-006-security-compliance.md} +184 -191
  35. data/docs/{ADR-007-opentelemetry-integration.md → architecture/ADR-007-opentelemetry-integration.md} +3 -21
  36. data/docs/{ADR-008-rails-integration.md → architecture/ADR-008-rails-integration.md} +209 -339
  37. data/docs/{ADR-009-cost-optimization.md → architecture/ADR-009-cost-optimization.md} +45 -54
  38. data/docs/architecture/ADR-010-developer-experience.md +522 -0
  39. data/docs/{ADR-011-testing-strategy.md → architecture/ADR-011-testing-strategy.md} +41 -83
  40. data/docs/{ADR-013-reliability-error-handling.md → architecture/ADR-013-reliability-error-handling.md} +37 -12
  41. data/docs/{ADR-014-event-driven-slo.md → architecture/ADR-014-event-driven-slo.md} +12 -24
  42. data/docs/{ADR-015-middleware-order.md → architecture/ADR-015-middleware-order.md} +23 -41
  43. data/docs/{ADR-016-self-monitoring-slo.md → architecture/ADR-016-self-monitoring-slo.md} +52 -349
  44. data/docs/{ADR-017-multi-rails-compatibility.md → architecture/ADR-017-multi-rails-compatibility.md} +4 -11
  45. data/docs/architecture/ADR-018-memory-optimization.md +366 -0
  46. data/docs/{ADR-INDEX.md → architecture/ADR-INDEX.md} +11 -6
  47. data/docs/{00-ICP-AND-TIMELINE.md → prd/00-ICP-AND-TIMELINE.md} +6 -6
  48. data/docs/{01-SCALE-REQUIREMENTS.md → prd/01-SCALE-REQUIREMENTS.md} +6 -6
  49. data/docs/prd/01-overview-vision.md +19 -14
  50. data/docs/use_cases/README.md +22 -23
  51. data/docs/use_cases/UC-001-request-scoped-debug-buffering.md +50 -44
  52. data/docs/use_cases/UC-002-business-event-tracking.md +26 -95
  53. data/docs/use_cases/UC-003-event-metrics.md +66 -0
  54. data/docs/use_cases/UC-004-zero-config-slo-tracking.md +42 -101
  55. data/docs/use_cases/UC-005-sentry-integration.md +13 -15
  56. data/docs/use_cases/UC-006-trace-context-management.md +30 -28
  57. data/docs/use_cases/UC-007-pii-filtering.md +35 -87
  58. data/docs/use_cases/UC-008-opentelemetry-integration.md +51 -89
  59. data/docs/use_cases/UC-009-multi-service-tracing.md +4 -4
  60. data/docs/use_cases/UC-010-background-job-tracking.md +5 -5
  61. data/docs/use_cases/UC-011-rate-limiting.md +95 -168
  62. data/docs/use_cases/UC-012-audit-trail.md +21 -46
  63. data/docs/use_cases/UC-013-high-cardinality-protection.md +29 -167
  64. data/docs/use_cases/UC-014-adaptive-sampling.md +2 -2
  65. data/docs/use_cases/UC-015-cost-optimization.md +46 -99
  66. data/docs/use_cases/UC-016-rails-logger-migration.md +39 -213
  67. data/docs/use_cases/UC-017-local-development.md +203 -777
  68. data/docs/use_cases/UC-018-testing-events.md +3 -3
  69. data/docs/use_cases/UC-019-retention-based-routing.md +53 -106
  70. data/docs/use_cases/UC-020-event-versioning.md +8 -9
  71. data/docs/use_cases/UC-021-error-handling-retry-dlq.md +18 -22
  72. data/docs/use_cases/UC-022-event-registry.md +15 -21
  73. data/docs/use_cases/backlog.md +119 -87
  74. data/e11y.gemspec +2 -2
  75. data/gems/e11y-devtools/README.md +136 -0
  76. data/gems/e11y-devtools/config/routes.rb +8 -0
  77. data/gems/e11y-devtools/e11y-devtools.gemspec +25 -0
  78. data/gems/e11y-devtools/exe/e11y +34 -0
  79. data/gems/e11y-devtools/lib/e11y/devtools/mcp/server.rb +96 -0
  80. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tool_base.rb +25 -0
  81. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/clear.rb +31 -0
  82. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/errors.rb +35 -0
  83. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/event_detail.rb +33 -0
  84. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/events_by_trace.rb +33 -0
  85. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/interactions.rb +40 -0
  86. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/recent_events.rb +34 -0
  87. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/search.rb +34 -0
  88. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/stats.rb +30 -0
  89. data/gems/e11y-devtools/lib/e11y/devtools/overlay/assets/overlay.js +115 -0
  90. data/gems/e11y-devtools/lib/e11y/devtools/overlay/controller.rb +54 -0
  91. data/gems/e11y-devtools/lib/e11y/devtools/overlay/engine.rb +26 -0
  92. data/gems/e11y-devtools/lib/e11y/devtools/overlay/middleware.rb +80 -0
  93. data/gems/e11y-devtools/lib/e11y/devtools/overlay/rails_controller.rb +42 -0
  94. data/gems/e11y-devtools/lib/e11y/devtools/tui/app.rb +262 -0
  95. data/gems/e11y-devtools/lib/e11y/devtools/tui/grouping.rb +66 -0
  96. data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/event_detail.rb +62 -0
  97. data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/event_list.rb +70 -0
  98. data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/interaction_list.rb +47 -0
  99. data/gems/e11y-devtools/lib/e11y/devtools/version.rb +8 -0
  100. data/gems/e11y-devtools/lib/e11y/devtools.rb +13 -0
  101. data/gems/e11y-devtools/spec/e11y/devtools/mcp/tools_spec.rb +107 -0
  102. data/gems/e11y-devtools/spec/e11y/devtools/overlay/controller_spec.rb +58 -0
  103. data/gems/e11y-devtools/spec/e11y/devtools/overlay/middleware_spec.rb +46 -0
  104. data/gems/e11y-devtools/spec/e11y/devtools/tui/app_spec.rb +85 -0
  105. data/gems/e11y-devtools/spec/e11y/devtools/tui/grouping_spec.rb +64 -0
  106. data/gems/e11y-devtools/spec/spec_helper.rb +5 -0
  107. data/gems/e11y-devtools/spec/tui/widgets/event_list_spec.rb +44 -0
  108. data/gems/e11y-devtools/spec/tui/widgets/interaction_list_spec.rb +62 -0
  109. data/lib/e11y/adapters/audit_encrypted.rb +53 -11
  110. data/lib/e11y/adapters/base.rb +33 -34
  111. data/lib/e11y/adapters/dev_log/file_store.rb +143 -0
  112. data/lib/e11y/adapters/dev_log/query.rb +219 -0
  113. data/lib/e11y/adapters/dev_log.rb +118 -0
  114. data/lib/e11y/adapters/file.rb +3 -6
  115. data/lib/e11y/adapters/in_memory.rb +52 -5
  116. data/lib/e11y/adapters/in_memory_test.rb +29 -0
  117. data/lib/e11y/adapters/loki.rb +58 -23
  118. data/lib/e11y/adapters/null.rb +82 -0
  119. data/lib/e11y/adapters/opentelemetry_collector.rb +183 -0
  120. data/lib/e11y/adapters/otel_logs.rb +136 -23
  121. data/lib/e11y/adapters/sentry.rb +4 -7
  122. data/lib/e11y/adapters/stdout.rb +73 -7
  123. data/lib/e11y/adapters/yabeda.rb +153 -29
  124. data/lib/e11y/buffers/adaptive_buffer.rb +3 -17
  125. data/lib/e11y/buffers/{request_scoped_buffer.rb → ephemeral_buffer.rb} +72 -58
  126. data/lib/e11y/buffers/ring_buffer.rb +3 -16
  127. data/lib/e11y/configuration.rb +272 -0
  128. data/lib/e11y/console.rb +10 -17
  129. data/lib/e11y/current.rb +53 -1
  130. data/lib/e11y/debug/pipeline_inspector.rb +96 -0
  131. data/lib/e11y/documentation/generator.rb +48 -0
  132. data/lib/e11y/event/base.rb +176 -82
  133. data/lib/e11y/event/value_sampling_config.rb +1 -5
  134. data/lib/e11y/events/rails/database/query.rb +1 -4
  135. data/lib/e11y/events/rails/job/failed.rb +2 -0
  136. data/lib/e11y/instruments/active_job.rb +46 -12
  137. data/lib/e11y/instruments/rails_instrumentation.rb +49 -24
  138. data/lib/e11y/instruments/sidekiq.rb +137 -31
  139. data/lib/e11y/linters/base.rb +11 -0
  140. data/lib/e11y/linters/pii/pii_declaration_linter.rb +120 -0
  141. data/lib/e11y/linters/slo/config_consistency_linter.rb +76 -0
  142. data/lib/e11y/linters/slo/explicit_declaration_linter.rb +36 -0
  143. data/lib/e11y/linters/slo/slo_status_from_linter.rb +41 -0
  144. data/lib/e11y/logger/bridge.rb +26 -7
  145. data/lib/e11y/metrics/cardinality_protection.rb +10 -15
  146. data/lib/e11y/metrics/cardinality_tracker.rb +16 -6
  147. data/lib/e11y/metrics/registry.rb +3 -5
  148. data/lib/e11y/metrics/test_backend.rb +62 -0
  149. data/lib/e11y/metrics.rb +56 -10
  150. data/lib/e11y/middleware/adapter_resolver.rb +40 -0
  151. data/lib/e11y/middleware/audit_signing.rb +43 -6
  152. data/lib/e11y/middleware/baggage_protection.rb +75 -0
  153. data/lib/e11y/middleware/dev_log_source.rb +24 -0
  154. data/lib/e11y/middleware/event_slo.rb +23 -9
  155. data/lib/e11y/middleware/otel_span.rb +23 -0
  156. data/lib/e11y/middleware/pii_filter.rb +104 -75
  157. data/lib/e11y/middleware/rate_limiting.rb +54 -27
  158. data/lib/e11y/middleware/request.rb +70 -23
  159. data/lib/e11y/middleware/routing.rb +78 -21
  160. data/lib/e11y/middleware/sampling.rb +66 -17
  161. data/lib/e11y/middleware/self_monitoring_emit.rb +39 -0
  162. data/lib/e11y/middleware/trace_context.rb +45 -10
  163. data/lib/e11y/middleware/track_latency.rb +34 -0
  164. data/lib/e11y/middleware/validation.rb +7 -16
  165. data/lib/e11y/middleware/versioning.rb +26 -22
  166. data/lib/e11y/opentelemetry/semantic_conventions.rb +109 -0
  167. data/lib/e11y/opentelemetry/span_creator.rb +142 -0
  168. data/lib/e11y/pii/patterns.rb +12 -1
  169. data/lib/e11y/pipeline/builder.rb +1 -1
  170. data/lib/e11y/presets/audit_event.rb +13 -2
  171. data/lib/e11y/railtie.rb +52 -15
  172. data/lib/e11y/registry.rb +306 -0
  173. data/lib/e11y/reliability/circuit_breaker.rb +19 -21
  174. data/lib/e11y/reliability/dlq/base.rb +71 -0
  175. data/lib/e11y/reliability/dlq/file_adapter.rb +301 -0
  176. data/lib/e11y/reliability/dlq/file_storage.rb +63 -34
  177. data/lib/e11y/reliability/dlq/filter.rb +37 -54
  178. data/lib/e11y/reliability/retry_handler.rb +26 -29
  179. data/lib/e11y/reliability/retry_rate_limiter.rb +3 -11
  180. data/lib/e11y/sampling/error_spike_detector.rb +0 -2
  181. data/lib/e11y/sampling/load_monitor.rb +5 -9
  182. data/lib/e11y/sampling/stratified_tracker.rb +18 -0
  183. data/lib/e11y/self_monitoring/buffer_monitor.rb +2 -0
  184. data/lib/e11y/self_monitoring/performance_monitor.rb +19 -61
  185. data/lib/e11y/self_monitoring/reliability_monitor.rb +4 -74
  186. data/lib/e11y/slo/config_loader.rb +40 -0
  187. data/lib/e11y/slo/config_validator.rb +58 -0
  188. data/lib/e11y/slo/dashboard_generator.rb +122 -0
  189. data/lib/e11y/slo/event_driven.rb +8 -0
  190. data/lib/e11y/slo/tracker.rb +31 -4
  191. data/lib/e11y/testing/have_tracked_event_matcher.rb +190 -0
  192. data/lib/e11y/testing/rspec_matchers.rb +21 -0
  193. data/lib/e11y/testing/snapshot_matcher.rb +86 -0
  194. data/lib/e11y/trace_context/sampler.rb +35 -0
  195. data/lib/e11y/tracing/faraday_middleware.rb +31 -0
  196. data/lib/e11y/tracing/net_http_patch.rb +33 -0
  197. data/lib/e11y/tracing/propagator.rb +116 -0
  198. data/lib/e11y/tracing.rb +47 -0
  199. data/lib/e11y/version.rb +1 -1
  200. data/lib/e11y/versioning/version_extractor.rb +32 -0
  201. data/lib/e11y.rb +141 -265
  202. data/lib/generators/e11y/event/event_generator.rb +22 -0
  203. data/lib/generators/e11y/event/templates/event.rb.tt +16 -0
  204. data/lib/generators/e11y/grafana_dashboard/grafana_dashboard_generator.rb +30 -0
  205. data/lib/generators/e11y/grafana_dashboard/templates/e11y_dashboard.json +81 -0
  206. data/lib/generators/e11y/install/install_generator.rb +34 -0
  207. data/lib/generators/e11y/install/templates/e11y.rb +239 -0
  208. data/lib/generators/e11y/prometheus_alerts/prometheus_alerts_generator.rb +29 -0
  209. data/lib/generators/e11y/prometheus_alerts/templates/e11y_alerts.yml +28 -0
  210. data/lib/tasks/e11y_docs.rake +30 -0
  211. data/lib/tasks/e11y_events.rake +71 -0
  212. data/lib/tasks/e11y_lint.rake +91 -0
  213. data/lib/tasks/e11y_slo.rake +29 -0
  214. metadata +129 -39
  215. data/docs/ADR-010-developer-experience.md +0 -2166
  216. data/docs/API-REFERENCE-L28.md +0 -914
  217. data/docs/COMPREHENSIVE-CONFIGURATION.md +0 -2366
  218. data/docs/CONTRIBUTING.md +0 -312
  219. data/docs/IMPLEMENTATION_NOTES.md +0 -2804
  220. data/docs/IMPLEMENTATION_PLAN.md +0 -1971
  221. data/docs/IMPLEMENTATION_PLAN_ARCHITECTURE.md +0 -586
  222. data/docs/PLAN.md +0 -148
  223. data/docs/README.md +0 -296
  224. data/docs/design/00-memory-optimization.md +0 -593
  225. data/docs/guides/MIGRATION-L27-L28.md +0 -692
  226. data/docs/guides/PERFORMANCE-BENCHMARKS.md +0 -434
  227. data/docs/guides/README.md +0 -44
  228. data/docs/use_cases/UC-003-pattern-based-metrics.md +0 -1627
  229. data/lib/e11y/adapters/registry.rb +0 -141
  230. /data/docs/{ADR-012-event-evolution.md → architecture/ADR-012-event-evolution.md} +0 -0
@@ -0,0 +1,29 @@
1
+ # Rails Integration
2
+
3
+ > Back to [README](../README.md#documentation)
4
+
5
+ E11y integrates with Rails via Railtie.
6
+
7
+ ## Auto-Instrumented Components
8
+
9
+ E11y includes event definitions for common Rails components:
10
+
11
+ | Component | Event Classes | Location |
12
+ |-----------|--------------|----------|
13
+ | **HTTP Requests** | Request, StartProcessing, Redirect, SendFile | `lib/e11y/events/rails/http/` |
14
+ | **ActiveRecord** | Query | `lib/e11y/events/rails/database/` |
15
+ | **ActiveJob** | Enqueued, Started, Completed, Failed, Scheduled | `lib/e11y/events/rails/job/` |
16
+ | **Cache** | Read, Write, Delete | `lib/e11y/events/rails/cache/` |
17
+ | **View** | Render | `lib/e11y/events/rails/view/` |
18
+
19
+ Enable instrumentation in your configuration as needed.
20
+
21
+ ## Sidekiq Integration
22
+
23
+ E11y includes Sidekiq instrumentation support. Configure in your initializer:
24
+
25
+ ```ruby
26
+ E11y.configure do |config|
27
+ config.rails_instrumentation_enabled = true
28
+ end
29
+ ```
@@ -0,0 +1,63 @@
1
+ # Schema Validation
2
+
3
+ > Back to [README](../README.md#documentation)
4
+
5
+ E11y validates event data using [dry-schema](https://dry-rb.org/gems/dry-schema/).
6
+
7
+ ## Basic Example
8
+
9
+ ```ruby
10
+ class OrderCreatedEvent < E11y::Event::Base
11
+ schema do
12
+ required(:order_id).filled(:string)
13
+ required(:total).filled(:float, gt?: 0)
14
+ required(:currency).filled(:string, included_in?: %w[USD EUR GBP])
15
+ optional(:coupon_code).maybe(:string)
16
+ end
17
+ end
18
+
19
+ # Valid event
20
+ OrderCreatedEvent.track(order_id: "123", total: 99.99, currency: "USD")
21
+
22
+ # Invalid event raises E11y::ValidationError
23
+ OrderCreatedEvent.track(order_id: nil, total: -10, currency: "INVALID")
24
+ # => ValidationError: order_id is missing, total must be > 0
25
+ ```
26
+
27
+ ## Validation Modes
28
+
29
+ For high-frequency events, you can configure validation behavior:
30
+
31
+ ```ruby
32
+ class HighFrequencyEvent < E11y::Event::Base
33
+ # Always validate (default)
34
+ validation_mode :always
35
+
36
+ # Sampled validation (validate 1% of events)
37
+ validation_mode :sampled, sample_rate: 0.01
38
+
39
+ # Never validate (use with caution)
40
+ validation_mode :never
41
+ end
42
+ ```
43
+
44
+ Use `:always` for user input and critical events. Use `:sampled` for high-frequency internal events. Use `:never` only for trusted, typed input.
45
+
46
+ ## Validation Behavior
47
+
48
+ By default, invalid events raise `E11y::ValidationError`:
49
+
50
+ ```ruby
51
+ OrderEvent.track(order_id: nil)
52
+ # => E11y::ValidationError
53
+ ```
54
+
55
+ To handle validation errors gracefully:
56
+
57
+ ```ruby
58
+ begin
59
+ OrderEvent.track(order_id: nil)
60
+ rescue E11y::ValidationError => e
61
+ Rails.logger.warn "Invalid event: #{e.message}"
62
+ end
63
+ ```
@@ -0,0 +1,161 @@
1
+ # SLO: PromQL Queries and Alert Rules
2
+
3
+ > Back to [README](../README.md#documentation)
4
+
5
+ E11y emits SLO metrics to Prometheus via Yabeda. Use these PromQL queries and alert rules in Grafana dashboards and Prometheus.
6
+
7
+ **Metrics emitted:**
8
+ - `slo_http_requests_total{controller, action, status}` — HTTP request count
9
+ - `slo_http_request_duration_seconds` — HTTP latency histogram
10
+ - `slo_background_jobs_total{job_class, status, queue}` — Job count
11
+ - `slo_event_result_total{slo_name, slo_status}` — Event-driven SLO (EventSlo middleware)
12
+ - `e11y_track_duration_seconds` — E11y pipeline latency (TrackLatency middleware)
13
+ - `e11y_events_tracked_total{result, event_name}` — E11y delivery success/drop
14
+
15
+ ---
16
+
17
+ ## HTTP Availability SLO
18
+
19
+ **Success rate (30d window):**
20
+ ```promql
21
+ sum(rate(slo_http_requests_total{status=~"2..|3.."}[30d])) by (controller, action)
22
+ /
23
+ sum(rate(slo_http_requests_total[30d])) by (controller, action)
24
+ ```
25
+
26
+ **Error rate (5m, for alerts):**
27
+ ```promql
28
+ sum(rate(slo_http_requests_total{status=~"4..|5.."}[5m])) by (controller, action)
29
+ /
30
+ sum(rate(slo_http_requests_total[5m])) by (controller, action)
31
+ ```
32
+
33
+ **Per-endpoint availability (99.9% target):**
34
+ ```promql
35
+ # Replace OrdersController, create with your controller#action
36
+ sum(rate(slo_http_requests_total{controller="OrdersController",action="create",status=~"2..|3.."}[30d]))
37
+ /
38
+ sum(rate(slo_http_requests_total{controller="OrdersController",action="create"}[30d]))
39
+ ```
40
+
41
+ ---
42
+
43
+ ## HTTP Latency SLO
44
+
45
+ **p99 latency (30d):**
46
+ ```promql
47
+ histogram_quantile(0.99,
48
+ sum(rate(slo_http_request_duration_seconds_bucket[30d])) by (le, controller, action)
49
+ )
50
+ ```
51
+
52
+ **p99 > 500ms alert:**
53
+ ```promql
54
+ histogram_quantile(0.99,
55
+ sum(rate(slo_http_request_duration_seconds_bucket[5m])) by (le, controller, action)
56
+ ) > 0.5
57
+ ```
58
+
59
+ ---
60
+
61
+ ## Event-Driven SLO (EventSlo)
62
+
63
+ **Success rate by slo_name (30d):**
64
+ ```promql
65
+ sum(rate(slo_event_result_total{slo_status="success"}[30d])) by (slo_name)
66
+ /
67
+ sum(rate(slo_event_result_total[30d])) by (slo_name)
68
+ ```
69
+
70
+ **Example — payment success rate:**
71
+ ```promql
72
+ sum(rate(slo_event_result_total{slo_name="payment_success_rate",slo_status="success"}[30d]))
73
+ /
74
+ sum(rate(slo_event_result_total{slo_name="payment_success_rate"}[30d]))
75
+ ```
76
+
77
+ ---
78
+
79
+ ## E11y Self-Monitoring
80
+
81
+ **E11y pipeline latency p99 (<1ms target):**
82
+ ```promql
83
+ histogram_quantile(0.99,
84
+ sum(rate(e11y_track_duration_seconds_bucket[30d])) by (le)
85
+ )
86
+ ```
87
+
88
+ **E11y delivery success rate (99.9% target):**
89
+ ```promql
90
+ sum(rate(e11y_events_tracked_total{result="success"}[30d]))
91
+ /
92
+ sum(rate(e11y_events_tracked_total[30d]))
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Prometheus Alert Rules
98
+
99
+ Save as `prometheus/alerts/e11y_slo.yml`:
100
+
101
+ ```yaml
102
+ groups:
103
+ - name: e11y_slo_http
104
+ rules:
105
+ - alert: SLOHttpAvailabilityLow
106
+ expr: |
107
+ sum(rate(slo_http_requests_total{status=~"4..|5.."}[5m])) by (controller, action)
108
+ /
109
+ sum(rate(slo_http_requests_total[5m])) by (controller, action)
110
+ > 0.01
111
+ for: 5m
112
+ labels:
113
+ severity: warning
114
+ annotations:
115
+ summary: "HTTP availability below 99%"
116
+ description: "Error rate > 1% for 5 minutes"
117
+
118
+ - alert: SLOHttpLatencyHigh
119
+ expr: |
120
+ histogram_quantile(0.99,
121
+ sum(rate(slo_http_request_duration_seconds_bucket[5m])) by (le, controller, action)
122
+ ) > 0.5
123
+ for: 5m
124
+ labels:
125
+ severity: warning
126
+ annotations:
127
+ summary: "HTTP p99 latency > 500ms"
128
+
129
+ - name: e11y_self_monitoring
130
+ rules:
131
+ - alert: E11yTrackLatencyHigh
132
+ expr: |
133
+ histogram_quantile(0.99,
134
+ sum(rate(e11y_track_duration_seconds_bucket[5m])) by (le)
135
+ ) > 0.001
136
+ for: 5m
137
+ labels:
138
+ severity: warning
139
+ annotations:
140
+ summary: "E11y track() p99 > 1ms"
141
+
142
+ - alert: E11yDeliveryRateLow
143
+ expr: |
144
+ sum(rate(e11y_events_tracked_total{result="success"}[1h]))
145
+ /
146
+ sum(rate(e11y_events_tracked_total[1h]))
147
+ < 0.999
148
+ for: 5m
149
+ labels:
150
+ severity: warning
151
+ annotations:
152
+ summary: "E11y delivery rate below 99.9%"
153
+ ```
154
+
155
+ ---
156
+
157
+ ## Grafana Dashboard
158
+
159
+ Use `rake e11y:slo:dashboard` to generate a dashboard from `slo.yml`, or add panels manually with the PromQL above.
160
+
161
+ **Metric name prefix:** Yabeda exports with `yabeda_` prefix. If queries return no data, try `yabeda_e11y_slo_http_requests_total` or check your Prometheus scrape config.
data/docs/TESTING.md ADDED
@@ -0,0 +1,69 @@
1
+ # Testing
2
+
3
+ > Back to [README](../README.md#documentation)
4
+
5
+ Use the **InMemoryTest** adapter for testing. It extends `InMemory` and overrides `last_event` to skip Rails auto-instrumentation events (`E11y::Events::Rails::*`), so your business events aren't obscured by request lifecycle events.
6
+
7
+ ## Setup
8
+
9
+ ```ruby
10
+ # spec/rails_helper.rb or spec/spec_helper.rb
11
+ RSpec.configure do |config|
12
+ config.before(:each) do
13
+ E11y.configure do |e11y_config|
14
+ e11y_config.adapters[:test] = E11y::Adapters::InMemoryTest.new
15
+ end
16
+ end
17
+
18
+ config.after(:each) do
19
+ E11y.configuration.adapters[:test]&.clear!
20
+ end
21
+ end
22
+ ```
23
+
24
+ ## Test Events
25
+
26
+ ```ruby
27
+ RSpec.describe OrdersController do
28
+ let(:test_adapter) { E11y.configuration.adapters[:test] }
29
+
30
+ it "tracks order creation" do
31
+ post :create, params: { item: "Book", price: 29.99 }
32
+
33
+ events = test_adapter.events
34
+ expect(events).to include(
35
+ a_hash_including(
36
+ event_name: "OrderCreatedEvent",
37
+ payload: hash_including(item: "Book", price: 29.99)
38
+ )
39
+ )
40
+ end
41
+
42
+ it "does not track payment for free orders" do
43
+ post :create, params: { item: "Free Book", price: 0 }
44
+
45
+ payment_events = test_adapter.events.select { |e| e[:event_name] == "PaymentProcessedEvent" }
46
+ expect(payment_events).to be_empty
47
+ end
48
+ end
49
+ ```
50
+
51
+ ## InMemoryTest Adapter API
52
+
53
+ ```ruby
54
+ test_adapter = E11y::Adapters::InMemoryTest.new
55
+
56
+ # Get all events
57
+ test_adapter.events # => Array<Hash>
58
+
59
+ # Count events
60
+ test_adapter.event_count # => Integer
61
+
62
+ # Find last event (skips Rails instrumentation events)
63
+ test_adapter.last_event # => Hash
64
+
65
+ # Clear all events
66
+ test_adapter.clear!
67
+ ```
68
+
69
+ > **Note:** Use `InMemoryTest` in test suites; use `InMemory` in production configs (e.g. benchmarks).
@@ -66,12 +66,12 @@ Modern Rails applications need:
66
66
  - ✅ Zero-allocation event tracking (class methods only)
67
67
  - ✅ <1ms p99 latency @ 1000 events/sec
68
68
  - ✅ <100MB memory footprint
69
- - ✅ Rails 8.0+ exclusive
69
+ - ✅ Rails 7.0+ (7.x, 8.x)
70
70
  - ✅ Open-source extensibility
71
71
 
72
72
  **Non-Goals:**
73
73
  - ❌ Plain Ruby support (Rails only)
74
- - ❌ Backwards compatibility with Rails 7.x
74
+ - ❌ Rails 6.x and earlier
75
75
  - ❌ Hot configuration reload
76
76
  - ❌ Distributed tracing coordination (only propagation)
77
77
 
@@ -452,9 +452,9 @@ graph TB
452
452
  end
453
453
 
454
454
  subgraph "Thread-Local Storage"
455
- TL1[Current.trace_id<br/>Current.request_buffer]
456
- TL2[Current.trace_id<br/>Current.request_buffer]
457
- TL3[Current.trace_id<br/>Current.request_buffer]
455
+ TL1[Current.trace_id<br/>EphemeralBuffer<br/>Thread.current]
456
+ TL2[Current.trace_id<br/>EphemeralBuffer<br/>Thread.current]
457
+ TL3[Current.trace_id<br/>EphemeralBuffer<br/>Thread.current]
458
458
  end
459
459
 
460
460
  subgraph "Shared Resources"
@@ -805,7 +805,7 @@ class RoutingMiddleware < E11y::Middleware
805
805
 
806
806
  if severity == :debug
807
807
  # Route to request-scoped buffer
808
- RequestBuffer.add(event_data)
808
+ EphemeralBuffer.add_event(event_data)
809
809
  else
810
810
  # Route to main buffer
811
811
  MainBuffer.add(event_data)
@@ -1239,74 +1239,46 @@ end
1239
1239
 
1240
1240
  ### 3.4. Request-Scoped Buffer
1241
1241
 
1242
- **Design Decision:** Thread-local storage using ActiveSupport::CurrentAttributes.
1242
+ **Design Decision:** Thread-local storage via `EphemeralBuffer` with `Thread.current[:e11y_ephemeral_buffer]`. Context (trace_id, request_id) in `E11y::Current` (ActiveSupport::CurrentAttributes); buffer is separate.
1243
1243
 
1244
1244
  ```ruby
1245
1245
  module E11y
1246
1246
  class Current < ActiveSupport::CurrentAttributes
1247
- # Thread-local attributes
1247
+ # Thread-local context attributes (no buffer here)
1248
1248
  attribute :trace_id
1249
1249
  attribute :user_id
1250
1250
  attribute :request_id
1251
- attribute :request_buffer # Debug events buffer
1252
1251
  attribute :sampled # Sampling decision
1253
-
1254
- def request_buffer
1255
- attributes[:request_buffer] ||= []
1256
- end
1257
-
1258
- def add_debug_event(event_data)
1259
- request_buffer << event_data if request_buffer.size < Config.request_buffer_limit
1260
- end
1261
-
1262
- def flush_debug_events
1263
- events = request_buffer.dup
1264
- request_buffer.clear
1265
- events
1266
- end
1267
1252
  end
1268
-
1269
- # Request-scoped buffer manager
1270
- class RequestBuffer
1271
- class << self
1272
- def add(event_data)
1273
- Current.add_debug_event(event_data)
1253
+
1254
+ module Buffers
1255
+ class EphemeralBuffer
1256
+ THREAD_KEY_BUFFER = :e11y_ephemeral_buffer
1257
+
1258
+ def self.initialize!(request_id: nil, buffer_limit: nil)
1259
+ Thread.current[THREAD_KEY_BUFFER] = []
1274
1260
  end
1275
-
1276
- def flush
1277
- Current.flush_debug_events
1261
+
1262
+ def self.add_event(event_data)
1263
+ buf = Thread.current[THREAD_KEY_BUFFER]
1264
+ return false unless buf
1265
+ buf << event_data if buf.size < (buffer_limit || Config.buffer (job_buffer_limit))
1278
1266
  end
1279
-
1280
- def setup_rails_integration
1281
- # Hook into Rails request cycle
1282
- ActiveSupport::Notifications.subscribe('process_action.action_controller') do |*args|
1283
- event = ActiveSupport::Notifications::Event.new(*args)
1284
-
1285
- # Flush on error
1286
- if event.payload[:exception]
1287
- flush_to_adapters
1288
- else
1289
- # Discard on success
1290
- flush
1291
- end
1292
- end
1267
+
1268
+ def self.flush_on_error
1269
+ # Flush buffered events to adapters
1293
1270
  end
1294
-
1295
- private
1296
-
1297
- def flush_to_adapters
1298
- events = flush
1299
-
1300
- # Send debug events to adapters
1301
- events.each do |event_data|
1302
- MainBuffer.add(event_data)
1303
- end
1271
+
1272
+ def self.discard
1273
+ Thread.current[THREAD_KEY_BUFFER] = nil
1304
1274
  end
1305
1275
  end
1306
1276
  end
1307
1277
  end
1308
1278
  ```
1309
1279
 
1280
+ Rails integration: `Middleware::Request` and Sidekiq/ActiveJob instruments call `EphemeralBuffer.initialize!`, `flush_on_error`, `discard` at request/job boundaries.
1281
+
1310
1282
  ---
1311
1283
 
1312
1284
  ### 3.5. Adapter Base Class
@@ -1608,7 +1580,7 @@ Pipeline.process(event_data)
1608
1580
  └─ next
1609
1581
 
1610
1582
  7. RoutingMiddleware
1611
- ├─ severity == :debug? → RequestBuffer
1583
+ ├─ severity == :debug? → EphemeralBuffer
1612
1584
  └─ severity == :info+? → MainBuffer
1613
1585
 
1614
1586
  Buffer → Adapters (receive normalized event_name)
@@ -1636,6 +1608,9 @@ Buffer → Adapters (receive normalized event_name)
1636
1608
 
1637
1609
  ## 5. Memory Optimization Strategy
1638
1610
 
1611
+ > **📖 For full design, implementation details, and trade-offs, see:**
1612
+ > **[ADR-018: Memory Optimization](ADR-018-memory-optimization.md)**
1613
+
1639
1614
  ### 5.1. Zero-Allocation Pattern
1640
1615
 
1641
1616
  **Key Principle:** No object instances, only hashes.
@@ -1754,8 +1729,8 @@ end
1754
1729
  **Components:**
1755
1730
 
1756
1731
  1. **Thread-local (no sync needed):**
1757
- - Request-scoped buffer (Current.request_buffer)
1758
- - Context (Current.trace_id, etc.)
1732
+ - Request-scoped buffer (EphemeralBuffer + Thread.current[:e11y_ephemeral_buffer])
1733
+ - Context (Current.trace_id, request_id, etc.)
1759
1734
 
1760
1735
  2. **Concurrent (thread-safe):**
1761
1736
  - Main ring buffer (Concurrent::AtomicFixnum)
@@ -2037,7 +2012,7 @@ Gem::Specification.new do |spec|
2037
2012
  spec.required_ruby_version = '>= 3.3.0'
2038
2013
 
2039
2014
  # Required
2040
- spec.add_dependency 'rails', '>= 8.0.0'
2015
+ spec.add_dependency 'rails', '>= 7.0'
2041
2016
  spec.add_dependency 'dry-schema', '~> 1.13'
2042
2017
  spec.add_dependency 'dry-configurable', '~> 1.1'
2043
2018
  spec.add_dependency 'concurrent-ruby', '~> 1.2'
@@ -2602,10 +2577,6 @@ end
2602
2577
  - **[ADR-006: Security & Compliance](ADR-006-security-compliance.md)** - PII filtering, rate limiting
2603
2578
  - **[ADR-011: Testing Strategy](ADR-011-testing-strategy.md)** - Testing approach
2604
2579
 
2605
- **Configuration:**
2606
- - **[COMPREHENSIVE-CONFIGURATION.md](COMPREHENSIVE-CONFIGURATION.md)** - Complete configuration examples
2607
- - **[CONFLICT-ANALYSIS.md](CONFLICT-ANALYSIS.md)** - Feature conflict resolutions
2608
-
2609
2580
  **Use Cases:**
2610
2581
  - **[docs/use_cases/](use_cases/)** - All 22 use cases documented
2611
2582