e11y 0.2.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +130 -10
  3. data/CHANGELOG.md +80 -1
  4. data/CLAUDE.md +168 -0
  5. data/CONTRIBUTING.md +640 -0
  6. data/README.md +165 -701
  7. data/RELEASE.md +41 -12
  8. data/Rakefile +249 -57
  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 +79 -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} +36 -65
  30. data/docs/{ADR-002-metrics-yabeda.md → architecture/ADR-002-metrics-yabeda.md} +62 -236
  31. data/docs/architecture/ADR-003-slo-observability.md +1402 -0
  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} +182 -743
  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} +44 -86
  40. data/docs/{ADR-012-event-evolution.md → architecture/ADR-012-event-evolution.md} +11 -11
  41. data/docs/{ADR-013-reliability-error-handling.md → architecture/ADR-013-reliability-error-handling.md} +37 -12
  42. data/docs/{ADR-014-event-driven-slo.md → architecture/ADR-014-event-driven-slo.md} +12 -24
  43. data/docs/{ADR-015-middleware-order.md → architecture/ADR-015-middleware-order.md} +43 -59
  44. data/docs/{ADR-016-self-monitoring-slo.md → architecture/ADR-016-self-monitoring-slo.md} +58 -355
  45. data/docs/{ADR-017-multi-rails-compatibility.md → architecture/ADR-017-multi-rails-compatibility.md} +4 -11
  46. data/docs/architecture/ADR-018-memory-optimization.md +366 -0
  47. data/docs/{ADR-INDEX.md → architecture/ADR-INDEX.md} +11 -6
  48. data/docs/plans/2026-03-20-browser-overlay-svelte.md +281 -0
  49. data/docs/{00-ICP-AND-TIMELINE.md → prd/00-ICP-AND-TIMELINE.md} +6 -6
  50. data/docs/{01-SCALE-REQUIREMENTS.md → prd/01-SCALE-REQUIREMENTS.md} +6 -6
  51. data/docs/prd/01-overview-vision.md +19 -14
  52. data/docs/use_cases/README.md +22 -23
  53. data/docs/use_cases/UC-001-request-scoped-debug-buffering.md +50 -44
  54. data/docs/use_cases/UC-002-business-event-tracking.md +26 -95
  55. data/docs/use_cases/UC-003-event-metrics.md +66 -0
  56. data/docs/use_cases/UC-004-zero-config-slo-tracking.md +33 -684
  57. data/docs/use_cases/UC-005-sentry-integration.md +13 -15
  58. data/docs/use_cases/UC-006-trace-context-management.md +30 -28
  59. data/docs/use_cases/UC-007-pii-filtering.md +35 -87
  60. data/docs/use_cases/UC-008-opentelemetry-integration.md +51 -89
  61. data/docs/use_cases/UC-009-multi-service-tracing.md +30 -178
  62. data/docs/use_cases/UC-010-background-job-tracking.md +24 -91
  63. data/docs/use_cases/UC-011-rate-limiting.md +95 -168
  64. data/docs/use_cases/UC-012-audit-trail.md +21 -46
  65. data/docs/use_cases/UC-013-high-cardinality-protection.md +29 -167
  66. data/docs/use_cases/UC-014-adaptive-sampling.md +2 -2
  67. data/docs/use_cases/UC-015-cost-optimization.md +46 -99
  68. data/docs/use_cases/UC-016-rails-logger-migration.md +39 -213
  69. data/docs/use_cases/UC-017-local-development.md +203 -777
  70. data/docs/use_cases/UC-018-testing-events.md +3 -3
  71. data/docs/use_cases/UC-019-retention-based-routing.md +53 -106
  72. data/docs/use_cases/UC-020-event-versioning.md +8 -9
  73. data/docs/use_cases/UC-021-error-handling-retry-dlq.md +18 -22
  74. data/docs/use_cases/UC-022-event-registry.md +15 -21
  75. data/docs/use_cases/backlog.md +119 -87
  76. data/e11y.gemspec +2 -2
  77. data/gems/e11y-devtools/README.md +158 -0
  78. data/gems/e11y-devtools/config/routes.rb +15 -0
  79. data/gems/e11y-devtools/e11y-devtools.gemspec +25 -0
  80. data/gems/e11y-devtools/exe/e11y +34 -0
  81. data/gems/e11y-devtools/frontend/.gitignore +24 -0
  82. data/gems/e11y-devtools/frontend/README.md +51 -0
  83. data/gems/e11y-devtools/frontend/index.html +14 -0
  84. data/gems/e11y-devtools/frontend/package-lock.json +3707 -0
  85. data/gems/e11y-devtools/frontend/package.json +28 -0
  86. data/gems/e11y-devtools/frontend/public/mocks/v1/events/recent.json +4205 -0
  87. data/gems/e11y-devtools/frontend/public/mocks/v1/interactions.json +194 -0
  88. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/0a2e04027cfa22d014bc22e8b27cd913/events.json +86 -0
  89. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/0e1543af6a630fb3af6b52283154b3e0/events.json +169 -0
  90. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/1838b691faa49564f97db8592ff3978d/events.json +78 -0
  91. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/29f198f6588dacffb687777eb5f8f118/events.json +197 -0
  92. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/34bc3c9c0097de28a7a6f99b90a8e7bc/events.json +194 -0
  93. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/3ba6c20d068ab9cee00e51b180e66444/events.json +184 -0
  94. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/435bfd8f17b9009146a79812d7c3726d/events.json +144 -0
  95. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/4c7676e3fe668e99edb2b94d7d5678a9/events.json +222 -0
  96. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/6daf0d47974bedfc55d5de7004a3ea9f/events.json +194 -0
  97. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/8a81ada42834d15f287bb40010043605/events.json +194 -0
  98. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/8c0a98900edaae105469df8daedccf02/events.json +198 -0
  99. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/8e4f645180f8a7d1dce426b07380466b/events.json +222 -0
  100. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/93db346fa5d44a032605a13b627f4b80/events.json +128 -0
  101. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/98ff6146faf7bd9be8bd03a8275817ba/events.json +223 -0
  102. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/9997ddd0247bc7e25f2ca7a5c415c93d/events.json +197 -0
  103. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/99e35f8ef3baedd798cc4fd085980ad9/events.json +194 -0
  104. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/b4f3095c1909924cbc98889a86c83d6d/events.json +131 -0
  105. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/b54b7fc32b7575a7110de809d11ccda0/events.json +128 -0
  106. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/c0b48033fa06746bcc5886745e053cff/events.json +169 -0
  107. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/c44649ac76701b4558927cd2305ab535/events.json +169 -0
  108. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/d601ae3320057580a39dbdac2edfdf4a/events.json +248 -0
  109. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/e67e724bab422d2b52eeb49635e512e1/events.json +194 -0
  110. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/e6c72765a28f158a8485b35fa63f73da/events.json +194 -0
  111. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/f541b87405c9a54819b18ebe529f6419/events.json +194 -0
  112. data/gems/e11y-devtools/frontend/scripts/generate_mocks.rb +397 -0
  113. data/gems/e11y-devtools/frontend/src/App.svelte +827 -0
  114. data/gems/e11y-devtools/frontend/src/components/Fab.svelte +19 -0
  115. data/gems/e11y-devtools/frontend/src/components/FilterBar.svelte +38 -0
  116. data/gems/e11y-devtools/frontend/src/components/FullscreenPanel.svelte +82 -0
  117. data/gems/e11y-devtools/frontend/src/components/InteractionsTimeline.svelte +264 -0
  118. data/gems/e11y-devtools/frontend/src/components/RecentHistogram.svelte +354 -0
  119. data/gems/e11y-devtools/frontend/src/lib/api.ts +37 -0
  120. data/gems/e11y-devtools/frontend/src/lib/eventIdentity.ts +12 -0
  121. data/gems/e11y-devtools/frontend/src/lib/format.ts +37 -0
  122. data/gems/e11y-devtools/frontend/src/lib/listFilter.ts +43 -0
  123. data/gems/e11y-devtools/frontend/src/lib/recentVolume.ts +80 -0
  124. data/gems/e11y-devtools/frontend/src/lib/router.ts +12 -0
  125. data/gems/e11y-devtools/frontend/src/lib/transitions.ts +34 -0
  126. data/gems/e11y-devtools/frontend/src/lib/viewportOrigin.ts +25 -0
  127. data/gems/e11y-devtools/frontend/src/main.ts +8 -0
  128. data/gems/e11y-devtools/frontend/src/overlay-entry.ts +24 -0
  129. data/gems/e11y-devtools/frontend/src/overlay.css +1080 -0
  130. data/gems/e11y-devtools/frontend/svelte.config.js +2 -0
  131. data/gems/e11y-devtools/frontend/test_puppeteer.js +41 -0
  132. data/gems/e11y-devtools/frontend/test_scale.js +3 -0
  133. data/gems/e11y-devtools/frontend/tsconfig.app.json +21 -0
  134. data/gems/e11y-devtools/frontend/tsconfig.json +7 -0
  135. data/gems/e11y-devtools/frontend/tsconfig.node.json +26 -0
  136. data/gems/e11y-devtools/frontend/vite.config.ts +36 -0
  137. data/gems/e11y-devtools/lib/e11y/devtools/mcp/server.rb +96 -0
  138. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tool_base.rb +25 -0
  139. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/clear.rb +31 -0
  140. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/errors.rb +35 -0
  141. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/event_detail.rb +33 -0
  142. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/events_by_trace.rb +33 -0
  143. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/interactions.rb +40 -0
  144. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/recent_events.rb +34 -0
  145. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/search.rb +34 -0
  146. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/stats.rb +30 -0
  147. data/gems/e11y-devtools/lib/e11y/devtools/overlay/assets/overlay.js +20 -0
  148. data/gems/e11y-devtools/lib/e11y/devtools/overlay/controller.rb +94 -0
  149. data/gems/e11y-devtools/lib/e11y/devtools/overlay/engine.rb +26 -0
  150. data/gems/e11y-devtools/lib/e11y/devtools/overlay/middleware.rb +80 -0
  151. data/gems/e11y-devtools/lib/e11y/devtools/overlay/rails_controller.rb +67 -0
  152. data/gems/e11y-devtools/lib/e11y/devtools/tui/app.rb +262 -0
  153. data/gems/e11y-devtools/lib/e11y/devtools/tui/grouping.rb +66 -0
  154. data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/event_detail.rb +62 -0
  155. data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/event_list.rb +70 -0
  156. data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/interaction_list.rb +47 -0
  157. data/gems/e11y-devtools/lib/e11y/devtools/version.rb +8 -0
  158. data/gems/e11y-devtools/lib/e11y/devtools.rb +13 -0
  159. data/gems/e11y-devtools/spec/e11y/devtools/mcp/tools_spec.rb +107 -0
  160. data/gems/e11y-devtools/spec/e11y/devtools/overlay/controller_spec.rb +91 -0
  161. data/gems/e11y-devtools/spec/e11y/devtools/overlay/middleware_spec.rb +46 -0
  162. data/gems/e11y-devtools/spec/e11y/devtools/tui/app_spec.rb +85 -0
  163. data/gems/e11y-devtools/spec/e11y/devtools/tui/grouping_spec.rb +64 -0
  164. data/gems/e11y-devtools/spec/spec_helper.rb +5 -0
  165. data/gems/e11y-devtools/spec/tui/widgets/event_list_spec.rb +44 -0
  166. data/gems/e11y-devtools/spec/tui/widgets/interaction_list_spec.rb +62 -0
  167. data/lib/e11y/adapters/audit_encrypted.rb +53 -11
  168. data/lib/e11y/adapters/base.rb +33 -34
  169. data/lib/e11y/adapters/dev_log/file_store.rb +143 -0
  170. data/lib/e11y/adapters/dev_log/query.rb +219 -0
  171. data/lib/e11y/adapters/dev_log.rb +118 -0
  172. data/lib/e11y/adapters/file.rb +3 -6
  173. data/lib/e11y/adapters/in_memory.rb +52 -5
  174. data/lib/e11y/adapters/in_memory_test.rb +29 -0
  175. data/lib/e11y/adapters/loki.rb +58 -23
  176. data/lib/e11y/adapters/null.rb +82 -0
  177. data/lib/e11y/adapters/opentelemetry_collector.rb +183 -0
  178. data/lib/e11y/adapters/otel_logs.rb +136 -23
  179. data/lib/e11y/adapters/sentry.rb +4 -7
  180. data/lib/e11y/adapters/stdout.rb +73 -7
  181. data/lib/e11y/adapters/yabeda.rb +153 -29
  182. data/lib/e11y/buffers/adaptive_buffer.rb +3 -17
  183. data/lib/e11y/buffers/{request_scoped_buffer.rb → ephemeral_buffer.rb} +72 -58
  184. data/lib/e11y/buffers/ring_buffer.rb +3 -16
  185. data/lib/e11y/configuration.rb +272 -0
  186. data/lib/e11y/console.rb +10 -17
  187. data/lib/e11y/current.rb +53 -1
  188. data/lib/e11y/debug/pipeline_inspector.rb +96 -0
  189. data/lib/e11y/documentation/generator.rb +48 -0
  190. data/lib/e11y/event/base.rb +176 -82
  191. data/lib/e11y/event/value_sampling_config.rb +1 -5
  192. data/lib/e11y/events/rails/database/query.rb +1 -4
  193. data/lib/e11y/events/rails/job/failed.rb +2 -0
  194. data/lib/e11y/instruments/active_job.rb +44 -12
  195. data/lib/e11y/instruments/rails_instrumentation.rb +49 -24
  196. data/lib/e11y/instruments/sidekiq.rb +135 -31
  197. data/lib/e11y/linters/base.rb +11 -0
  198. data/lib/e11y/linters/pii/pii_declaration_linter.rb +120 -0
  199. data/lib/e11y/linters/slo/config_consistency_linter.rb +76 -0
  200. data/lib/e11y/linters/slo/explicit_declaration_linter.rb +36 -0
  201. data/lib/e11y/linters/slo/slo_status_from_linter.rb +41 -0
  202. data/lib/e11y/logger/bridge.rb +26 -7
  203. data/lib/e11y/metrics/cardinality_protection.rb +10 -15
  204. data/lib/e11y/metrics/cardinality_tracker.rb +16 -6
  205. data/lib/e11y/metrics/registry.rb +3 -5
  206. data/lib/e11y/metrics/test_backend.rb +62 -0
  207. data/lib/e11y/metrics.rb +56 -10
  208. data/lib/e11y/middleware/adapter_resolver.rb +40 -0
  209. data/lib/e11y/middleware/audit_signing.rb +43 -6
  210. data/lib/e11y/middleware/baggage_protection.rb +75 -0
  211. data/lib/e11y/middleware/dev_log_source.rb +24 -0
  212. data/lib/e11y/middleware/event_slo.rb +23 -9
  213. data/lib/e11y/middleware/otel_span.rb +23 -0
  214. data/lib/e11y/middleware/pii_filter.rb +104 -75
  215. data/lib/e11y/middleware/rate_limiting.rb +54 -27
  216. data/lib/e11y/middleware/request.rb +70 -23
  217. data/lib/e11y/middleware/routing.rb +78 -21
  218. data/lib/e11y/middleware/sampling.rb +66 -17
  219. data/lib/e11y/middleware/self_monitoring_emit.rb +39 -0
  220. data/lib/e11y/middleware/trace_context.rb +45 -10
  221. data/lib/e11y/middleware/track_latency.rb +34 -0
  222. data/lib/e11y/middleware/validation.rb +7 -16
  223. data/lib/e11y/middleware/versioning.rb +26 -22
  224. data/lib/e11y/opentelemetry/semantic_conventions.rb +109 -0
  225. data/lib/e11y/opentelemetry/span_creator.rb +142 -0
  226. data/lib/e11y/pii/patterns.rb +12 -1
  227. data/lib/e11y/pipeline/builder.rb +4 -4
  228. data/lib/e11y/presets/audit_event.rb +13 -2
  229. data/lib/e11y/railtie.rb +52 -14
  230. data/lib/e11y/registry.rb +306 -0
  231. data/lib/e11y/reliability/circuit_breaker.rb +19 -21
  232. data/lib/e11y/reliability/dlq/base.rb +71 -0
  233. data/lib/e11y/reliability/dlq/file_adapter.rb +301 -0
  234. data/lib/e11y/reliability/dlq/file_storage.rb +63 -34
  235. data/lib/e11y/reliability/dlq/filter.rb +37 -54
  236. data/lib/e11y/reliability/retry_handler.rb +26 -29
  237. data/lib/e11y/reliability/retry_rate_limiter.rb +3 -11
  238. data/lib/e11y/sampling/error_spike_detector.rb +0 -2
  239. data/lib/e11y/sampling/load_monitor.rb +5 -9
  240. data/lib/e11y/sampling/stratified_tracker.rb +18 -0
  241. data/lib/e11y/self_monitoring/buffer_monitor.rb +2 -0
  242. data/lib/e11y/self_monitoring/performance_monitor.rb +19 -61
  243. data/lib/e11y/self_monitoring/reliability_monitor.rb +4 -74
  244. data/lib/e11y/slo/config_loader.rb +40 -0
  245. data/lib/e11y/slo/config_validator.rb +58 -0
  246. data/lib/e11y/slo/dashboard_generator.rb +122 -0
  247. data/lib/e11y/slo/event_driven.rb +8 -0
  248. data/lib/e11y/slo/tracker.rb +31 -4
  249. data/lib/e11y/testing/have_tracked_event_matcher.rb +190 -0
  250. data/lib/e11y/testing/rspec_matchers.rb +21 -0
  251. data/lib/e11y/testing/snapshot_matcher.rb +86 -0
  252. data/lib/e11y/trace_context/sampler.rb +35 -0
  253. data/lib/e11y/tracing/faraday_middleware.rb +31 -0
  254. data/lib/e11y/tracing/net_http_patch.rb +33 -0
  255. data/lib/e11y/tracing/propagator.rb +144 -0
  256. data/lib/e11y/tracing.rb +47 -0
  257. data/lib/e11y/version.rb +1 -1
  258. data/lib/e11y/versioning/version_extractor.rb +32 -0
  259. data/lib/e11y.rb +123 -266
  260. data/lib/generators/e11y/event/event_generator.rb +22 -0
  261. data/lib/generators/e11y/event/templates/event.rb.tt +16 -0
  262. data/lib/generators/e11y/grafana_dashboard/grafana_dashboard_generator.rb +30 -0
  263. data/lib/generators/e11y/grafana_dashboard/templates/e11y_dashboard.json +81 -0
  264. data/lib/generators/e11y/install/install_generator.rb +34 -0
  265. data/lib/generators/e11y/install/templates/e11y.rb +239 -0
  266. data/lib/generators/e11y/prometheus_alerts/prometheus_alerts_generator.rb +29 -0
  267. data/lib/generators/e11y/prometheus_alerts/templates/e11y_alerts.yml +28 -0
  268. data/lib/tasks/e11y_docs.rake +30 -0
  269. data/lib/tasks/e11y_events.rake +71 -0
  270. data/lib/tasks/e11y_lint.rake +91 -0
  271. data/lib/tasks/e11y_slo.rake +29 -0
  272. metadata +186 -39
  273. data/docs/ADR-003-slo-observability.md +0 -3337
  274. data/docs/ADR-010-developer-experience.md +0 -2166
  275. data/docs/API-REFERENCE-L28.md +0 -914
  276. data/docs/COMPREHENSIVE-CONFIGURATION.md +0 -2366
  277. data/docs/CONTRIBUTING.md +0 -312
  278. data/docs/IMPLEMENTATION_NOTES.md +0 -2804
  279. data/docs/IMPLEMENTATION_PLAN.md +0 -1971
  280. data/docs/IMPLEMENTATION_PLAN_ARCHITECTURE.md +0 -586
  281. data/docs/PLAN.md +0 -148
  282. data/docs/README.md +0 -296
  283. data/docs/design/00-memory-optimization.md +0 -593
  284. data/docs/guides/MIGRATION-L27-L28.md +0 -692
  285. data/docs/guides/PERFORMANCE-BENCHMARKS.md +0 -434
  286. data/docs/guides/README.md +0 -44
  287. data/docs/use_cases/UC-003-pattern-based-metrics.md +0 -1627
  288. data/lib/e11y/adapters/registry.rb +0 -141
