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
@@ -184,164 +184,17 @@ sequenceDiagram
184
184
 
185
185
  ## 3. Railtie & Initialization
186
186
 
187
- ### 3.1. Railtie Implementation
187
+ ### 3.1. Railtie implementation
188
188
 
189
- ```ruby
190
- # lib/e11y/railtie.rb
191
- module E11y
192
- class Railtie < Rails::Railtie
193
- # Run before framework initialization
194
- config.before_initialize do
195
- # Set up basic configuration
196
- E11y.configure do |config|
197
- config.environment = Rails.env
198
- config.service_name = Rails.application.class.module_parent_name.underscore
199
- end
200
- end
201
-
202
- # Run after framework initialization
203
- config.after_initialize do
204
- next unless E11y.config.enabled
205
-
206
- # Setup instruments (each can be enabled/disabled separately)
207
- if E11y.config.instruments.active_support_notifications.enabled
208
- E11y::Instruments::ActiveSupportNotifications.setup!
209
- end
210
-
211
- if E11y.config.instruments.sidekiq.enabled
212
- E11y::Instruments::Sidekiq.setup!
213
- end
214
-
215
- if E11y.config.instruments.active_job.enabled
216
- E11y::Instruments::ActiveJob.setup!
217
- end
218
-
219
- if E11y.config.instruments.rack_middleware.enabled
220
- E11y::Instruments::RackMiddleware.setup!
221
- end
222
-
223
- # Setup logger bridge
224
- E11y::Logger::Bridge.setup! if E11y.config.logger_bridge.enabled
225
-
226
- # Setup development tools
227
- E11y::Console.setup! if Rails.env.development?
228
- end
229
-
230
- # Middleware insertion
231
- initializer 'e11y.middleware' do |app|
232
- app.middleware.insert_before(
233
- Rails::Rack::Logger,
234
- E11y::Middleware::Request
235
- )
236
- end
237
-
238
- # ActiveSupport::Notifications subscribers
239
- initializer 'e11y.notifications' do
240
- ActiveSupport::Notifications.subscribe(/.*/) do |name, start, finish, id, payload|
241
- E11y::Instruments::NotificationSubscriber.handle(
242
- name: name,
243
- started_at: start,
244
- finished_at: finish,
245
- transaction_id: id,
246
- payload: payload
247
- )
248
- end
249
- end
250
-
251
- # Sidekiq integration
252
- initializer 'e11y.sidekiq' do
253
- if defined?(Sidekiq)
254
- require 'e11y/instruments/sidekiq'
255
-
256
- Sidekiq.configure_server do |config|
257
- config.server_middleware do |chain|
258
- chain.add E11y::Instruments::Sidekiq::ServerMiddleware
259
- end
260
- end
261
-
262
- Sidekiq.configure_client do |config|
263
- config.client_middleware do |chain|
264
- chain.add E11y::Instruments::Sidekiq::ClientMiddleware
265
- end
266
- end
267
- end
268
- end
269
-
270
- # ActiveJob integration
271
- initializer 'e11y.active_job' do
272
- ActiveSupport.on_load(:active_job) do
273
- require 'e11y/instruments/active_job'
274
-
275
- include E11y::Instruments::ActiveJob::Callbacks
276
- end
277
- end
278
-
279
- # Console helpers
280
- console do
281
- E11y::Console.enable!
282
-
283
- puts "E11y loaded. Try: E11y.stats"
284
- end
285
-
286
- # Rake task helpers
287
- rake_tasks do
288
- load 'e11y/tasks.rake'
289
- end
290
- end
291
- end
292
- ```
189
+ **Source of truth:** `lib/e11y/railtie.rb` (read the file in the repo; do not paste stale excerpts).
293
190
 
294
- ### 3.2. Configuration Loading
191
+ Shipped behavior in short: `before_initialize` sets `environment`, `service_name`, and default `enabled`; after `load_config_initializers`, `e11y.setup_instrumentation` wires Rails instrumentation, optional logger bridge, and optional Sidekiq / Active Job when the corresponding **boolean** flags are set; `e11y.middleware` inserts `E11y::Middleware::Request`; development/test may register the DevLog adapter and `DevLogSource` middleware; optional `e11y.http_tracing` patches Net::HTTP; console loads `E11y::Console`; Rake tasks load the `lib/e11y/tasks/*.rake` files.
295
192
 
296
- ```ruby
297
- # lib/e11y/configuration/rails.rb
298
- module E11y
299
- module Configuration
300
- class Rails < Base
301
- def initialize
302
- super
303
-
304
- # Rails-specific defaults
305
- @environment = ::Rails.env
306
- @service_name = derive_service_name
307
- @enabled = !::Rails.env.test? # Disabled in tests by default
308
-
309
- # Auto-detect adapters
310
- @adapters = auto_detect_adapters
311
-
312
- # Rails logger bridge
313
- @logger_bridge = LoggerBridgeConfig.new
314
- end
315
-
316
- private
317
-
318
- def derive_service_name
319
- ::Rails.application.class.module_parent_name.underscore
320
- rescue
321
- 'rails_app'
322
- end
323
-
324
- def auto_detect_adapters
325
- adapters = []
326
-
327
- # Always include stdout in development
328
- adapters << :stdout if ::Rails.env.development?
329
-
330
- # Auto-detect file logging
331
- adapters << :file if ::Rails.root.join('log').directory?
332
-
333
- # Auto-detect Sentry
334
- adapters << :sentry if defined?(Sentry)
335
-
336
- # Auto-detect Loki
337
- adapters << :loki if ENV['LOKI_URL'].present?
338
-
339
- adapters
340
- end
341
- end
342
- end
343
- end
344
- ```
193
+ Older drafts of this ADR showed extra initializers (e.g. a global `ActiveSupport::Notifications.subscribe(/.*/)`, unconditional Sidekiq registration, `lib/e11y/configuration/rails.rb`, `LoggerBridgeConfig` as a nested type). **Those are not in the codebase.**
194
+
195
+ ### 3.2. Configuration
196
+
197
+ All tunables live on **`E11y::Configuration`** in `lib/e11y/configuration.rb`. There is no separate `E11y::Configuration::Rails` class.
345
198
 
346
199
  ---
347
200
 
@@ -400,7 +253,7 @@ module E11y
400
253
  # ========================================
401
254
 
402
255
  def self.setup!
403
- return unless E11y.config.instruments.rails_instrumentation.enabled
256
+ return unless E11y.config.rails_instrumentation_enabled
404
257
 
405
258
  # Subscribe to Rails events
406
259
  event_mapping.each do |asn_pattern, e11y_event_class|
@@ -421,19 +274,20 @@ module E11y
421
274
 
422
275
  # Built-in event mappings (can be overridden in config!)
