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,272 @@
1
+ # frozen_string_literal: true
2
+
3
+ module E11y
4
+ # Configuration class for E11y.
5
+ #
6
+ # Adapters are referenced by name (e.g., :logs, :errors_tracker).
7
+ # The actual implementation (Loki, Sentry, etc.) is configured separately.
8
+ #
9
+ # @example Configure adapters
10
+ # E11y.configure do |config|
11
+ # config.adapters[:logs] = E11y::Adapters::Loki.new(url: "...")
12
+ # config.adapters[:errors_tracker] = E11y::Adapters::Sentry.new(dsn: "...")
13
+ # end
14
+ #
15
+ # @example Configure severity => adapter mapping
16
+ # E11y.configure do |config|
17
+ # config.adapter_mapping[:error] = [:logs, :errors_tracker]
18
+ # config.adapter_mapping[:info] = [:logs]
19
+ # end
20
+ #
21
+ # @example Configure middleware pipeline
22
+ # E11y.configure do |config|
23
+ # config.pipeline.use E11y::Middleware::Sampling, default_sample_rate: 0.1
24
+ # end
25
+ class Configuration
26
+ attr_accessor :adapters, :log_level, :logger, :enabled, :environment, :service_name, :default_retention_period,
27
+ :routing_rules, :fallback_adapters, :enable_http_tracing,
28
+ :rails_instrumentation_enabled, :rails_instrumentation_custom_mappings, :rails_instrumentation_ignore_events,
29
+ :logger_bridge_enabled, :logger_bridge_track_severities, :logger_bridge_ignore_patterns,
30
+ :sidekiq_enabled, :active_job_enabled,
31
+ :ephemeral_buffer_enabled, :ephemeral_buffer_flush_on_error, :ephemeral_buffer_flush_on_statuses,
32
+ :ephemeral_buffer_debug_adapters, :ephemeral_buffer_job_buffer_limit,
33
+ :error_handling_fail_on_error,
34
+ :rate_limiting_enabled, :rate_limiting_global_limit, :rate_limiting_global_window,
35
+ :rate_limiting_per_event_limit, :rate_limiting_per_event_limits,
36
+ :slo_tracking_enabled, :slo_tracking_http_ignore_statuses, :slo_tracking_latency_percentiles,
37
+ :slo_tracking_controller_configs, :slo_tracking_job_configs,
38
+ :security_baggage_protection_enabled, :security_baggage_protection_allowed_keys, :security_baggage_protection_block_mode,
39
+ :tracing_source, :tracing_default_sample_rate, :tracing_respect_parent_sampling,
40
+ :tracing_per_event_sample_rates, :tracing_always_sample_if,
41
+ :opentelemetry_span_creation_patterns,
42
+ :cardinality_protection_max_cardinality_limit, :cardinality_protection_denylist, :cardinality_protection_overflow_strategy,
43
+ :dlq_storage, :dlq_filter
44
+ attr_reader :adapter_mapping, :pipeline
45
+
46
+ def initialize
47
+ initialize_basic_config
48
+ initialize_routing_config
49
+ initialize_feature_configs
50
+ configure_default_pipeline
51
+ end
52
+
53
+ # Get adapters for given severity
54
+ #
55
+ # @param severity [Symbol] Severity level
56
+ # @return [Array<Symbol>] Adapter names (e.g., [:logs, :errors_tracker])
57
+ def adapters_for_severity(severity)
58
+ @adapter_mapping[severity] || @adapter_mapping[:default] || []
59
+ end
60
+
61
+ # Get built pipeline (cached after first call)
62
+ #
63
+ # @return [#call] Built middleware pipeline
64
+ def built_pipeline
65
+ @built_pipeline ||= @pipeline.build(->(_event_data) {})
66
+ end
67
+
68
+ # Add per-event rate limit rule.
69
+ #
70
+ # @param pattern [String] Event name or glob pattern (e.g. "payment.*")
71
+ # @param limit [Integer] Max events per window for this pattern
72
+ # @param window [Numeric] Window size in seconds
73
+ def add_rate_limit_per_event(pattern, limit:, window: 1.0)
74
+ (@rate_limiting_per_event_limits ||= []) << {
75
+ pattern: pattern.to_s,
76
+ limit: limit,
77
+ window: window.to_f
78
+ }
79
+ end
80
+
81
+ # Find the most specific rate limit config for a given event name.
82
+ #
83
+ # @param event_name [String] Event name to look up
84
+ # @return [Hash] { limit:, window: }
85
+ def rate_limit_for(event_name)
86
+ limits = @rate_limiting_per_event_limits || []
87
+ match = limits.find do |rule|
88
+ pattern = rule[:pattern].gsub(".", "\\.").gsub("*", ".*")
89
+ Regexp.new("^#{pattern}$").match?(event_name.to_s)
90
+ end
91
+ m = match || { limit: @rate_limiting_per_event_limit, window: @rate_limiting_global_window }
92
+ { limit: m[:limit], window: m[:window] }
93
+ end
94
+
95
+ # Add per-controller SLO config.
96
+ def add_slo_controller(name, action: nil, &)
97
+ key = action ? "#{name}##{action}" : name
98
+ cfg = ControllerSLOConfig.new
99
+ cfg.instance_eval(&) if block_given?
100
+ (@slo_tracking_controller_configs ||= {})[key] = cfg
101
+ end
102
+
103
+ # Add per-job SLO config.
104
+ def add_slo_job(name, &)
105
+ cfg = JobSLOConfig.new
106
+ cfg.instance_eval(&) if block_given?
107
+ (@slo_tracking_job_configs ||= {})[name] = cfg
108
+ end
109
+
110
+ # Set slo_tracking enabled — accepts Boolean.
111
+ def slo_tracking=(value)
112
+ @slo_tracking_enabled = value if value.is_a?(TrueClass) || value.is_a?(FalseClass)
113
+ end
114
+
115
+ # Filter baggage hash to only allowed keys (for tracestate/job propagation).
116
+ def filter_baggage_for_propagation(hash)
117
+ return {} if hash.nil? || !hash.is_a?(Hash)
118
+ return hash unless @security_baggage_protection_enabled
119
+
120
+ allowed = (@security_baggage_protection_allowed_keys || E11y::BAGGAGE_PROTECTION_DEFAULT_ALLOWED_KEYS).map(&:to_s)
121
+ hash.select { |k, _| allowed.include?(k.to_s) }
122
+ end
123
+
124
+ # Register an adapter instance by name.
125
+ def register_adapter(name, instance)
126
+ @adapters[name.to_sym] = instance
127
+ end
128
+
129
+ # Set the default adapter(s) used when no severity-specific mapping matches.
130
+ def default_adapters=(names)
131
+ @adapter_mapping[:default] = Array(names).map(&:to_sym)
132
+ end
133
+
134
+ # @return [Array<Symbol>] Default adapter names
135
+ def default_adapters
136
+ @adapter_mapping[:default]
137
+ end
138
+
139
+ private
140
+
141
+ def initialize_basic_config
142
+ @adapters = {}
143
+ @log_level = :info
144
+ @pipeline = E11y::Pipeline::Builder.new
145
+ @enabled = nil
146
+ @environment = nil
147
+ @service_name = nil
148
+ @enable_http_tracing = false
149
+ end
150
+
151
+ def initialize_routing_config
152
+ @adapter_mapping = default_adapter_mapping
153
+ @default_retention_period = 30.days
154
+ @routing_rules = []
155
+ @fallback_adapters = [:stdout]
156
+ end
157
+
158
+ def initialize_feature_configs
159
+ init_instrumentation_configs
160
+ init_ephemeral_buffer_configs
161
+ init_error_handling_configs
162
+ init_rate_limiting_configs
163
+ init_slo_configs
164
+ init_security_configs
165
+ init_tracing_configs
166
+ init_cardinality_configs
167
+ end
168
+
169
+ def init_instrumentation_configs
170
+ @rails_instrumentation_enabled = false
171
+ @rails_instrumentation_custom_mappings = {}
172
+ @rails_instrumentation_ignore_events = []
173
+ @logger_bridge_enabled = false
174
+ @logger_bridge_track_severities = nil
175
+ @logger_bridge_ignore_patterns = []
176
+ @sidekiq_enabled = false
177
+ @active_job_enabled = false
178
+ end
179
+
180
+ def init_ephemeral_buffer_configs
181
+ @ephemeral_buffer_enabled = false
182
+ @ephemeral_buffer_flush_on_error = true
183
+ @ephemeral_buffer_flush_on_statuses = []
184
+ @ephemeral_buffer_debug_adapters = nil
185
+ @ephemeral_buffer_job_buffer_limit = nil
186
+ end
187
+
188
+ def init_error_handling_configs
189
+ @error_handling_fail_on_error = true
190
+ @dlq_storage = nil
191
+ @dlq_filter = nil
192
+ end
193
+
194
+ def init_rate_limiting_configs
195
+ @rate_limiting_enabled = false
196
+ @rate_limiting_global_limit = 10_000
197
+ @rate_limiting_global_window = 1.0
198
+ @rate_limiting_per_event_limit = 1_000
199
+ @rate_limiting_per_event_limits = []
200
+ end
201
+
202
+ def init_slo_configs
203
+ @slo_tracking_enabled = true
204
+ @slo_tracking_http_ignore_statuses = []
205
+ @slo_tracking_latency_percentiles = [50, 95, 99]
206
+ @slo_tracking_controller_configs = {}
207
+ @slo_tracking_job_configs = {}
208
+ end
209
+
210
+ def init_security_configs
211
+ @security_baggage_protection_enabled = true
212
+ @security_baggage_protection_allowed_keys = E11y::BAGGAGE_PROTECTION_DEFAULT_ALLOWED_KEYS.dup
213
+ @security_baggage_protection_block_mode = :silent
214
+ end
215
+
216
+ def init_tracing_configs
217
+ @tracing_source = :e11y
218
+ @tracing_default_sample_rate = 0.1
219
+ @tracing_respect_parent_sampling = true
220
+ @tracing_per_event_sample_rates = {}
221
+ @tracing_always_sample_if = nil
222
+ @opentelemetry_span_creation_patterns = []
223
+ end
224
+
225
+ def init_cardinality_configs
226
+ @cardinality_protection_max_cardinality_limit = 1000
227
+ @cardinality_protection_denylist = []
228
+ @cardinality_protection_overflow_strategy = :relabel
229
+ end
230
+
231
+ def default_adapter_mapping
232
+ {
233
+ error: %i[logs errors_tracker],
234
+ fatal: %i[logs errors_tracker],
235
+ default: [:logs]
236
+ }
237
+ end
238
+
239
+ def configure_default_pipeline
240
+ @pipeline.use E11y::Middleware::TrackLatency
241
+ @pipeline.use E11y::Middleware::TraceContext
242
+ @pipeline.use E11y::Middleware::Validation
243
+ @pipeline.use E11y::Middleware::BaggageProtection
244
+ @pipeline.use E11y::Middleware::AuditSigning
245
+ @pipeline.use E11y::Middleware::PIIFilter
246
+ @pipeline.use E11y::Middleware::RateLimiting
247
+ @pipeline.use E11y::Middleware::Sampling
248
+ @pipeline.use E11y::Middleware::Versioning
249
+ @pipeline.use E11y::Middleware::Routing
250
+ @pipeline.use E11y::Middleware::EventSlo
251
+ @pipeline.use E11y::Middleware::SelfMonitoringEmit
252
+ end
253
+ end
254
+
255
+ # Per-controller SLO config (used by add_slo_controller).
256
+ class ControllerSLOConfig
257
+ def slo_target(value = nil)
258
+ value ? @slo_target = value : @slo_target
259
+ end
260
+
261
+ def latency_target(value = nil)
262
+ value ? @latency_target = value : @latency_target
263
+ end
264
+ end
265
+
266
+ # Per-job SLO config (used by add_slo_job).
267
+ class JobSLOConfig
268
+ def ignore(value = nil)
269
+ value.nil? ? @ignore : @ignore = value
270
+ end
271
+ end
272
+ end
data/lib/e11y/console.rb CHANGED
@@ -51,19 +51,17 @@ module E11y
51
51
 
