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
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Status:** Implemented
4
4
  **Date:** January 12, 2026 (Updated: January 20, 2026)
5
- **Covers:** UC-003 (Pattern-Based Metrics), UC-013 (High Cardinality Protection)
5
+ **Covers:** UC-003 (Event Metrics), UC-013 (High Cardinality Protection)
6
6
  **Depends On:** ADR-001 (Core Architecture)
7
7
 
8
8
  **Implementation Notes:** Refactored to "Rails Way" architecture (January 20, 2026) - see [IMPLEMENTATION_NOTES.md](./IMPLEMENTATION_NOTES.md#2026-01-20-metrics-architecture-refactoring---rails-way-)
@@ -13,14 +13,13 @@
13
13
 
14
14
  1. [Context & Problem](#1-context--problem)
15
15
  2. [Architecture Overview](#2-architecture-overview)
16
- 3. [Pattern-Based Metrics](#3-pattern-based-metrics)
16
+ 3. [Event Metrics](#3-event-metrics)
17
17
  4. [Cardinality Protection](#4-cardinality-protection)
18
- - 4.1. [Four-Layer Defense](#41-four-layer-defense)
18
+ - 4.1. [Three-Layer Defense](#41-three-layer-defense)
19
19
  - 4.2. [Layer 1: Universal Denylist](#42-layer-1-universal-denylist)
20
- - 4.3. [Layer 2: Safe Allowlist](#43-layer-2-safe-allowlist)
21
- - 4.4. [Layer 3: Per-Metric Limits](#44-layer-3-per-metric-cardinality-limits)
22
- - 4.5. [Layer 4: Dynamic Actions](#45-layer-4-dynamic-actions)
23
- - 4.6. [Relabeling Rules](#46-relabeling-rules)
20
+ - 4.3. [Layer 2: Per-Metric Limits](#43-layer-2-per-metric-cardinality-limits)
21
+ - 4.4. [Layer 3: Dynamic Actions](#44-layer-3-dynamic-actions)
22
+ - 4.5. [Relabeling Rules](#45-relabeling-rules)
24
23
  5. [Yabeda Integration](#5-yabeda-integration)
25
24
  6. [Self-Monitoring](#6-self-monitoring)
26
25
  7. [Configuration](#7-configuration)
@@ -142,7 +141,7 @@ end
142
141
  # Adapter automatically:
143
142
  # 1. Finds matching metrics from Registry
144
143
  # 2. Extracts labels from event data
145
- # 3. Applies 3-layer cardinality protection
144
+ # 3. Applies 3-layer cardinality protection (denylist, per-metric limits, dynamic actions)
146
145
  # 4. Updates Yabeda metrics
147
146
  ```
148
147
 
@@ -178,21 +177,7 @@ end
178
177
 
179
178
  ### 0.5. Global Metrics via Registry
180
179
 
181
- **Pattern-based metrics for multiple events:**
182
-
183
- ```ruby
184
- # config/initializers/e11y.rb
185
- E11y.configure do |config|
186
- # Global metric for all order.* events
187
- E11y::Metrics::Registry.instance.register(
188
- type: :counter,
189
- pattern: 'order.*', # Matches order.created, order.paid, etc.
190
- name: :orders_total,
191
- tags: [:currency, :status],
192
- source: 'config/initializers/e11y.rb'
193
- )
194
- end
195
- ```
180
+ **Event-level metrics** each event class defines its own metrics via `metrics do ... end` DSL. The Registry validates and registers them at boot time.
196
181
 
197
182
  ### 0.6. Key Benefits
198
183
 
@@ -236,8 +221,8 @@ end
236
221
  > **⚠️ NOTE (C03 Resolution):** Yabeda is the **default metrics backend** for E11y. OpenTelemetry metrics are **optional** (see ADR-007). Choose ONE backend to avoid double overhead. See [CONFLICT-ANALYSIS.md C03](../researches/CONFLICT-ANALYSIS.md#c03-dual-metrics-collection-overhead) for details.
237
222
 
238
223
  **Primary Goals:**
239
- - ✅ Auto-create metrics from events (pattern-based)
240
- - ✅ Prevent cardinality explosions (4-layer defense)
224
+ - ✅ Auto-create metrics from events (event-level DSL)
225
+ - ✅ Prevent cardinality explosions (3-layer defense)
241
226
  - ✅ Zero manual metric definitions
242
227
  - ✅ Prometheus-friendly
243
228
  - ✅ Cost-effective (<10k time series per metric)
@@ -308,7 +293,7 @@ graph TB
308
293
  subgraph "Protection (3-Layer)"
309
294
  L1[Layer 1: Denylist]
310
295
  L2[Layer 2: Per-Metric Limits]
311
- L3[Layer 3: Monitoring]
296
+ L3[Layer 3: Dynamic Actions]
312
297
  end
313
298
 
314
299
  EventClass -->|defines| MetricsDSL
@@ -355,8 +340,7 @@ sequenceDiagram
355
340
  loop For each metric
356
341
  Middleware->>Protection: extract_labels(event_data)
357
342
  Protection->>Protection: Check denylist (order_id? ❌)
358
- Protection->>Protection: Check allowlist (status? ✅)
359
- Protection->>Protection: Check cardinality (3 unique values ✅)
343
+ Protection->>Protection: Check cardinality (status: 3 unique values ✅)
360
344
  Protection-->>Middleware: safe_labels: {status: 'paid'}
361
345
 
362
346
  Middleware->>Yabeda: counter.increment(safe_labels)
@@ -370,7 +354,7 @@ sequenceDiagram
370
354
 
371
355
  ---
372
356
 
373
- ## 3. Pattern-Based Metrics
357
+ ## 3. Event Metrics
374
358
 
375
359
  ### 3.1. Pattern Matching
376
360
 
@@ -449,30 +433,13 @@ graph TB
449
433
 
450
434
  **Configuration:**
451
435
 
436
+
452
437
  ```ruby
453
- E11y.configure do |config|
454
- config.metrics do
455
- # Counter: count events
456
- counter pattern: 'order.*',
457
- name: 'orders_total',
458
- comment: 'Total orders by status',
459
- tags: [:status], # ← Extract from payload
460
- unit: :count
461
-
462
- # Histogram: measure values
463
- histogram pattern: 'order.paid',
464
- name: 'order_amount',
465
- comment: 'Order payment amounts',
466
- tags: [:payment_method],
467
- unit: :dollars,
468
- buckets: [10, 50, 100, 500, 1000, 5000]
469
-
470
- # Gauge: current value
471
- gauge pattern: 'buffer.*',
472
- name: 'buffer_size',
473
- comment: 'Current buffer size',
474
- tags: [:buffer_type],
475
- unit: :events
438
+ # Event-level metrics (implemented)
439
+ class Events::OrderPaid < E11y::Event::Base
440
+ metrics do
441
+ counter :orders_total, tags: [:status]
442
+ histogram :order_amount, value: :amount, tags: [:payment_method], buckets: [10, 50, 100, 500]
476
443
  end
477
444
  end
478
445
  ```
@@ -540,7 +507,7 @@ Layers are applied **sequentially** (not simultaneously):
540
507
 
541
508
  1. **Layer 1 (Universal Denylist):** If label in denylist → DROP, stop processing
542
509
  2. **Layer 2 (Per-Metric Limits):** Track unique values per label, drop if exceeded
543
- 3. **Layer 3 (Monitoring):** Log warnings when limits exceeded
510
+ 3. **Layer 3 (Dynamic Actions):** Configurable action on overflow (drop, alert, relabel)
544
511
 
545
512
  **Example Flow:**
546
513
 
@@ -550,13 +517,12 @@ Label: user_id
550
517
 
551
518
  Label: status
552
519
  → Layer 1: in FORBIDDEN_LABELS? ❌ No → continue
553
- → Layer 2: in SAFE_LABELS? ✅ Yes → KEEP ✅ (skip Layer 3-4)
520
+ → Layer 2: cardinality < limit? ✅ Yes (3 values) → KEEP ✅
554
521
 
555
522
  Label: custom_field
556
523
  → Layer 1: in FORBIDDEN_LABELS? ❌ No → continue
557
- → Layer 2: in SAFE_LABELS? ❌ No → continue
558
- → Layer 3: cardinality < limit? No (150 > 100) → continue
559
- → Layer 4: action=drop → DROP ❌
524
+ → Layer 2: cardinality < limit? ❌ No (150 > 100) → continue
525
+ → Layer 3: action=drop DROP
560
526
  ```
561
527
 
562
528
  ```mermaid
@@ -564,16 +530,13 @@ graph TB
564
530
  Input[Event Labels] --> L1{Layer 1<br/>Denylist}
565
531
 
566
532
  L1 -->|In denylist| Drop1[❌ Drop Label]
567
- L1 -->|Not in denylist| L2{Layer 2<br/>Allowlist}
568
-
569
- L2 -->|In allowlist| Keep[✅ Keep Label]
570
- L2 -->|Not in allowlist| L3{Layer 3<br/>Cardinality Limit}
533
+ L1 -->|Not in denylist| L2{Layer 2<br/>Per-Metric Limit}
571
534
 
572
- L3 -->|Under limit| Keep
573
- L3 -->|Over limit| L4{Layer 4<br/>Dynamic Action}
535
+ L2 -->|Under limit| Keep[✅ Keep Label]
536
+ L2 -->|Over limit| L3{Layer 3<br/>Dynamic Action}
574
537
 
575
- L4 -->|drop| Drop2[❌ Drop Label]
576
- L4 -->|alert| Alert[🚨 Alert + Drop]
538
+ L3 -->|drop| Drop2[❌ Drop Label]
539
+ L3 -->|alert| Alert[🚨 Alert + Drop]
577
540
 
578
541
  Drop1 --> Log1[Log: denylist_hit]
579
542
  Drop2 --> Log2[Log: cardinality_exceeded]
@@ -582,9 +545,8 @@ graph TB
582
545
  Keep --> Metric[Export to Yabeda]
583
546
 
584
547
  style L1 fill:#f8d7da
585
- style L2 fill:#d4edda
586
- style L3 fill:#fff3cd
587
- style L4 fill:#d1ecf1
548
+ style L2 fill:#fff3cd
549
+ style L3 fill:#d1ecf1
588
550
  style Drop1 fill:#f8d7da
589
551
  style Drop2 fill:#f8d7da
590
552
  style Keep fill:#d4edda
@@ -673,53 +635,7 @@ graph LR
673
635
  style File fill:#d4edda
674
636
  ```
675
637
 
676
- ### 4.3. Layer 2: Safe Allowlist
677
-
678
- **Decision:** Pre-approved low-cardinality labels.
679
-
680
- ```ruby
681
- module E11y
682
- module Metrics
683
- class CardinalityProtection
684
- # Safe labels (low cardinality, always allowed)
685
- SAFE_LABELS = [
686
- # Status/state
687
- :status, :state, :result, :outcome,
688
-
689
- # Types/categories
690
- :type, :kind, :category, :class_name,
691
-
692
- # Methods/operations
693
- :method, :action, :operation, :command,
694
-
695
- # Environments
696
- :env, :environment, :region, :zone, :datacenter,
697
-
698
- # Services
699
- :service, :component, :adapter, :backend,
700
-
701
- # Severities
702
- :severity, :level, :priority,
703
-
704
- # HTTP
705
- :http_method, :http_status, :http_status_class, # (200 → '2xx')
706
-
707
- # Protocols
708
- :protocol, :version,
709
-
710
- # Success/failure
711
- :success, :error_type, :error_class
712
- ].freeze
713
-
714
- def in_allowlist?(label_name)
715
- SAFE_LABELS.include?(label_name.to_sym)
716
- end
717
- end
718
- end
719
- end
720
- ```
721
-
722
- ### 4.4. Layer 3: Per-Metric Cardinality Limits
638
+ ### 4.3. Layer 2: Per-Metric Cardinality Limits
723
639
 
724
640
  **Decision:** Track unique values per label, enforce limits.
725
641
 
@@ -783,7 +699,7 @@ tracker.check_and_track('orders_total', :status, 'cancelled') # ❌ false (limi
783
699
  tracker.check_and_track('orders_total', :status, 'paid') # ✅ true (seen before)
784
700
  ```
785
701
 
786
- ### 4.5. Layer 4: Dynamic Actions
702
+ ### 4.4. Layer 3: Dynamic Actions
787
703
 
788
704
  **Decision:** Configurable actions when limits exceeded.
789
705
 
@@ -822,8 +738,8 @@ Events::ApiCall.track(
822
738
  customer_id: 'cust_12345' # ← 101st unique value, exceeds limit
823
739
  )
824
740
 
825
- # Layer 3: cardinality exceeded (100 limit)
826
- # Layer 4: action=drop → customer_id dropped
741
+ # Layer 2: cardinality exceeded (100 limit)
742
+ # Layer 3: action=drop → customer_id dropped
827
743
 
828
744
  # Result metric:
829
745
  # api_calls_total{endpoint="/api/users"} 1
@@ -863,7 +779,7 @@ graph TB
863
779
 
864
780
  **Note:** For v1.0, we keep it simple with just **drop** and **alert**. Advanced strategies (hash bucketing, aggregation) can be added in v1.1+ if needed.
865
781
 
866
- ### 4.6. Relabeling Rules
782
+ ### 4.5. Relabeling Rules
867
783
 
868
784
  **Decision:** Transform high-cardinality labels to low-cardinality.
869
785
 
@@ -937,21 +853,16 @@ end
937
853
 
938
854
  ### 5.2. Metric Registration
939
855
 
856
+
940
857
  ```ruby
941
- # Auto-create Yabeda metrics from config
942
- E11y.configure do |config|
943
- config.metrics do
944
- counter pattern: 'order.*',
945
- name: :orders_total,
946
- comment: 'Total orders',
947
- tags: [:status]
858
+ # Event-level metrics (implemented)
859
+ class Events::OrderCreated < E11y::Event::Base
860
+ metrics do
861
+ counter :orders_total, tags: [:status]
948
862
  end
949
863
  end
950
864
 
951
- # Internally creates:
952
- Yabeda.e11y.counter :orders_total,
953
- tags: [:status],
954
- comment: 'Total orders'
865
+ # Yabeda adapter auto-registers from Registry when events are loaded
955
866
  ```
956
867
 
957
868
  ### 5.3. Metric Updates
@@ -1052,97 +963,17 @@ end
1052
963
  ```ruby
1053
964
  # config/initializers/e11y.rb
1054
965
  E11y.configure do |config|
1055
- config.metrics do
1056
- # Enable metrics
1057
- enabled true
1058
-
1059
- # Yabeda integration
1060
- yabeda_integration true
1061
-
1062
- # ===== Pattern-Based Metrics =====
1063
-
1064
- # Counter: count all orders
1065
- counter pattern: 'order.*',
1066
- name: :orders_total,
1067
- comment: 'Total orders by status and payment method',
1068
- tags: [:status, :payment_method],
1069
- unit: :count
1070
-
1071
- # Histogram: order amounts
1072
- histogram pattern: 'order.paid',
1073
- name: :order_amount,
1074
- comment: 'Order payment amounts',
1075
- tags: [:payment_method, :currency],
1076
- value_field: :amount, # Extract from payload
1077
- unit: :dollars,
1078
- buckets: [10, 50, 100, 500, 1000, 5000, 10000]
1079
-
1080
- # Histogram: API response times
1081
- histogram pattern: 'api.*',
1082
- name: :api_duration_seconds,
1083
- comment: 'API call durations',
1084
- tags: [:endpoint, :http_status_class],
1085
- value_field: :duration,
1086
- unit: :seconds,
1087
- buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5]
1088
-
1089
- # ===== Cardinality Protection =====
1090
-
1091
- cardinality_protection do
1092
- # Default limit per label
1093
- default_cardinality_limit 100
1094
-
1095
- # Per-metric limits
1096
- per_metric do
1097
- metric :orders_total, label: :status, limit: 10
1098
- metric :orders_total, label: :payment_method, limit: 20
1099
- metric :api_duration_seconds, label: :endpoint, limit: 500
1100
- end
1101
-
1102
- # Action on excess
1103
- action_on_excess :drop # :drop or :alert
1104
-
1105
- # Denylist (in addition to universal)
1106
- forbidden_labels [
1107
- :customer_id,
1108
- :internal_ref
1109
- ]
1110
-
1111
- # Allowlist (in addition to safe list)
1112
- allowed_labels [
1113
- :subscription_tier,
1114
- :user_role
1115
- ]
1116
-
1117
- # Relabeling rules
1118
- relabel :http_status do |value|
1119
- "#{value.to_i / 100}xx"
1120
- end
1121
-
1122
- relabel :path do |value|
1123
- value.gsub(/\/\d+/, '/:id')
1124
- end
1125
-
1126
- # Monitoring
1127
- monitoring do
1128
- alert_on_new_label true
1129
- alert_threshold 80 # Alert at 80% of limit
1130
-
1131
- on_alert do |metric_name, label_name, cardinality, limit|
1132
- Rails.logger.warn "Cardinality alert: #{metric_name}.#{label_name} = #{cardinality}/#{limit}"
1133
- end
1134
- end
1135
- end
1136
-
1137
- # ===== Advanced Features =====
1138
-
1139
- # Exemplars (sample trace IDs for metric values)
1140
- exemplars do
1141
- enabled true
1142
- max_per_bucket 1 # 1 trace_id per histogram bucket
1143
- end
1144
- end
966
+ config.adapters[:metrics] = E11y::Adapters::Yabeda.new
1145
967
  end
968
+
969
+ # Define metrics in event classes:
970
+ # class Events::OrderPaid < E11y::Event::Base
971
+ # metrics do
972
+ # counter :orders_total, tags: [:status, :payment_method]
973
+ # histogram :order_amount, value: :amount, tags: [:payment_method, :currency],
974
+ # buckets: [10, 50, 100, 500, 1000, 5000]
975
+ # end
976
+ # end
1146
977
  ```
1147
978
 
1148
979
  ---
@@ -1238,7 +1069,7 @@ RSpec.describe E11y::Metrics::CardinalityProtection do
1238
1069
  end
1239
1070
  end
1240
1071
 
1241
- describe 'Layer 3: Cardinality Limits' do
1072
+ describe 'Layer 2: Cardinality Limits' do
1242
1073
  it 'enforces per-metric limits' do
1243
1074
  protection = CardinalityProtection.new(
1244
1075
  limits: { orders_total: { status: 3 } }
@@ -1269,7 +1100,7 @@ end
1269
1100
  |----------|-----|-----|-----------|
1270
1101
  | **Auto-metrics** | Zero boilerplate | Less control | DX > control |
1271
1102
  | **Pattern matching** | Flexible | Slower than exact | Flexibility matters |
1272
- | **4-layer defense** | Robust | Complex | Safety critical |
1103
+ | **3-layer defense** | Robust | Complex | Safety critical |
1273
1104
  | **Hash bucketing** | Preserves some signal | Loss of precision | Better than drop |
1274
1105
  | **Yabeda dependency** | Battle-tested | External dep | Standard in Ruby |
1275
1106
 
@@ -1310,7 +1141,7 @@ Loki/Sentry/File:
1310
1141
 
1311
1142
  ---
1312
1143
 
1313
- ### Q2: Are Layers 1-4 applied simultaneously or sequentially?
1144
+ ### Q2: Are Layers 1–3 applied simultaneously or sequentially?
1314
1145
 
1315
1146
  **A: Sequentially (waterfall), not simultaneously.**
1316
1147
 
@@ -1321,16 +1152,12 @@ Processing order:
1321
1152
  ↓ If in denylist → DROP, stop
1322
1153
  ↓ If not in denylist → continue to Layer 2
1323
1154
 
1324
- 2. Layer 2 (Allowlist)
1325
- ↓ If in allowlist → KEEP, skip Layer 3-4
1326
- ↓ If not in allowlist → continue to Layer 3
1327
-
1328
- 3. Layer 3 (Cardinality Limit)
1155
+ 2. Layer 2 (Per-Metric Limit)
1329
1156
  ↓ If under limit → KEEP, stop
1330
- ↓ If over limit → continue to Layer 4
1157
+ ↓ If over limit → continue to Layer 3
1331
1158
 
1332
- 4. Layer 4 (Dynamic Action)
1333
- ↓ Apply configured action: hash/drop/aggregate/alert
1159
+ 3. Layer 3 (Dynamic Action)
1160
+ ↓ Apply configured action: drop/alert/relabel
1334
1161
  ```
1335
1162
 
1336
1163
  **Example:**
@@ -1343,15 +1170,14 @@ Processing order:
1343
1170
 
1344
1171
  # status:
1345
1172
  # Layer 1: not in FORBIDDEN_LABELS → continue
1346
- # Layer 2: in SAFE_LABELS → ✅ KEEP (skip Layer 3-4)
1173
+ # Layer 2: cardinality under limit → ✅ KEEP
1347
1174
 
1348
1175
  # tier:
1349
1176
  # Layer 1: not in FORBIDDEN_LABELS → continue
1350
- # Layer 2: not in SAFE_LABELS → continue
1351
- # Layer 3: cardinality = 150 > limit (100) → continue
1352
- # Layer 4: action=hash → ✅ KEEP as "bucket_7"
1177
+ # Layer 2: cardinality = 150 > limit (100) → continue
1178
+ # Layer 3: action=drop DROP
1353
1179
 
1354
- # Result: { status: 'paid', tier_bucket: 'bucket_7' }
1180
+ # Result: { status: 'paid' }
1355
1181
  ```
1356
1182
 
1357
1183
  ---