423
276
  DEFAULT_RAILS_EVENT_MAPPING = {
424
- 'sql.active_record' => Events::Rails::Database::Query,
425
- 'process_action.action_controller' => Events::Rails::Http::Request,
426
- 'render_template.action_view' => Events::Rails::View::Render,
427
- 'send_file.action_controller' => Events::Rails::Http::SendFile,
428
- 'redirect_to.action_controller' => Events::Rails::Http::Redirect,
429
- 'start_processing.action_controller' => Events::Rails::Http::StartProcessing,
430
- 'cache_read.active_support' => Events::Rails::Cache::Read,
431
- 'cache_write.active_support' => Events::Rails::Cache::Write,
432
- 'cache_delete.active_support' => Events::Rails::Cache::Delete,
433
- 'enqueue.active_job' => Events::Rails::Job::Enqueued,
434
- 'enqueue_at.active_job' => Events::Rails::Job::Scheduled,
435
- 'perform_start.active_job' => Events::Rails::Job::Started,
436
- 'perform.active_job' => Events::Rails::Job::Completed
277
+ 'sql.active_record' => E11y::Events::Rails::Database::Query,
278
+ 'process_action.action_controller' => E11y::Events::Rails::Http::Request,
279
+ 'render_template.action_view' => E11y::Events::Rails::View::Render,
280
+ 'send_file.action_controller' => E11y::Events::Rails::Http::SendFile,
281
+ 'redirect_to.action_controller' => E11y::Events::Rails::Http::Redirect,
282
+ 'start_processing.action_controller' => E11y::Events::Rails::Http::StartProcessing,
283
+ 'cache_read.active_support' => E11y::Events::Rails::Cache::Read,
284
+ 'cache_write.active_support' => E11y::Events::Rails::Cache::Write,
285
+ 'cache_delete.active_support' => E11y::Events::Rails::Cache::Delete,
286
+ 'enqueue.active_job' => E11y::Events::Rails::Job::Enqueued,
287
+ 'enqueue_at.active_job' => E11y::Events::Rails::Job::Scheduled,
288
+ 'perform_start.active_job' => E11y::Events::Rails::Job::Started,
289
+ # perform.active_job: Completed on success; Failed when payload has exception
290
+ 'perform.active_job' => E11y::Events::Rails::Job::Completed
437
291
  }.freeze
438
292
 
439
293
  # Get final event mapping (after config overrides)
@@ -442,7 +296,7 @@ module E11y
442
296
  mapping = DEFAULT_RAILS_EVENT_MAPPING.dup
443
297
 
444
298
  # Apply custom mappings from config (Devise-style overrides)
445
- custom_mappings = E11y.config.instruments.rails_instrumentation.custom_mappings || {}
299
+ custom_mappings = E11y.config.rails_instrumentation_custom_mappings || {}
446
300
  mapping.merge!(custom_mappings)
447
301
 
448
302
  mapping
@@ -450,7 +304,7 @@ module E11y
450
304
  end
451
305
 
452
306
  def self.ignored?(pattern)
453
- ignore_list = E11y.config.instruments.rails_instrumentation.ignore_events || []
307
+ ignore_list = E11y.config.rails_instrumentation_ignore_events || []
454
308
  ignore_list.include?(pattern)
455
309
  end
456
310
 
@@ -464,81 +318,32 @@ module E11y
464
318
  end
465
319
  ```
466
320
 
467
- ### 4.2. Configuration: Overridable Event Classes (Devise-Style)
321
+ ### 4.2. Configuration (Rails integration)
468
322
 
469
- **Design Decision (Updated 2026-01-17):** Built-in event classes can be overridden in config (Devise-style pattern).
323
+ **As implemented:** flags and hashes on `E11y::Configuration` (`lib/e11y/configuration.rb`). There is **no** nested `config.rails_instrumentation do`, `config.sidekiq do`, or `config.active_job do` DSL—those snippets were design sketches and must not be copied into apps.
470
324
 
471
- **Rationale:**
472
- - ✅ **Flexibility**: Custom schema, PII rules, adapters per event type
473
- - **Familiar pattern**: Developers know Devise controller overrides
474
- - ✅ **Opt-in**: Defaults work for 90% of cases, override only when needed
475
- - ✅ **No monkey-patching**: Clean override mechanism
325
+ **Rails → E11y:** enable `rails_instrumentation_enabled`, optionally set `rails_instrumentation_custom_mappings` (String ASN pattern → event class) and `rails_instrumentation_ignore_events` (Array of patterns to skip). Instrumentation is wired in `E11y::Instruments::RailsInstrumentation` and invoked from `E11y::Railtie`.
326
+
327
+ **Jobs:** `sidekiq_enabled` and `active_job_enabled` are booleans. **Logger bridge:** `logger_bridge_enabled`, `logger_bridge_track_severities`, `logger_bridge_ignore_patterns`.
476
328
 
477
329
  ```ruby
478
330
  # config/initializers/e11y.rb
479
331
  E11y.configure do |config|
480
- config.instruments do
481
- # ========================================
482
- # Rails Instrumentation (ActiveSupport::Notifications → E11y)
483
- # ========================================
484
- rails_instrumentation do
485
- # Enable/disable entire ASN integration
486
- enabled true # Set to false to completely disable
487
-
488
- # Built-in event classes (auto-created by E11y)
489
- # Located in Events::Rails namespace
490
- use_built_in_events true # If false, no auto-mapping
491
-
492
- # ========================================
493
- # OVERRIDE EVENT CLASSES (Devise-style)
494
- # ========================================
495
-
496
- # Override default event class for specific ASN pattern
497
- event_class_for 'sql.active_record', MyApp::Events::CustomDatabaseQuery
498
- event_class_for 'process_action.action_controller', MyApp::Events::CustomHttpRequest
499
-
500
- # Disable specific events (too noisy or not needed)
501
- ignore_event 'cache_read.active_support'
502
- ignore_event 'render_partial.action_view'
503
- ignore_event 'SCHEMA' # Schema queries
504
-
505
- # ========================================
506
- # SELECTIVE INSTRUMENTATION
507
- # ========================================
508
-
509
- # Which Rails events to track (glob patterns)
510
- track_patterns [
511
- 'sql.active_record',
512
- 'process_action.action_controller',
513
- 'render_template.action_view',
514
- 'cache_*.active_support'
515
- ]
516
-
517
- # Sampling for high-volume events
518
- sample_patterns do
519
- pattern 'sql.active_record', sample_rate: 0.1 # 10% of SQL queries
520
- pattern 'cache_read.active_support', sample_rate: 0.01 # 1% of cache reads
521
- end
522
-
523
- # Enrich with custom data
524
- enrich do |asn_event|
525
- {
526
- controller: asn_event.payload[:controller],
527
- action: asn_event.payload[:action],
528
- format: asn_event.payload[:format],
529
- user_id: Current.user&.id # Add context
530
- }
531
- end
532
- end
533
-
534
- # (Other integrations: Sidekiq, ActiveJob, etc.)
535
- end
332
+ config.rails_instrumentation_enabled = true
333
+
334
+ config.rails_instrumentation_custom_mappings["sql.active_record"] =
335
+ MyApp::Events::CustomDatabaseQuery
336
+ config.rails_instrumentation_ignore_events << "cache_read.active_support"
337
+
338
+ config.sidekiq_enabled = true
339
+ config.active_job_enabled = true
340
+ config.ephemeral_buffer_enabled = true
536
341
  end
537
342
  ```
538
343
 
539
344
  ### 4.2.1. Custom Event Class Example
540
345
 
541
- **Use Case:** Override default `Events::Rails::Database::Query` with custom schema and PII rules.
346
+ **Use Case:** Override default `E11y::Events::Rails::Database::Query` with custom schema and PII rules.
542
347
 
543
348
  ```ruby