52
52
  # List all registered event classes
53
53
  def events
54
- puts "📋 E11y events list"
55
- puts " (Waiting for Event registry implementation)"
56
- []
54
+ Registry.event_classes.map { |e| e.respond_to?(:event_name) ? e.event_name : e.name }
57
55
  end
58
56
 
59
- # List all registered adapters
57
+ # List all registered adapters (from config.adapters)
60
58
  def adapters
61
- Adapters::Registry.all.map do |adapter|
59
+ config.adapters.map do |name, adapter|
62
60
  {
63
- name: adapter.name,
61
+ name: name,
64
62
  class: adapter.class.name,
65
- healthy: adapter.healthy?,
66
- capabilities: adapter.capabilities
63
+ healthy: adapter.respond_to?(:healthy?) ? adapter.healthy? : true,
64
+ capabilities: adapter.respond_to?(:capabilities) ? adapter.capabilities : {}
67
65
  }
68
66
  end
69
67
  end
@@ -77,8 +75,8 @@ module E11y
77
75
  private
78
76
 
79
77
  def adapters_info
80
- Adapters::Registry.all.map do |a|
81
- { name: a.name, class: a.class.name, healthy: a.healthy? }
78
+ config.adapters.map do |name, adapter|
79
+ { name: name, class: adapter.class.name, healthy: adapter.respond_to?(:healthy?) ? adapter.healthy? : true }
82
80
  end