@@ -0,0 +1,79 @@
1
+ # Rails Integration
2
+
3
+ > Back to [README](../README.md#documentation)
4
+
5
+ E11y integrates with Rails via `E11y::Railtie` (`lib/e11y/railtie.rb`). After `bundle install`, requiring the gem in a Rails app loads the Railtie automatically.
6
+
7
+ ## Request middleware
8
+
9
+ `E11y::Middleware::Request` is inserted into the Rack stack (before `Rails::Rack::Logger` when present). It sets trace and request context on `E11y::Current`, optionally starts the **ephemeral (request-scoped) buffer** for debug events, and adds `X-E11y-Trace-Id` / `X-E11y-Span-Id` response headers.
10
+
11
+ ## Rails instrumentation (`ActiveSupport::Notifications`)
12
+
13
+ When **`config.rails_instrumentation_enabled = true`**, E11y subscribes to Rails instrumentation and maps notifications to typed events (see `lib/e11y/instruments/rails_instrumentation.rb`).
14
+
15
+ | Area | Event classes (under `E11y::Events::Rails::`) |
16
+ |------|-----------------------------------------------|
17
+ | HTTP | `Http::Request`, `Http::StartProcessing`, `Http::Redirect`, `Http::SendFile` |
18
+ | Database | `Database::Query` |
19
+ | Active Job (notification names) | `Job::Enqueued`, `Job::Scheduled`, `Job::Started`, `Job::Completed`, `Job::Failed` |
20
+ | Cache | `Cache::Read`, `Cache::Write`, `Cache::Delete` |
21
+ | Views | `View::Render` |
22
+
23
+ This is **independent** of the Sidekiq and Active Job toggles below: instrumentation listens to Rails; the job toggles add **extra** process integration (buffer lifecycle, middleware, callbacks).
24
+
25
+ ## Sidekiq
26
+
27
+ Enable **only if** you use Sidekiq:
28
+
29
+ ```ruby
30
+ E11y.configure do |config|
31
+ config.sidekiq_enabled = true
32
+ end
33
+ ```
34
+
35
+ The Railtie registers client and server middleware (`E11y::Instruments::Sidekiq`) so jobs participate in the same **ephemeral buffer** semantics as HTTP requests when `config.ephemeral_buffer_enabled` is true.
36
+
37
+ On enqueue, **`E11y::Current.user_id`** (when set, e.g. from request middleware) is merged into **`e11y_baggage`** together with any allowed `Current.baggage` keys. The worker restores **`E11y::Current.baggage`** and **`E11y::Current.user_id`** from that hash. Key **`user_id`** is in the default baggage allowlist (`E11y::BAGGAGE_PROTECTION_DEFAULT_ALLOWED_KEYS`).
38
+
39
+ ## Active Job
40
+
41
+ Enable when you want callbacks and buffer handling on **`ActiveJob::Base`** (and **`ApplicationJob`** when that constant is already defined at hook time):
42
+
43
+ ```ruby
44
+ E11y.configure do |config|
45
+ config.active_job_enabled = true
46
+ end
47
+ ```
48
+
49
+ You can use **both** `rails_instrumentation_enabled` and `active_job_enabled`; they complement each other. If you only enqueue via Sidekiq without Active Job, you may rely on `sidekiq_enabled` alone.
50
+
51
+ The **`before_enqueue`** callback applies the same **`e11y_baggage`** merge as Sidekiq (including **`user_id`** from `E11y::Current`).
52
+
53
+ ## Rails.logger bridge
54
+
55
+ Optional wrapper that still delegates to the original logger but also emits **`E11y::Events::Rails::Log::*`** events (`lib/e11y/events/rails/log.rb`):
56
+
57
+ ```ruby
58
+ E11y.configure do |config|
59
+ config.logger_bridge_enabled = true
60
+ # Optional: only these severities (Symbol or String); nil / empty = all
61
+ config.logger_bridge_track_severities = %i[warn error fatal]
62
+ # Optional: skip noisy lines (String substrings or Regexp)
63
+ config.logger_bridge_ignore_patterns = [%r{health}]
64
+ end
65
+ ```
66
+
67
+ Filtering uses **`logger_bridge_track_severities`** and **`logger_bridge_ignore_patterns`** only.
68
+
69
+ ## Configuration reference
70
+
71
+ | Flag | Purpose |
72
+ |------|---------|
73
+ | `rails_instrumentation_enabled` | Map `ActiveSupport::Notifications` to E11y events |
74
+ | `sidekiq_enabled` | Sidekiq client/server middleware |
75
+ | `active_job_enabled` | `ActiveJob::Base` / `ApplicationJob` callbacks |
76
+ | `logger_bridge_enabled` | Wrap `Rails.logger` with `E11y::Logger::Bridge` |
77
+ | `ephemeral_buffer_enabled` | Request/job-scoped debug buffer (see README) |
78
+
79
+ Further detail: [ADR-008: Rails integration](architecture/ADR-008-rails-integration.md), [Quick Start](QUICK-START.md).
@@ -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'
@@ -2376,7 +2351,7 @@ end
2376
2351
  **Middleware checks opt-out flag before processing:**
2377
2352
 
2378
2353
  ```ruby
2379
- # E11y::Middleware::PIIFiltering
2354
+ # E11y::Middleware::PIIFilter
2380
2355
  def call(event_data)
2381
2356
  event_class = event_data[:event_class]
2382
2357
 
@@ -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