544
349
  # app/events/custom_database_query.rb
@@ -580,153 +385,11 @@ end
580
385
 
581
386
  # config/initializers/e11y.rb
582
387
  E11y.configure do |config|
583
- config.instruments.rails_instrumentation do
584
- # Override default event class
585
- event_class_for 'sql.active_record', MyApp::Events::CustomDatabaseQuery
586
- end
388
+ config.rails_instrumentation_custom_mappings["sql.active_record"] =
389
+ MyApp::Events::CustomDatabaseQuery
587
390
  end
588
391
 
589
- # Now all sql.active_record events will use CustomDatabaseQuery!
590
- ```
591
-
592
- ### 4.2.2. Implementation (Configuration DSL)
593
-
594
- ```ruby
595
- # lib/e11y/configuration/rails_instrumentation.rb
596
- module E11y
597
- module Configuration
598
- class RailsInstrumentation
599
- attr_accessor :enabled, :use_built_in_events
600
-
601
- def initialize
602
- @enabled = true
603
- @use_built_in_events = true
604
- @custom_event_classes = {}
605
- @ignored_events = []
606
- @track_patterns = []
607
- @sample_patterns = {}
608
- @enrich_block = nil
609
- end
610
-
611
- # Override event class (Devise-style)
612
- def event_class_for(asn_pattern, custom_event_class)
613
- unless custom_event_class < E11y::Event::Base
614
- raise ArgumentError, "#{custom_event_class} must inherit from E11y::Event::Base"
615
- end
616
-
617
- @custom_event_classes[asn_pattern] = custom_event_class
618
- end
619
-
620
- # Disable specific event
621
- def ignore_event(asn_pattern)
622
- @ignored_events << asn_pattern
623
- end
624
-
625
- # Track only specific patterns
626
- def track_patterns(*patterns)
627
- @track_patterns = patterns.flatten
628
- end
629
-
630
- # Sampling configuration
631
- def sample_patterns(&block)
632
- @sample_patterns_builder ||= SamplePatternsBuilder.new
633
- @sample_patterns_builder.instance_eval(&block) if block_given?
634
- @sample_patterns_builder
635
- end
636
-
637
- # Enrich ASN events before conversion
638
- def enrich(&block)
639
- @enrich_block = block
640
- end
641
-
642
- # Get final event mapping (after overrides)
643
- def event_mapping
644
- mapping = E11y::Instruments::RailsInstrumentation::DEFAULT_RAILS_EVENT_MAPPING.dup
645
- mapping.merge!(@custom_event_classes)
646
- mapping.except(*@ignored_events)
647
- end
648
-
649
- # Check if event should be tracked
650
- def track?(asn_pattern)
651
- return false if @ignored_events.include?(asn_pattern)
652
- return true if @track_patterns.empty?
653
-
654
- @track_patterns.any? do |pattern|
655
- File.fnmatch(pattern, asn_pattern)
656
- end
657
- end
658
- end
659
- end
660
- end
661
- ```
662
-
663
- # ========================================
664
- # Sidekiq
665
- # ========================================
666
- sidekiq do
667
- enabled true # Set to false to disable Sidekiq integration
668
-
669
- # Server middleware (job execution)
670
- server_middleware do
671
- enabled true
672
- track_start true
673
- track_complete true
674
- track_failure true
675
- end
676
-
677
- # Client middleware (job enqueuing)
678
- client_middleware do
679
- enabled true
680
- track_enqueue true
681
- end
682
-
683
- # Trace propagation
684
- propagate_trace_context true
685
- trace_context_keys ['e11y_trace_id', 'e11y_span_id', 'e11y_sampled']
686
- end
687
-
688
- # ========================================
689
- # ActiveJob
690
- # ========================================
691
- active_job do
692
- enabled true # Set to false to disable ActiveJob integration
693
-
694
- track_enqueue true
695
- track_start true
696
- track_complete true
697
- track_failure true
698
-
699
- # Trace propagation
700
- propagate_trace_context true
701
-
702
- # Job-scoped buffer (like request-scoped buffer for HTTP)
703
- use_job_buffer true
704
-
705
- job_buffer do
706
- buffer_severities [:debug]
707
- flush_on do
708
- error true # Flush debug events if job fails
709
- success false # Discard debug events if job succeeds
710
- end
711
- max_events 1000
712
- end
713
- end
714
-
715
- # ========================================
716
- # Rack Middleware
717
- # ========================================
718
- rack_middleware do
719
- enabled true # Set to false to disable Rack middleware
720
-
721
- track_request_start true
722
- track_request_complete true
723
- track_request_failure true
724
-
725
- # Request-scoped buffer
726
- use_request_buffer true
727
- end
728
- end
729
- end
392
+ # Now sql.active_record uses CustomDatabaseQuery (see RailsInstrumentation.event_mapping).
730
393
  ```
731
394
 
732
395
  **Example: Disable specific instruments:**
@@ -734,19 +397,16 @@ end
734
397
  ```ruby
735
398
  # Disable ASN but keep Sidekiq
736
399
  E11y.configure do |config|
737
- config.instruments.active_support_notifications.enabled = false
738
- config.instruments.sidekiq.enabled = true
739
- config.instruments.active_job.enabled = true
400
+ config.rails_instrumentation_enabled = false
401
+ config.sidekiq_enabled = true
402
+ config.active_job_enabled = true
740
403
  end
741
404
 
742
405
  # Minimal setup: only Sidekiq
743
406
  E11y.configure do |config|
744
- config.instruments do
745
- active_support_notifications { enabled false }
746
- sidekiq { enabled true }
747
- active_job { enabled false }
748
- rack_middleware { enabled false }
749
- end
407
+ config.rails_instrumentation_enabled = false
408
+ config.sidekiq_enabled = true
409
+ config.active_job_enabled = false
750
410
  end
751
411
  ```
752
412
 
@@ -756,7 +416,7 @@ end
756
416
 
757
417
  **Design Decision:** E11y provides built-in event classes for standard Rails events.
758
418
 
759
- **Location:** `Events::Rails` namespace (auto-loaded by gem)
419
+ **Location:** `E11y::Events::Rails` namespace (auto-loaded by gem)
760
420
 
761
421
  ```ruby
762
422
  # app/events/rails/ (provided by E11y gem)
@@ -855,28 +515,14 @@ module Events
855
515
  end
856
516
  ```
857
517
 
858
- **User can override:**
859
-
860
- ```ruby
861
- # config/initializers/e11y.rb
862
- E11y.configure do |config|
863
- config.instruments.active_support_notifications do
864
- # Disable built-in events
865
- use_built_in_events false
866
-
867
- # Use custom events instead
868
- custom_mappings do
869
- map 'sql.active_record', to: MyApp::Events::DatabaseQuery
870
- map 'process_action.action_controller', to: MyApp::Events::HttpRequest
871
- end
872
- end
873
- end
874
- ```
518
+ **User can override** built-in ASN → event class mappings with `rails_instrumentation_custom_mappings` and skip patterns with `rails_instrumentation_ignore_events` (see §4.2).
875
519
 
876
520
  ---
877
521
 
878
522
  ## 5. Sidekiq Integration
879
523
 