83
81
  end
84
82
 
@@ -95,13 +93,8 @@ module E11y
95
93
  # @return [void]
96
94
  def self.configure_for_console
97
95
  E11y.configure do |config|
98
- # Console-friendly output
99
- config.adapters&.clear
100
-
101
- # Use stdout adapter with pretty printing
102
- config.adapters&.register :stdout, E11y::Adapters::Stdout.new(
103
- colorize: true
104
- )
96
+ config.adapters.clear
97
+ config.adapters[:stdout] = E11y::Adapters::Stdout.new(colorize: true, format: :rich)
105
98
 
106
99
  # Show all severities
107
100
  # TODO: Implement severity_threshold config
data/lib/e11y/current.rb CHANGED
@@ -37,12 +37,64 @@ module E11y
37
37
  class Current < ActiveSupport::CurrentAttributes
38
38
  attribute :trace_id
39
39
  attribute :span_id
40
- attribute :parent_trace_id # ✅ NEW: Link to parent trace (C17 Resolution)
40
+ attribute :parent_trace_id # Link to parent trace (C17 Resolution)
41
+ attribute :sampled # Trace-consistent sampling (ADR-005 §7)
42
+ attribute :baggage # Key-value metadata for cross-service propagation (ADR-005 §3)
41
43
  attribute :request_id
42
44
  attribute :user_id
43
45
  attribute :ip_address
44
46
  attribute :user_agent
45
47
  attribute :request_method
46
48
  attribute :request_path
49
+
50
+ # Add baggage key-value (propagated via tracestate / job metadata).
51
+ # Respects config.security_baggage_protection_*: blocks PII keys per allowed_keys (ADR-006 §5.5).
52
+ #
53
+ # @param key [String, Symbol]
54
+ # @param value [Object] Converted to string
55
+ # @raise [E11y::BaggagePiiError] when block_mode is :raise and key not allowed
56
+ def self.add_baggage(key, value)
57
+ cfg = E11y.config
58
+ if cfg&.security_baggage_protection_enabled
59
+ allowed = (cfg.security_baggage_protection_allowed_keys || E11y::BAGGAGE_PROTECTION_DEFAULT_ALLOWED_KEYS).map(&:to_s)
60
+ unless allowed.include?(key.to_s)
61
+ message = "[E11y] Blocked PII from E11y baggage: key=#{key.inspect}"
62
+ case cfg.security_baggage_protection_block_mode || :silent
63
+ when :silent then E11y.logger&.debug(message)
64
+ when :warn then E11y.logger&.warn(message)
65
+ when :raise then raise E11y::BaggagePiiError, "#{message}. Only allowed keys: #{allowed.join(', ')}"
66
+ end
67
+ return
68
+ end
69
+ end
70
+ self.baggage = (baggage || {}).merge(key.to_s => value.to_s)
71
+ end
72
+
73
+ # Get baggage value by key.
74
+ # @param key [String, Symbol]
75
+ # @return [String, nil]
76
+ def self.get_baggage(key)
77
+ baggage&.[](key.to_s)
78
+ end
79
+
80
+ # Returns current attributes as a hash for sampling context (symbol keys, nil values omitted).
81
+ # Callers may merge job-specific keys (job_class, queue) when in job context.
82
+ #
83
+ # @return [Hash{Symbol=>Object}]
84
+ def self.to_context
85
+ {
86
+ trace_id: trace_id,
87
+ span_id: span_id,
88
+ parent_trace_id: parent_trace_id,
89
+ sampled: sampled,
90
+ baggage: baggage,
91
+ request_id: request_id,
92
+ user_id: user_id,
93
+ ip_address: ip_address,
94
+ user_agent: user_agent,
95
+ request_method: request_method,
96
+ request_path: request_path
97
+ }.compact
98
+ end
47
99
  end