524
+ **Implementation Note:** Sidekiq middleware emits `E11y::Events::Rails::Job::Enqueued`, `Started`, `Completed`, `Failed` for **raw Sidekiq jobs only** (`include Sidekiq::Worker`). When Sidekiq is the queue adapter for ActiveJob, jobs use `ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper`; we skip event emission in Sidekiq middleware to avoid double emission — ActiveJob events come from RailsInstrumentation (ASN).
525
+
880
526
  ### 5.1. Server Middleware (Job Execution)
881
527
 
882
528
  ```ruby
@@ -886,26 +532,29 @@ module E11y
886
532
  module Sidekiq
887
533
  class ServerMiddleware
888
534
  def call(worker, job, queue)
889
- # Extract trace context from job metadata
890
- trace_id = job['e11y_trace_id'] || E11y::TraceContext.generate_id
535
+ # C17 Hybrid: Job gets NEW trace_id, parent_trace_id links to enqueuing request
536
+ parent_trace_id = job['e11y_parent_trace_id']
537
+ trace_id = E11y::TraceContext.generate_id # NEW trace per job
891
538
  parent_span_id = job['e11y_span_id']
892
539
 
893
- # Restore trace context
894
540
  E11y::Current.set(
895
541
  trace_id: trace_id,
542
+ parent_trace_id: parent_trace_id,
896
543
  parent_span_id: parent_span_id,
897
544
  job_id: job['jid'],
898
545
  job_class: worker.class.name,
899
546
  queue: queue
900
547
  )
901
548
 
902
- # Start job-scoped buffer (optional, configurable)
903
- if E11y.config.instruments.sidekiq.use_job_buffer
904
- E11y::JobBuffer.start!
549
+ # Start request-scoped buffer (same as HTTP; config.ephemeral_buffer_enabled)
550
+ if E11y.config.ephemeral_buffer_enabled
551
+ limit = E11y.config.ephemeral_buffer_job_buffer_limit ||
552
+ E11y::Buffers::EphemeralBuffer::DEFAULT_BUFFER_LIMIT
553
+ E11y::Buffers::EphemeralBuffer.initialize!(buffer_limit: limit)
905
554
  end
906
555
 
907
556
  # Track job start
908
- Events::Rails::Job::Started.track(
557
+ E11y::Events::Rails::Job::Started.track(
909
558
  job_class: worker.class.name,
910
559
  job_id: job['jid'],
911
560
  queue: queue,
@@ -919,20 +568,20 @@ module E11y
919
568
  result = yield
920
569
 
921
570
  # Track job success
922
- Events::Rails::Job::Completed.track(
571
+ E11y::Events::Rails::Job::Completed.track(
923
572
  job_class: worker.class.name,
924
573
  job_id: job['jid'],
925
574
  duration: (Time.now - start_time) * 1000,
926
575
  queue: queue
927
576
  )
928
577
 
929
- # Flush job buffer (success case)
930
- E11y::JobBuffer.flush! if E11y.config.instruments.sidekiq.use_job_buffer
578
+ # Discard buffer on success (same as HTTP)
579
+ E11y::Buffers::EphemeralBuffer.discard if E11y.config.ephemeral_buffer_enabled
931
580
 
932
581
  result
933
582
  rescue => error
934
583
  # Track job failure
935
- Events::Rails::Job::Failed.track(
584
+ E11y::Events::Rails::Job::Failed.track(
936
585
  job_class: worker.class.name,
937
586
  job_id: job['jid'],
938
587
  duration: (Time.now - start_time) * 1000,
@@ -942,8 +591,8 @@ module E11y
942
591
  backtrace: error.backtrace&.first(10)
943
592
  )
944
593
 
945
- # Flush job buffer on error (includes debug events)
946
- E11y::JobBuffer.flush_on_error! if E11y.config.instruments.sidekiq.use_job_buffer
594
+ # Flush buffer on error (includes debug events)
595
+ E11y::Buffers::EphemeralBuffer.flush_on_error if E11y.config.ephemeral_buffer_enabled
947
596
 
948
597
  raise
949
598
  ensure
@@ -976,13 +625,13 @@ module E11y
976
625
  module Sidekiq
977
626
  class ClientMiddleware
978
627
  def call(worker_class, job, queue, redis_pool)
979
- # Propagate trace context to job
980
- job['e11y_trace_id'] = E11y::Current.trace_id
628
+ # C17 Hybrid: Propagate parent trace (job will create NEW trace_id)
629
+ job['e11y_parent_trace_id'] = E11y::Current.trace_id if E11y::Current.trace_id
981
630
  job['e11y_span_id'] = E11y::TraceContext.generate_span_id
982
631
  job['e11y_sampled'] = E11y::Current.sampled # Trace-consistent sampling
983
632
 
984
633
  # Track job enqueued
985
- Events::Rails::Job::Enqueued.track(
634
+ E11y::Events::Rails::Job::Enqueued.track(
986
635
  job_class: worker_class.to_s,
987
636
  job_id: job['jid'],
988
637
  queue: queue,
@@ -997,89 +646,22 @@ module E11y
997
646
  end
998
647
  ```
999
648
 
1000
- ### 5.3. Job-Scoped Buffer (Optional Feature)
649
+ ### 5.3. Buffer for Jobs
1001
650
 
1002
- **Design Decision:** Similar to request-scoped buffer, jobs can have their own buffer for debug events.
651
+ **Design Decision:** Jobs reuse the same `EphemeralBuffer` as HTTP requests. Same semantics: buffer debug events, flush on error, discard on success. Optional job-specific config allows tuning for longer-running jobs.
1003
652
 
1004
653
  ```ruby
1005
654
  # config/initializers/e11y.rb
1006
655
  E11y.configure do |config|
1007
- config.instruments.sidekiq do
1008
- # Enable job-scoped buffer (like request-scoped buffer)
1009
- use_job_buffer true
1010
-
1011
- job_buffer do
1012
- # Buffer debug events during job execution
1013
- buffer_severities [:debug]
1014
-
1015
- # Flush conditions
1016
- flush_on do
1017
- error true # Flush debug events if job fails
1018
- success false # Discard debug events if job succeeds
1019
- interval 5.seconds # Or flush every 5 seconds
1020
- end
1021
-
1022
- # Max buffer size per job
1023
- max_events 1000
1024
- end
1025
- end
1026
- end
1027
- ```
1028
-
1029
- **How it works:**
1030
-
1031
- ```ruby
1032
- class InvoiceGenerationWorker
1033
- include Sidekiq::Worker
656
+ # Shared buffer for HTTP and jobs
657
+ config.ephemeral_buffer_enabled = true
1034
658
 
1035
- def perform(order_id)
1036
- # Debug events are buffered
1037
- Events::Debug::FetchOrder.track(order_id: order_id)
1038
-
1039
- order = Order.find(order_id)
1040
-
1041
- Events::Debug::ValidateOrder.track(order_id: order_id, valid: order.valid?)
1042
-
1043
- if order.invalid?
1044
- # Job fails → debug events are flushed to adapters
1045
- raise "Invalid order"
1046
- end
1047
-
1048
- # Job succeeds → debug events are discarded
1049
- Events::InvoiceGenerated.track(order_id: order_id)
1050
- end
659
+ # Optional: job-specific overrides (jobs can run longer → more debug events)
660
+ config.ephemeral_buffer_job_buffer_limit = 500 # nil = use default (100)
1051
661
  end
1052
662
  ```