48
100
  end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/numeric/time"
4
+
5
+ module E11y
6
+ module Debug
7
+ # Debug utility to trace events through the pipeline with per-middleware logging.
8
+ #
9
+ # Runs the full pipeline including adapter writes. For debugging, use Stdout or
10
+ # InMemory adapter. Stub adapters in tests if needed.
11
+ #
12
+ # @see E11y.trace
13
+ class PipelineInspector
14
+ # Wraps a middleware to log enter/exit for pipeline tracing.
15
+ class TracingWrapper
16
+ def initialize(middleware_class, next_app, name, args: [], options: {})
17
+ @middleware_class = middleware_class
18
+ @next_app = next_app
19
+ @name = name
20
+ @args = args
21
+ @options = options
22
+ end
23
+
24
+ def call(event_data)
25
+ log_enter(@name)
26
+ result = @middleware_class.new(@next_app, *@args, **@options).call(event_data)
27
+ log_exit(@name)
28
+ result
29
+ end
30
+
31
+ private
32
+
33
+ def log_enter(name)
34
+ prefix = $stdout.tty? ? "\e[33m" : ""
35
+ suffix = $stdout.tty? ? "\e[0m" : ""
36
+ print " #{prefix}#{name}#{suffix}... "
37
+ end
38
+
39
+ def log_exit(_name)
40
+ mark = $stdout.tty? ? "\e[32m✓\e[0m" : "✓"
41
+ puts mark
42
+ end
43
+ end
44
+
45
+ class << self
46
+ # Traces an event through the pipeline with per-middleware logging.
47
+ # Note: adapters WILL receive the event.
48
+ #
49
+ # @param event_class [Class] Event class
50
+ # @param payload [Hash] Event payload
51
+ # @return [Hash] event_data after pipeline
52
+ def trace_event(event_class, **payload)
53
+ event_name = event_class.respond_to?(:event_name) ? event_class.event_name : event_class.name
54
+ puts "\n🔍 Tracing Event Pipeline: #{event_name}\n\n"
55
+ event_data = build_event_data(event_class, payload)
56
+ pipeline = build_tracing_pipeline
57
+ result = pipeline.call(event_data)
58
+ puts "\n✅ Pipeline trace complete\n"
59
+ result
60
+ end
61
+
62
+ private
63
+
64
+ def build_event_data(event_class, payload)
65
+ {
66
+ event_class: event_class,
67
+ event_name: event_class.respond_to?(:event_name) ? event_class.event_name : event_class.name,
68
+ payload: payload,
69
+ severity: event_class.respond_to?(:severity) ? event_class.severity : :info,
70
+ version: event_class.respond_to?(:version) ? event_class.version : 1,
71
+ adapters: event_class.respond_to?(:adapters) ? event_class.adapters : nil,
72
+ timestamp: Time.now.utc,
73
+ retention_period: event_class.respond_to?(:retention_period) ? event_class.retention_period : 30.days,
74
+ context: {}
75
+ }
76
+ end
77
+
78
+ def build_tracing_pipeline
79
+ builder = E11y.configuration.pipeline
80
+ final_app = ->(event_data) { event_data }
81
+
82
+ builder.middlewares.reverse.reduce(final_app) do |next_app, entry|
83
+ name = entry.middleware_class.name.split("::").last
84
+ TracingWrapper.new(
85
+ entry.middleware_class,
86
+ next_app,
87
+ name,
88
+ args: entry.args,
89
+ options: entry.options
90
+ )
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module E11y
4
+ module Documentation
5
+ # Generates Markdown documentation for registered E11y events.
6
+ class Generator
7
+ def self.generate(output_dir, criteria: {}, grep: nil)
8
+ classes = criteria.any? ? E11y::Registry.where(**criteria) : E11y::Registry.event_classes
9
+ classes = classes.select { |c| (c.respond_to?(:event_name) ? c.event_name : c.name).to_s.include?(grep) } if grep
10
+
11
+ FileUtils.mkdir_p(output_dir)
12
+ write_index(output_dir, classes)
13
+ classes.each { |klass| write_event_doc(output_dir, klass) }
14
+ end
15
+
16
+ def self.write_index(output_dir, classes)
17
+ lines = ["# E11y Events", "", "| Event | Class | Severity |", "|-------|-------|----------|"]
18
+ classes.each do |klass|
19
+ name = klass.respond_to?(:event_name) ? klass.event_name : klass.name
20
+ sev = klass.respond_to?(:severity) ? klass.severity : "—"
21
+ lines << "| #{name} | #{klass.name} | #{sev} |"
22
+ end
23
+ File.write(File.join(output_dir, "README.md"), "#{lines.join("\n")}\n")
24
+ end
25
+
26
+ def self.write_event_doc(output_dir, klass)
27
+ name = klass.respond_to?(:event_name) ? klass.event_name : klass.name
28
+ schema_keys = extract_schema_keys(klass)
29
+ sev = klass.respond_to?(:severity) ? klass.severity : "—"
30
+ lines = ["# #{name}", "", "- **Class:** #{klass.name}", "- **Severity:** #{sev}"]
31
+ lines << "- **Schema keys:** #{schema_keys.join(', ')}" if schema_keys&.any?
32
+ lines << ""
33
+ File.write(File.join(output_dir, "#{name.to_s.tr('.', '_')}.md"), "#{lines.join("\n")}\n")
34
+ end
35
+
36
+ def self.extract_schema_keys(klass)
37
+ return nil unless klass.respond_to?(:compiled_schema)
38
+
39
+ schema = klass.compiled_schema
40
+ return nil if schema.nil? || !schema.respond_to?(:key_map)
41
+
42
+ schema.key_map.keys.map(&:name)
43
+ rescue StandardError
44
+ nil
45
+ end
46
+ end
47
+ end
48
+ end