1053
663
 
1054
- **Job Buffer Lifecycle:**
1055
-
1056
- ```mermaid
1057
- sequenceDiagram
1058
- participant Worker as Sidekiq Worker
1059
- participant JobBuffer as Job Buffer
1060
- participant MainBuffer as Main Buffer
1061
- participant Adapters as Adapters
1062
-
1063
- Worker->>JobBuffer: Start job buffer
1064
-
1065
- Note over Worker: Job execution starts
1066
-
1067
- Worker->>JobBuffer: Track debug event
1068
- JobBuffer->>JobBuffer: Buffer (not flushed)
1069
-
1070
- Worker->>MainBuffer: Track info event
1071
- MainBuffer->>Adapters: Flush immediately (normal flow)
1072
-
1073
- alt Job succeeds
1074
- Worker->>JobBuffer: flush! (success)
1075
- JobBuffer->>JobBuffer: Discard debug events
1076
- Note over JobBuffer: Debug events never sent
1077
- else Job fails
1078
- Worker->>JobBuffer: flush_on_error!
1079
- JobBuffer->>MainBuffer: Move debug events to main buffer
1080
- MainBuffer->>Adapters: Flush all events (including debug)
1081
- end
1082
- ```
664
+ **Rationale:** Single buffer implementation, single config. Jobs may need higher `job_buffer_limit` when they process many items and emit more debug events than a typical HTTP request.
1083
665
 
1084
666
  ---
1085
667
 
@@ -1100,21 +682,21 @@ sequenceDiagram
1100
682
  ClientMW->>ClientMW: Extract trace_id from Current
1101
683
  ClientMW->>Redis: Store job + trace metadata
1102
684
 
1103
- Note over Redis: job['e11y_trace_id'] = 'abc123'<br/>job['e11y_span_id'] = 'span002'<br/>job['e11y_sampled'] = true
685
+ Note over Redis: job['e11y_parent_trace_id'] = 'abc123'<br/>job['e11y_span_id'] = 'span002'<br/>job['e11y_sampled'] = true
1104
686
 
1105
687
  ClientMW->>E11y: Track Enqueued event
1106
688
 
1107
689
  Note over ServerMW: Later... job dequeued
1108
690
 
1109
691
  Redis->>ServerMW: Fetch job
1110
- ServerMW->>ServerMW: Extract trace_id from job
692
+ ServerMW->>ServerMW: C17: new trace_id, parent_trace_id from job
1111
693
  ServerMW->>E11y: Restore Current context
1112
694
 
1113
- Note over E11y: Current.trace_id = 'abc123'<br/>Current.parent_span_id = 'span002'
695
+ Note over E11y: Current.trace_id = NEW<br/>Current.parent_trace_id = 'abc123'<br/>Current.parent_span_id = 'span002'
1114
696
 
1115
697
  ServerMW->>E11y: Track Started event
1116
698
  ServerMW->>Worker: perform
1117
- Worker->>E11y: Track custom events (same trace_id!)
699
+ Worker->>E11y: Track events (linked via parent_trace_id!)
1118
700
  ServerMW->>E11y: Track Completed event
1119
701
  ```
1120
702
 
@@ -1122,6 +704,13 @@ sequenceDiagram
1122
704
 
1123
705
  ## 6. ActiveJob Integration
1124
706
 
707
+ **Implementation Note:** Job lifecycle events (`Enqueued`, `Started`, `Completed`, `Failed`) come from **RailsInstrumentation** (ASN), not from ActiveJob callbacks. ActiveJob callbacks handle trace context propagation, request-scoped buffer, and SLO tracking only. This design:
708
+ - Uses ASN as single source for all queue adapters (Sidekiq, Resque, Solid Queue, etc.)
709
+ - Avoids duplicate emission when ActiveJob uses Sidekiq as adapter
710
+ - Keeps callbacks focused on context/buffer/SLO
711
+
712
+ **perform.active_job routing:** When payload contains `exception`, RailsInstrumentation routes to `E11y::Events::Rails::Job::Failed` (with `error_class`, `error_message`); otherwise to `Completed`.
713
+
1125
714
  ### 6.1. Callbacks Integration
1126
715
 
1127
716
  ```ruby
@@ -1140,23 +729,27 @@ module E11y
1140
729
  private
1141
730
 
1142
731
  def e11y_track_job_execution
1143
- # Extract trace context
1144
- trace_id = job_metadata['e11y_trace_id'] || E11y::TraceContext.generate_id
732
+ # C17 Hybrid: Job gets NEW trace_id, parent_trace_id links to enqueuer
733
+ parent_trace_id = job_metadata['e11y_parent_trace_id']
734
+ trace_id = E11y::TraceContext.generate_id
1145
735
  parent_span_id = job_metadata['e11y_span_id']
1146
736
 
1147
737
  E11y::Current.set(
1148
738
  trace_id: trace_id,
739
+ parent_trace_id: parent_trace_id,
1149
740
  parent_span_id: parent_span_id,
1150
741
  job_id: job_id,
1151
742
  job_class: self.class.name
1152
743
  )
1153
744
 
1154
- # Start job-scoped buffer (optional, configurable)
1155
- if E11y.config.instruments.active_job.use_job_buffer
1156
- E11y::JobBuffer.start!
745
+ # Start request-scoped buffer (same as HTTP; config.ephemeral_buffer_enabled)
746
+ if E11y.config.ephemeral_buffer_enabled
747
+ limit = E11y.config.ephemeral_buffer_job_buffer_limit ||
748
+ E11y::Buffers::EphemeralBuffer::DEFAULT_BUFFER_LIMIT
749
+ E11y::Buffers::EphemeralBuffer.initialize!(buffer_limit: limit)
1157
750
  end
1158
751
 
1159
- Events::Rails::Job::Started.track(
752
+ E11y::Events::Rails::Job::Started.track(
1160
753
  job_class: self.class.name,
1161
754
  job_id: job_id,
1162
755
  queue_name: queue_name,
@@ -1168,16 +761,16 @@ module E11y
1168
761
  begin
1169
762
  yield
1170
763
 
1171
- Events::Rails::Job::Completed.track(
764
+ E11y::Events::Rails::Job::Completed.track(
1172
765
  job_class: self.class.name,
1173
766
  job_id: job_id,
1174
767
  duration: (Time.now - start_time) * 1000
1175
768
  )
1176
769
 
1177
- # Flush job buffer (success case)
1178
- E11y::JobBuffer.flush! if E11y.config.instruments.active_job.use_job_buffer
770
+ # Discard buffer on success (same as HTTP)
771
+ E11y::Buffers::EphemeralBuffer.discard if E11y.config.ephemeral_buffer_enabled
1179
772
  rescue => error
1180
- Events::Rails::Job::Failed.track(
773
+ E11y::Events::Rails::Job::Failed.track(
1181
774
  job_class: self.class.name,
1182
775
  job_id: job_id,
1183
776
  duration: (Time.now - start_time) * 1000,
@@ -1185,8 +778,8 @@ module E11y
1185
778
  error_message: error.message
1186
779
  )
1187
780
 
1188
- # Flush job buffer on error (includes debug events)
1189
- E11y::JobBuffer.flush_on_error! if E11y.config.instruments.active_job.use_job_buffer
781
+ # Flush buffer on error (includes debug events)
782
+ E11y::Buffers::EphemeralBuffer.flush_on_error if E11y.config.ephemeral_buffer_enabled
1190
783
 
1191
784
  raise
1192
785
  ensure
@@ -1195,12 +788,12 @@ module E11y
1195
788
  end
1196
789
 
1197
790
  def e11y_track_job_enqueued
1198
- # Store trace context in job metadata
1199
- job_metadata['e11y_trace_id'] = E11y::Current.trace_id
791
+ # C17 Hybrid: Store parent trace (job will create NEW trace_id)
792
+ job_metadata['e11y_parent_trace_id'] = E11y::Current.trace_id if E11y::Current.trace_id
1200
793
  job_metadata['e11y_span_id'] = E11y::TraceContext.generate_span_id
1201
794
  job_metadata['e11y_sampled'] = E11y::Current.sampled
1202
795
 
1203
- Events::Rails::Job::Enqueued.track(
796
+ E11y::Events::Rails::Job::Enqueued.track(
1204
797
  job_class: self.class.name,
1205
798
  job_id: job_id,
1206
799
  queue_name: queue_name,
@@ -1235,7 +828,7 @@ module E11y
1235
828
  module Logger
1236
829
  class Bridge
1237
830
  def self.setup!
1238
- return unless E11y.config.logger_bridge.enabled
831
+ return unless E11y.config.logger_bridge_enabled
1239
832
 
1240
833
  # Replace Rails.logger
1241
834
  Rails.logger = Bridge.new(Rails.logger)
@@ -1304,17 +897,10 @@ module E11y
1304
897
  # Extract message
1305
898
  msg = message || (block_given? ? block.call : nil)
1306
899
 
1307
- # Track via E11y
1308
- Events::RailsLogger.track(
1309
- severity: severity,
1310
- message: msg.to_s,
1311
- caller_location: extract_caller_location
1312
- )
900
+ # Track via E11y (filtered by track_severities, ignore_patterns)
901
+ event_class_for_severity(severity).track(message: msg.to_s, caller_location: extract_caller_location)
1313
902
 
1314
- # Also log to original logger (dual logging)
1315
- if @original_logger && E11y.config.logger_bridge.dual_logging
1316
- @original_logger.public_send(severity, msg)
1317
- end
903
+ # Always delegate to original logger (SimpleDelegator super)
1318
904
  end
1319
905
 
1320
906
  def extract_caller_location
@@ -1330,50 +916,39 @@ module E11y
1330
916
  end
1331
917
  ```
1332
918
 
1333
- ### 7.2. Migration Strategy
919
+ ### 7.2. Configuration
1334
920
 
1335
921
  ```ruby
1336
922
  # config/initializers/e11y.rb
1337
923
  E11y.configure do |config|
1338
- config.logger_bridge do
1339
- enabled true
1340
-
1341
- # Dual logging (E11y + original Rails.logger)
1342
- dual_logging true # Keep writing to log/production.log
1343
-
1344
- # Which severities to track
1345
- track_severities [:info, :warn, :error, :fatal]
1346
-
1347
- # Skip noisy log messages
1348
- ignore_patterns [
1349
- /Started GET/,
1350
- /Completed \d+ OK/,
1351
- /CACHE/
1352
- ]
1353
-
1354
- # Sample high-volume logs
1355
- sample_rate 0.1 # 10% of logs
1356
-
1357
- # Enrich with Rails context
1358
- enrich_with_context true
1359
- context_fields [:controller, :action, :request_id]
1360
- end
924
+ config.logger_bridge_enabled = true
925
+
926
+ # Which severities to track (nil = all)
927
+ config.logger_bridge_track_severities = [:info, :warn, :error, :fatal]
928
+
929
+ # Skip noisy log messages (regex or string)
930
+ config.logger_bridge_ignore_patterns = [
931
+ /Started GET/,
932
+ /Completed \d+ OK/,
933
+ /CACHE/
934
+ ]
1361
935
  end
1362
936
  ```
1363
937
 
938
+ **Context:** Events are enriched with `trace_id`, `request_id`, `span_id`, `user_id` from `E11y::Current` via `Event::Base.build_context` — no separate config needed.
939
+
1364
940
  ---
1365
941
 
1366
942
  ## 8. Middleware Integration
1367
943
 
1368
- ### 8.0. Three Buffer Types (Summary)
944
+ ### 8.0. Buffer Types (Summary)
1369
945
 
1370
- **E11y has 3 independent buffer types:**
946
+ **E11y uses a single EphemeralBuffer for both HTTP and jobs:**
1371
947
 
1372
- | Buffer Type | Purpose | Lifecycle | Use Case |
1373
- |-------------|---------|-----------|----------|
1374
- | **Main Buffer** | All events (info+) | Global, flush every 200ms | Normal event tracking |
1375
- | **Request Buffer** | Debug events in HTTP requests | Per-request, flush on error | HTTP request debugging |
1376
- | **Job Buffer** | Debug events in background jobs | Per-job, flush on error | Background job debugging |
948
+ | Buffer | Purpose | Lifecycle | Config |
949
+ |--------|---------|-----------|--------|
950
+ | **Request Buffer** | Debug events (HTTP + jobs) | Per-request/job, flush on error, discard on success | `config.ephemeral_buffer_enabled`, `ephemeral_buffer_job_buffer_limit` |
951
+ | **Main Buffer** | All events (info+) | Global, flush every 200ms | |
1377
952
 
1378
953
  **Diagram:**
1379
954
 
@@ -1381,20 +956,20 @@ end
1381
956
  graph TB
1382
957
  subgraph "HTTP Request"
1383
958
  HTTPEvent[Event tracked] --> Decision1{Severity?}
1384
- Decision1 -->|:debug| RequestBuffer[Request Buffer]
959
+ Decision1 -->|:debug| [Request Buffer]
1385
960
  Decision1 -->|:info+| MainBuffer[Main Buffer]
1386
961
 
1387
- RequestBuffer --> OnError1{Request failed?}
962
+ --> OnError1{Request failed?}
1388
963
  OnError1 -->|Yes| MainBuffer
1389
964
  OnError1 -->|No| Discard1[Discard]
1390
965
  end
1391
966
 
1392
967
  subgraph "Background Job"
1393
968
  JobEvent[Event tracked] --> Decision2{Severity?}
1394
- Decision2 -->|:debug| JobBuffer[Job Buffer]
969
+ Decision2 -->|:debug|
1395
970
  Decision2 -->|:info+| MainBuffer2[Main Buffer]
1396
971
 
1397
- JobBuffer --> OnError2{Job failed?}
972
+ --> OnError2{Job failed?}
1398
973
  OnError2 -->|Yes| MainBuffer2
1399
974
  OnError2 -->|No| Discard2[Discard]
1400
975
  end
@@ -1405,8 +980,7 @@ graph TB
1405
980
  Interval --> Adapters[Flush to Adapters]
1406
981
  end
1407
982
 
1408
- style RequestBuffer fill:#fff3cd
1409
- style JobBuffer fill:#d4edda
983
+ style fill:#fff3cd
1410
984
  style MainBuffer fill:#d1ecf1
1411
985
  style MainBuffer2 fill:#d1ecf1
1412
986
  ```
@@ -1415,134 +989,17 @@ graph TB
1415
989
 
1416
990
  ```ruby
1417
991
  E11y.configure do |config|
1418
- # Main buffer (always enabled)
1419
- config.buffer.flush_interval = 200.milliseconds
1420
- config.buffer.max_size = 10_000
1421
-
1422
- # Request-scoped buffer (HTTP only)
1423
- config.instruments.rack_middleware.use_request_buffer = true
1424
-
1425
- # Job-scoped buffer (Sidekiq + ActiveJob)
1426
- config.instruments.sidekiq.use_job_buffer = true
1427
- config.instruments.active_job.use_job_buffer = true
992
+ # Request-scoped buffer (shared for HTTP and jobs)
993
+ config.ephemeral_buffer_enabled = true
994
+ config.ephemeral_buffer_job_buffer_limit = 500 # Optional: higher limit for jobs (nil = default 100)
1428
995
  end
1429
996
  ```
1430
997
 
1431
998
  ---
1432
999
 
1433
- ### 8.1. Request Middleware
1000
+ ### 8.1. Request middleware
1434
1001
 
1435
- ```ruby
1436
- # lib/e11y/middleware/request.rb
1437
- module E11y
1438
- module Middleware
1439
- class Request
1440
- def initialize(app)
1441
- @app = app
1442
- end
1443
-
1444
- def call(env)
1445
- request = Rack::Request.new(env)
1446
-
1447
- # Extract or generate trace_id
1448
- trace_id = extract_trace_id(request) || TraceContext.generate_id
1449
- span_id = TraceContext.generate_span_id
1450
-
1451
- # Set context
1452
- Current.set(
1453
- trace_id: trace_id,
1454
- span_id: span_id,
1455
- request_id: request_id(env),
1456
- user_id: extract_user_id(env),
1457
- ip_address: request.ip,
1458
- user_agent: request.user_agent
1459
- )
1460
-
1461
- # Start request-scoped buffer (for debug events)
1462
- # Note: This is ONLY for HTTP requests, not for jobs
1463
- # Jobs have their own JobBuffer (see Sidekiq/ActiveJob sections)
1464
- if E11y.config.instruments.rack_middleware.use_request_buffer
1465
- E11y::RequestBuffer.start!
1466
- end
1467
-
1468
- # Track request start
1469
- start_time = Time.now
1470
-
1471
- Events::Http::RequestStarted.track(
1472
- method: request.request_method,
1473
- path: request.path,
1474
- query: request.query_string,
1475
- format: request.format
1476
- )
1477
-
1478
- begin
1479
- status, headers, body = @app.call(env)
1480
-
1481
- # Track request completed
1482
- Events::Http::RequestCompleted.track(
1483
- method: request.request_method,
1484
- path: request.path,
1485
- status: status,
1486
- duration: (Time.now - start_time) * 1000
1487
- )
1488
-
1489
- # Add trace headers to response
1490
- headers['X-E11y-Trace-Id'] = trace_id
1491
- headers['X-E11y-Span-Id'] = span_id
1492
-
1493
- [status, headers, body]
1494
- rescue => error
1495
- # Track request failed
1496
- Events::Http::RequestFailed.track(
1497
- method: request.request_method,
1498
- path: request.path,
1499
- duration: (Time.now - start_time) * 1000,
1500
- error_class: error.class.name,
1501
- error_message: error.message
1502
- )
1503
-
1504
- # Flush request buffer (includes debug events on error)
1505
- if E11y.config.instruments.rack_middleware.use_request_buffer
1506
- E11y::RequestBuffer.flush_on_error!
1507
- end
1508
-
1509
- raise
1510
- ensure
1511
- # Flush request buffer (success case)
1512
- if E11y.config.instruments.rack_middleware.use_request_buffer && !error
1513
- E11y::RequestBuffer.flush!
1514
- end
1515
-
1516
- # Reset context
1517
- Current.reset
1518
- end
1519
- end
1520
-
1521
- private
1522
-
1523
- def extract_trace_id(request)
1524
- # W3C Trace Context
1525
- request.get_header('HTTP_TRACEPARENT')&.split('-')&.[](1) ||
1526
- # X-Request-ID
1527
- request.get_header('HTTP_X_REQUEST_ID') ||
1528
- # X-Trace-Id
1529
- request.get_header('HTTP_X_TRACE_ID')
1530
- end
1531
-
1532
- def request_id(env)
1533
- env['action_dispatch.request_id'] || SecureRandom.uuid
1534
- end
1535
-
1536
- def extract_user_id(env)
1537
- # Try to extract from Warden (Devise)
1538
- env['warden']&.user&.id ||
1539
- # Try to extract from session
1540
- env['rack.session']&.[]('user_id')
1541
- end
1542
- end
1543
- end
1544
- end
1545
- ```
1002
+ **Source of truth:** `lib/e11y/middleware/request.rb`. Earlier ADR excerpts used `Current.set`, non-existent `Events::Http::*` events, and a simplified error path—the real middleware wires W3C / fallback trace IDs, `E11y::Current`, optional `E11y::Buffers::EphemeralBuffer`, response status–based buffer flush, optional HTTP SLO tracking, and response headers.
1546
1003
 
1547
1004
  ---
1548
1005
 
@@ -1563,10 +1020,10 @@ module E11y
1563
1020
  # E11y.stats
1564
1021
  def E11y.stats
1565
1022
  {
1566
- events_tracked: Registry.all_events.sum { |e| e.track_count },
1023
+ events_tracked: Registry.event_classes.sum { |e| e.track_count },
1567
1024
  events_in_buffer: Buffer.size,
1568
- adapters: Adapters::Registry.all.map { |a|
1569
- { name: a.name, healthy: a.healthy? }
1025
+ adapters: config.adapters.map { |name, a|
1026
+ { name: name, healthy: a.respond_to?(:healthy?) ? a.healthy? : true }
1570
1027
  },
1571
1028
  rate_limiter: {
1572
1029
  current_rate: RateLimiter.current_rate,
@@ -1588,17 +1045,17 @@ module E11y
1588
1045
 
1589
1046
  # E11y.events
1590
1047
  def E11y.events
1591
- Registry.all_events.map(&:name).sort
1048
+ Registry.event_classes.map(&:name).sort
1592
1049
  end
1593
1050
 
1594
1051
  # E11y.adapters
1595
1052
  def E11y.adapters
1596
- Adapters::Registry.all.map do |adapter|
1053
+ config.adapters.map do |name, adapter|
1597
1054
  {
1598
- name: adapter.name,
1055
+ name: name,
1599
1056
  class: adapter.class.name,
1600
- healthy: adapter.healthy?,
1601
- capabilities: adapter.capabilities
1057
+ healthy: adapter.respond_to?(:healthy?) ? adapter.healthy? : true,
1058
+ capabilities: adapter.respond_to?(:capabilities) ? adapter.capabilities : {}
1602
1059
  }
1603
1060
  end
1604
1061
  end
@@ -1606,7 +1063,7 @@ module E11y
1606
1063
  # E11y.reset!
1607
1064
  def E11y.reset!
1608
1065
  Buffer.clear!
1609
- RequestBuffer.clear!
1066
+ .clear!
1610
1067
  puts "✅ Buffers cleared"
1611
1068
  end
1612
1069
  end
@@ -1621,7 +1078,7 @@ module E11y
1621
1078
  )
1622
1079
 
1623
1080
  # Disable rate limiting in console
1624
- config.rate_limiting.enabled = false
1081
+ config.rate_limiting_enabled = false
1625
1082
 
1626
1083
  # Show all severities
1627
1084
  config.severity_threshold = :debug
@@ -1772,7 +1229,7 @@ RSpec.describe OrdersController, type: :controller do
1772
1229
  }.to have_enqueued_job(SendOrderEmailJob)
1773
1230
 
1774
1231
  job = ActiveJob::Base.queue_adapter.enqueued_jobs.last
1775
- expect(job[:args].first['e11y_trace_id']).to be_present
1232
+ expect(job[:args].first['e11y_parent_trace_id']).to be_present
1776
1233
  end
1777
1234
  end
1778
1235
  end
@@ -1819,90 +1276,72 @@ end
1819
1276
 
1820
1277
  ```ruby
1821
1278
  E11y.configure do |config|
1822
- config.instruments.active_support_notifications.enabled = false # Disable ASN
1823
- config.instruments.sidekiq.enabled = true # Keep Sidekiq
1824
- config.instruments.active_job.enabled = true # Keep ActiveJob
1825
- config.instruments.rack_middleware.enabled = true # Keep HTTP tracking
1279
+ config.rails_instrumentation_enabled = false # Disable ASN
1280
+ config.sidekiq_enabled = true # Keep Sidekiq
1281
+ config.active_job_enabled = true # Keep ActiveJob
1282
+ config.logger_bridge_enabled = true # Keep logger bridge
1826
1283
  end
1827
1284
  ```
1828
1285
 
1829
1286
  ### Q2: Do you provide built-in event classes for Rails?
1830
1287
 
1831
- **Yes!** E11y includes `Events::Rails` namespace with common Rails events:
1288
+ **Yes!** E11y includes the `E11y::Events::Rails` namespace with common Rails events:
1832
1289
 
1833
- - `Events::Rails::Database::Query` (sql.active_record)
1834
- - `Events::Rails::Http::Request` (process_action.action_controller)
1835
- - `Events::Rails::Cache::Read/Write/Delete`
1836
- - `Events::Rails::Job::Enqueued/Started/Completed/Failed`
1290
+ - `E11y::Events::Rails::Database::Query` (sql.active_record)
1291
+ - `E11y::Events::Rails::Http::Request` (process_action.action_controller)
1292
+ - `E11y::Events::Rails::Cache::Read/Write/Delete`
1293
+ - `E11y::Events::Rails::Job::Enqueued/Started/Completed/Failed`
1837
1294
 
1838
1295
  **You can:**
1839
1296
  - Use them as-is (default)
1840
- - Override them with `custom_mappings`
1841
- - Disable them with `use_built_in_events false`
1297
+ - Override selected patterns via `config.rails_instrumentation_custom_mappings`
1298
+ - Skip patterns via `config.rails_instrumentation_ignore_events`
1299
+ - Turn the whole subscriber off with `config.rails_instrumentation_enabled = false`
1842
1300
 
1843
1301
  ### Q3: Is ActiveSupport::Notifications integration always on?
1844
1302
 
1845
- **No!** It's configurable:
1303
+ **No.** Toggle `rails_instrumentation_enabled`. Ignore individual ASN names by appending to `rails_instrumentation_ignore_events` (exact string match to the subscription key, e.g. `"render_template.action_view"`).
1846
1304
 
1847
1305
  ```ruby
1848
- config.instruments.active_support_notifications do
1849
- enabled false # Completely disable ASN integration
1306
+ E11y.configure do |config|
1307
+ config.rails_instrumentation_enabled = false
1850
1308
  end
1851
- ```
1852
1309
 
1853
- You can also filter which ASN events to track:
1854
-
1855
- ```ruby
1856
- config.instruments.active_support_notifications do
1857
- track_patterns ['sql.active_record', 'process_action.*']
1858
- ignore_patterns ['render_partial.*', 'SCHEMA']
1310
+ E11y.configure do |config|
1311
+ config.rails_instrumentation_ignore_events << "render_template.action_view"
1859
1312
  end
1860
1313
  ```
1861
1314
 
1862
1315
  ### Q4: Does request-scoped buffer work for Sidekiq/ActiveJob?
1863
1316
 
1864
- **No, they have their own job-scoped buffer!**
1317
+ **Yes. HTTP and jobs share the same EphemeralBuffer.**
1865
1318
 
1866
- - **Request Buffer** → HTTP requests only (Rack middleware)
1867
- - **Job Buffer** → Sidekiq + ActiveJob (separate buffer per job)
1319
+ - **Request Buffer** → HTTP requests and jobs (same buffer, same semantics)
1868
1320
  - **Main Buffer** → Global buffer for all info+ events
1869
1321
 
1870
- All 3 buffers are independent and configurable:
1322
+ Config:
1871
1323
 
1872
1324
  ```ruby
1873
- config.instruments.rack_middleware.use_request_buffer = true # HTTP
1874
- config.instruments.sidekiq.use_job_buffer = true # Sidekiq
1875
- config.instruments.active_job.use_job_buffer = true # ActiveJob
1325
+ config.ephemeral_buffer_enabled = true
1326
+ config.ephemeral_buffer_job_buffer_limit = 500 # Optional: higher limit for jobs (nil = default 100)
1876
1327
  ```
1877
1328
 
1878
1329
  ### Q5: How do I customize built-in Rails events?
1879
1330
 
1880
- **Option A: Override with custom event class:**
1331
+ **Override one mapping:**
1881
1332
 
1882
1333
  ```ruby
1883
- config.instruments.active_support_notifications do
1884
- custom_mappings do
1885
- map 'sql.active_record', to: MyApp::Events::CustomDatabaseQuery
1886
- end
1334
+ E11y.configure do |config|
1335
+ config.rails_instrumentation_custom_mappings["sql.active_record"] =
1336
+ MyApp::Events::CustomDatabaseQuery
1887
1337
  end
1888
1338
  ```
1889
1339
 
1890
- **Option B: Disable built-in events entirely:**
1891
-
1892
- ```ruby
1893
- config.instruments.active_support_notifications do
1894
- use_built_in_events false # No automatic mapping
1895
-
1896
- # Manually handle ASN events
1897
- enrich do |asn_event|
1898
- MyCustomHandler.call(asn_event)
1899
- end
1900
- end
1901
- ```
1340
+ **Disable automatic ASN E11y conversion:** set `rails_instrumentation_enabled = false` and subscribe to `ActiveSupport::Notifications` yourself if you need a custom pipeline.
1902
1341
 
1903
1342
  ### Q6: Can I use E11y without Rails?
1904
1343
 
1905
- **No.** E11y requires Rails 8.0+ and Ruby 3.3+. For non-Rails apps, consider other telemetry solutions.
1344
+ The supported path is **Rails 7.0+** (see gemspec). Non-Rails usage is not a supported integration surface today.
1906
1345
 
1907
1346
  ---
1908
1347