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
data/docker-compose.yml CHANGED
@@ -1,5 +1,3 @@
1
- version: '3.8'
2
-
3
1
  services:
4
2
  loki:
5
3
  image: grafana/loki:2.9.0
@@ -69,6 +67,24 @@ services:
69
67
  networks:
70
68
  - e11y_network
71
69
 
70
+ # OpenTelemetry Collector - receives OTLP, exports to debug + Loki
71
+ # HTTP: localhost:4318, gRPC: localhost:4317
72
+ # Usage: docker compose up -d loki otel-collector
73
+ otel-collector:
74
+ image: otel/opentelemetry-collector-contrib:0.104.0
75
+ container_name: e11y_otel_collector
76
+ ports:
77
+ - "4317:4317" # OTLP gRPC
78
+ - "4318:4318" # OTLP HTTP
79
+ volumes:
80
+ - ./config/otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml
81
+ command: ["--config=/etc/otelcol-contrib/config.yaml"]
82
+ depends_on:
83
+ loki:
84
+ condition: service_healthy
85
+ networks:
86
+ - e11y_network
87
+
72
88
  networks:
73
89
  e11y_network:
74
90
  driver: bridge
data/docs/ADAPTERS.md ADDED
@@ -0,0 +1,76 @@
1
+ # Adapters
2
+
3
+ > Back to [README](../README.md#documentation)
4
+
5
+ E11y supports multiple adapters for different backends.
6
+
7
+ | Adapter | Purpose | Batching | Use Case |
8
+ |---------|---------|----------|----------|
9
+ | **Loki** | Log aggregation (Grafana) | Yes | Production logs |
10
+ | **Sentry** | Error tracking | Via SDK | Error monitoring |
11
+ | **OpenTelemetry** | OTLP export (OTelLogs, OpenTelemetryCollector) | Varies | Distributed tracing, logs |
12
+ | **Yabeda** | Prometheus metrics | N/A | Metrics export |
13
+ | **File** | Local logs | Yes | Development, CI |
14
+ | **Stdout** | Console output | No | Development |
15
+ | **InMemory** | Test buffer | No | Testing |
16
+
17
+ ## Configuration
18
+
19
+ ```ruby
20
+ # config/initializers/e11y.rb
21
+ E11y.configure do |config|
22
+ # Configure adapters
23
+ config.adapters[:logs] = E11y::Adapters::Loki.new(
24
+ url: ENV["LOKI_URL"],
25
+ batch_size: 100,
26
+ batch_timeout: 5,
27
+ compress: true
28
+ )
29
+
30
+ config.adapters[:errors_tracker] = E11y::Adapters::Sentry.new(
31
+ dsn: ENV["SENTRY_DSN"]
32
+ )
33
+
34
+ config.adapters[:stdout] = E11y::Adapters::Stdout.new(
35
+ format: :pretty
36
+ )
37
+
38
+ # OpenTelemetry Collector (compress: true default, requires Faraday)
39
+ # config.adapters[:otel] = E11y::Adapters::OpenTelemetryCollector.new(
40
+ # endpoint: ENV["OTEL_EXPORTER_OTLP_ENDPOINT"],
41
+ # service_name: "my-app"
42
+ # )
43
+ end
44
+ ```
45
+
46
+ ## Adapter Routing by Severity
47
+
48
+ Events are routed to adapters based on severity. The default mapping:
49
+
50
+ - `error`, `fatal` → `[:logs, :errors_tracker]`
51
+ - Other severities → `[:logs]`
52
+
53
+ Override routing explicitly:
54
+
55
+ ```ruby
56
+ class CustomEvent < E11y::Event::Base
57
+ adapters :logs, :stdout # Explicit routing
58
+ end
59
+ ```
60
+
61
+ ## Custom Adapters
62
+
63
+ Implement the `write` method:
64
+
65
+ ```ruby
66
+ class MyBackendAdapter < E11y::Adapters::Base
67
+ def write(event_data)
68
+ # event_data contains event_name, payload, severity, timestamp, etc.
69
+ MyBackend.send_event(event_data)
70
+ end
71
+ end
72
+
73
+ E11y.configure do |config|
74
+ config.adapters[:my_backend] = MyBackendAdapter.new
75
+ end
76
+ ```
@@ -0,0 +1,59 @@
1
+ # Adaptive Sampling
2
+
3
+ > Back to [README](../README.md#documentation)
4
+
5
+ E11y supports adaptive sampling to reduce event volume during high load.
6
+
7
+ Sampling strategies:
8
+
9
+ 1. **Error-based** - Increase sampling during error spikes
10
+ 2. **Load-based** - Reduce sampling under high throughput
11
+ 3. **Value-based** - Always sample high-value events
12
+
13
+ > **Note:** Rate limiting (`E11y::Middleware::RateLimiting`) is **not included in the default
14
+ > pipeline**. To enable it, add it manually:
15
+ >
16
+ > ```ruby
17
+ > config.pipeline.use E11y::Middleware::RateLimiting
18
+ > ```
19
+ > Enabling `config.rate_limiting_enabled = true` alone has no effect without this step.
20
+
21
+ ## Configuration
22
+
23
+ ```ruby
24
+ E11y.configure do |config|
25
+ config.pipeline.use E11y::Middleware::Sampling,
26
+ default_sample_rate: 0.1,
27
+
28
+ # Error-based sampling
29
+ error_based_adaptive: true,
30
+ error_spike_config: {
31
+ window: 60,
32
+ absolute_threshold: 100,
33
+ relative_threshold: 3.0,
34
+ spike_duration: 300
35
+ },
36
+
37
+ # Load-based sampling
38
+ load_based_adaptive: true,
39
+ load_monitor_config: {
40
+ window: 60,
41
+ thresholds: {
42
+ normal: 1_000,
43
+ high: 10_000,
44
+ very_high: 50_000,
45
+ overload: 100_000
46
+ }
47
+ }
48
+ end
49
+ ```
50
+
51
+ ## Value-Based Sampling
52
+
53
+ Sample events based on payload values:
54
+
55
+ ```ruby
56
+ class PaymentEvent < E11y::Event::Base
57
+ sample_by_value :amount, greater_than: 1000 # Always sample large payments
58
+ end
59
+ ```
@@ -0,0 +1,104 @@
1
+ # E11y vs. Alternatives — Detailed Comparison
2
+
3
+ > For a quick overview, see the [comparison table in README](../README.md#what-makes-e11y-different).
4
+
5
+ ### Detailed Comparisons
6
+
7
+ #### vs. SaaS APM (Datadog, New Relic, Dynatrace)
8
+
9
+ **Datadog / New Relic:**
10
+ - ✅ **Pros:** Full-stack visibility, mature dashboards, auto-instrumentation
11
+ - ❌ **Cons:** $500-5k/month, vendor lock-in, no debug buffering, no schema validation
12
+ - **E11y advantage:** 10x cheaper, request-scoped buffering (unique), type-safe events, own your data
13
+
14
+ **When to use Datadog/New Relic instead:**
15
+ - You need frontend RUM (Real User Monitoring)
16
+ - You have polyglot microservices (not just Rails)
17
+ - Budget is unlimited, prefer turnkey solution
18
+
19
+ ---
20
+
21
+ #### vs. Open-Source Logging (Semantic Logger, Lograge)
22
+
23
+ **Semantic Logger:**
24
+ - ✅ **Pros:** Structured logs (JSON), async writes, Rails integration
25
+ - ❌ **Cons:** No debug buffering, no schema validation, no auto-metrics, logs-only
26
+ - **E11y advantage:** Request-scoped buffering (unique), schema validation, auto-metrics, unified events
27
+
28
+ **Lograge:**
29
+ - ✅ **Pros:** Reduces Rails log noise (single-line requests)
30
+ - ❌ **Cons:** Filtering only, no buffering, no validation, no metrics
31
+ - **E11y advantage:** Request-scoped buffering (selective, not filtering), schema validation, auto-metrics
32
+
33
+ **When to use Semantic Logger instead:**
34
+ - You only need structured JSON logs (no events/metrics)
35
+ - You don't need debug buffering or schema validation
36
+
37
+ ---
38
+
39
+ #### vs. OpenTelemetry
40
+
41
+ **OpenTelemetry:**
42
+ - ✅ **Pros:** Industry standard, polyglot, vendor-neutral, mature ecosystem
43
+ - ❌ **Cons:** Complex setup (1-2 weeks), no debug buffering, no schema validation, overkill for Rails monolith
44
+ - **E11y advantage:** Fast setup, Rails-first, request-scoped buffering, schema validation
45
+
46
+ **When to use OpenTelemetry instead:**
47
+ - You have microservices in multiple languages (Go, Java, Python, etc.)
48
+ - You need distributed tracing across services
49
+ - You have a platform team to manage complexity
50
+
51
+ **Use both:** E11y events can be sent to OpenTelemetry via `E11y::Adapters::OtelLogs`
52
+
53
+ ---
54
+
55
+ #### vs. Grafana + Loki + Prometheus
56
+
57
+ **Grafana Stack:**
58
+ - ✅ **Pros:** Open-source, powerful visualizations, mature, self-hosted
59
+ - ❌ **Cons:** Complex setup (2-3 days), requires DevOps, no Rails integration, no schema validation
60
+ - **E11y advantage:** Fast setup, Rails-native, schema validation, no DevOps required
61
+
62
+ **When to use Grafana Stack instead:**
63
+ - You already have Grafana/Loki infrastructure
64
+ - You have a dedicated DevOps team
65
+ - You need custom dashboards across multiple systems
66
+
67
+ **Use both:** E11y can send events to Loki via `E11y::Adapters::Loki`
68
+
69
+ ---
70
+
71
+ #### vs. Error Tracking (Sentry, Honeybadger, Rollbar)
72
+
73
+ **Sentry:**
74
+ - ✅ **Pros:** Excellent error tracking, stack traces, breadcrumbs, release tracking
75
+ - ❌ **Cons:** Errors-only, no debug buffering, no schema validation, $26-80/mo
76
+ - **E11y advantage:** Events + errors + metrics unified, request-scoped buffering, schema validation
77
+
78
+ **When to use Sentry instead:**
79
+ - You only need error tracking (not general observability)
80
+ - You need frontend JavaScript error tracking
81
+
82
+ **Use both:** E11y can send error events to Sentry via `E11y::Adapters::Sentry`
83
+
84
+ ---
85
+
86
+ #### vs. Rails-First APM (AppSignal, Skylight)
87
+
88
+ **AppSignal:**
89
+ - ✅ **Pros:** Rails-native, beautiful UI, performance monitoring, $23/mo entry
90
+ - ❌ **Cons:** SaaS lock-in, no debug buffering, no schema validation, limited to supported languages
91
+ - **E11y advantage:** Request-scoped buffering (unique), schema validation, own your data
92
+
93
+ **Skylight:**
94
+ - ✅ **Pros:** Rails performance profiling, SQL query analysis
95
+ - ❌ **Cons:** Performance-only (no logs/events), SaaS lock-in, $20+/mo
96
+ - **E11y advantage:** Unified events/logs/metrics, request-scoped buffering, own your data
97
+
98
+ **When to use AppSignal/Skylight instead:**
99
+ - You want zero-config turnkey solution
100
+ - You prefer paying for hosted service over self-hosting
101
+
102
+ **Use both:** E11y for events/logs/metrics, AppSignal for performance profiling
103
+
104
+ ---
@@ -0,0 +1,52 @@
1
+ # Configuration
2
+
3
+ > Back to [README](../README.md#documentation)
4
+
5
+ ## Basic Configuration
6
+
7
+ ```ruby
8
+ # config/initializers/e11y.rb
9
+ E11y.configure do |config|
10
+ # Service identification
11
+ config.service_name = "myapp"
12
+ config.environment = Rails.env
13
+
14
+ # Configure adapters
15
+ config.adapters[:logs] = E11y::Adapters::Loki.new(
16
+ url: ENV["LOKI_URL"],
17
+ batch_size: 100,
18
+ batch_timeout: 5,
19
+ compress: true
20
+ )
21
+
22
+ config.adapters[:errors_tracker] = E11y::Adapters::Sentry.new(
23
+ dsn: ENV["SENTRY_DSN"]
24
+ )
25
+
26
+ # Default retention period
27
+ config.default_retention_period = 30.days
28
+ end
29
+ ```
30
+
31
+ ## Middleware Pipeline
32
+
33
+ Configure middleware for sampling, PII filtering, and more. Add `TrackLatency` first for self-monitoring (Event.track() latency):
34
+
35
+ ```ruby
36
+ E11y.configure do |config|
37
+ # Self-monitoring: track latency (must be first)
38
+ config.pipeline.use E11y::Middleware::TrackLatency
39
+
40
+ # Sampling middleware
41
+ config.pipeline.use E11y::Middleware::Sampling,
42
+ default_sample_rate: 0.1,
43
+ error_based_adaptive: true,
44
+ load_based_adaptive: true
45
+
46
+ # PII filtering middleware
47
+ config.pipeline.use E11y::Middleware::PIIFilter
48
+
49
+ # Trace context middleware
50
+ config.pipeline.use E11y::Middleware::TraceContext
51
+ end
52
+ ```
@@ -0,0 +1,44 @@
1
+ # Distributed Tracing
2
+
3
+ > Back to [README](../README.md#documentation)
4
+
5
+ E11y automatically attaches W3C Trace Context headers to incoming requests via the `TraceContext` middleware and propagates trace/span IDs through the event pipeline.
6
+
7
+ ## Incoming Trace Context
8
+
9
+ Incoming `traceparent` / `tracestate` headers are extracted automatically:
10
+
11
+ ```ruby
12
+ E11y.configure do |config|
13
+ config.pipeline.use E11y::Middleware::TraceContext
14
+ end
15
+ ```
16
+
17
+ Events tracked during a request will include `trace_id` and `span_id` from the incoming context.
18
+
19
+ ## Outgoing HTTP Trace Propagation (Manual — v1.0)
20
+
21
+ > **Note:** Automatic outgoing trace context injection (Faraday / Net::HTTP middleware) is planned for v1.1.
22
+ > Until then, use the helper below to propagate W3C Trace Context manually:
23
+
24
+ ```ruby
25
+ # Helper: build W3C traceparent header from current context
26
+ def traceparent_header
27
+ return {} unless E11y::Current.trace_id
28
+
29
+ span_id = E11y::Current.span_id || SecureRandom.hex(8)
30
+ { "traceparent" => "00-#{E11y::Current.trace_id}-#{span_id}-01" }
31
+ end
32
+
33
+ # Faraday — inject on each connection
34
+ conn = Faraday.new(url: "https://api.example.com") do |f|
35
+ f.headers.merge!(traceparent_header)
36
+ end
37
+
38
+ # Net::HTTP — inject per request
39
+ request = Net::HTTP::Post.new("/events")
40
+ traceparent_header.each { |k, v| request[k] = v }
41
+ http.request(request)
42
+ ```
43
+
44
+ This ensures downstream services receive a valid `traceparent` header and can correlate logs/traces back to the originating request.
@@ -0,0 +1,13 @@
1
+ # Limitations & Tradeoffs
2
+
3
+ > Back to [README](../README.md#documentation)
4
+
5
+ E11y trades generality for Rails-specific ergonomics. Know what you're getting:
6
+
7
+ | Limitation | Detail |
8
+ |------------|--------|
9
+ | **Rails only** | No Sinatra, Hanami, or pure-Ruby support. Railtie is required. |
10
+ | **Ruby 3.2+** | Older projects can't use it without upgrading Ruby. |
11
+ | **Rails 7.0–8.0** | Rails 8.1 excluded (sqlite3 bug in test environment). |
12
+ | **Memory overhead** | Debug buffer holds events in RAM per request. Under heavy load with large payloads, monitor heap usage. |
13
+ | **No distributed tracing UI** | OTel adapter emits spans, but e11y has no built-in trace visualization. Use Grafana Tempo or Jaeger. |
@@ -0,0 +1,84 @@
1
+ # Metrics DSL
2
+
3
+ > Back to [README](../README.md#documentation)
4
+
5
+ Define Prometheus metrics alongside events.
6
+
7
+ ## Basic Example
8
+
9
+ ```ruby
10
+ class OrderPaidEvent < E11y::Event::Base
11
+ schema do
12
+ required(:order_id).filled(:string)
13
+ required(:amount).filled(:float)
14
+ required(:currency).filled(:string)
15
+ end
16
+
17
+ metrics do
18
+ # Counter: Track number of paid orders
19
+ counter :orders_total, tags: [:currency]
20
+
21
+ # Histogram: Track order amount distribution
22
+ histogram :order_amount,
23
+ value: :amount,
24
+ tags: [:currency],
25
+ buckets: [10, 50, 100, 500, 1000]
26
+
27
+ # Gauge: Track active orders
28
+ gauge :active_orders, value: :active_count
29
+ end
30
+ end
31
+
32
+ # One track() call = event + metrics
33
+ OrderPaidEvent.track(order_id: "123", amount: 99.99, currency: "USD")
34
+ # => orders_total{currency="USD"} +1
35
+ # => order_amount{currency="USD"} observe 99.99
36
+ ```
37
+
38
+ ## Metric Types
39
+
40
+ **Counter** - Monotonically increasing value:
41
+
42
+ ```ruby
43
+ metrics do
44
+ counter :orders_total, tags: [:currency, :status]
45
+ end
46
+ # => orders_total{currency="USD", status="paid"} 42
47
+ ```
48
+
49
+ **Histogram** - Distribution of values:
50
+
51
+ ```ruby
52
+ metrics do
53
+ histogram :order_amount,
54
+ value: :amount,
55
+ tags: [:currency],
56
+ buckets: [10, 50, 100, 500, 1000]
57
+ end
58
+ # => order_amount_bucket{currency="USD", le="100"} 15
59
+ ```
60
+
61
+ **Gauge** - Arbitrary value that can go up or down:
62
+
63
+ ```ruby
64
+ metrics do
65
+ gauge :queue_depth, value: :size, tags: [:queue_name]
66
+ end
67
+ # => queue_depth{queue_name="emails"} 37
68
+ ```
69
+
70
+ ## How It Works
71
+
72
+ 1. Define metrics in event class
73
+ 2. Metrics registered in `E11y::Metrics::Registry` at boot time
74
+ 3. When `track()` is called, metrics are automatically updated **if the Yabeda adapter is configured and routed to**
75
+ 4. Metrics exported via Yabeda adapter (Prometheus format)
76
+
77
+ > **Note:** The `metrics do` DSL only registers metric definitions. Metrics are actually updated
78
+ > when an event is written to the `E11y::Adapters::Yabeda` adapter. If you omit the Yabeda adapter
79
+ > from your configuration, `track()` will send events to Loki/Sentry but metric counters will not
80
+ > be incremented. Make sure to add:
81
+ >
82
+ > ```ruby
83
+ > config.adapters[:metrics] = E11y::Adapters::Yabeda.new
84
+ > ```
@@ -0,0 +1,60 @@
1
+ # Performance
2
+
3
+ > Back to [README](../README.md#documentation)
4
+
5
+ ## Design Principles
6
+
7
+ E11y is designed for performance:
8
+
9
+ - **Hash-based events** - Events are Hashes, not objects, minimizing allocations
10
+ - **Configurable validation** - Choose validation mode based on performance needs
11
+ - **Batching** - Loki and other adapters support batching to reduce network overhead
12
+ - **Sampling** - Adaptive sampling reduces event volume under high load
13
+
14
+ ## Benchmarks (Ruby 3.3, measured via `rake spec:benchmark` and `rake spec:memory`)
15
+
16
+ | Metric | Value | Notes |
17
+ |--------|-------|-------|
18
+ | Event tracking latency (p99, `:always`) | <70µs | Full dry-schema validation per event |
19
+ | Event tracking latency (p99, `:sampled` 1%) | <10µs | Schema runs ~1% of events |
20
+ | Event tracking latency (p99, `:never`) | <50µs | Pipeline overhead, no validation |
21
+ | Memory allocations (`:always` mode) | ~47 objects/event | Baseline; threshold ≤72 |
22
+ | Memory allocations (`:never` mode) | ~33 objects/event | Baseline; threshold ≤50 |
23
+ | Memory retained after 10K events | 0 objects | No leaks detected |
24
+ | Memory consumption (1K events) | <100 MB allocated | Small-scale benchmark target |
25
+
26
+ ## Validation Mode Trade-offs
27
+
28
+ ```ruby
29
+ # Fastest — skip schema checks in production hot paths
30
+ validation_mode :never # ~33 allocs/event, <50µs p99
31
+
32
+ # Balanced — validate 1% of traffic for regression detection
33
+ validation_mode :sampled, sample_rate: 0.01 # <10µs p99
34
+
35
+ # Safest — validate every event (default, recommended for dev/staging)
36
+ validation_mode :always # ~47 allocs/event, <70µs p99
37
+ ```
38
+
39
+ ## Running Benchmarks
40
+
41
+ ```bash
42
+ rake spec:benchmark # latency benchmarks (~44 examples)
43
+ rake spec:memory # allocation and leak checks
44
+ ```
45
+
46
+ See `spec/e11y/event/base_benchmark_spec.rb` and `spec/e11y/memory_spec.rb` for the full test suite.
47
+
48
+ ## Cardinality Protection
49
+
50
+ Optional cardinality protection prevents high-cardinality labels from overwhelming metrics systems:
51
+
52
+ ```ruby
53
+ E11y::Adapters::Loki.new(
54
+ url: "http://loki:3100",
55
+ enable_cardinality_protection: true, # default
56
+ max_label_cardinality: 1000 # ~1000 event types
57
+ )
58
+ ```
59
+
60
+ Labels = `event_name` + `severity` only. Payload (user_uuid, etc.) stays in log line — filter via LogQL: `| json | user_uuid="xxx"`.
@@ -0,0 +1,40 @@
1
+ # PII Filtering
2
+
3
+ > Back to [README](../README.md#documentation)
4
+
5
+ E11y provides PII filtering capabilities for sensitive data.
6
+
7
+ ## Rails Integration
8
+
9
+ E11y can respect `Rails.application.config.filter_parameters` when configured:
10
+
11
+ ```ruby
12
+ # config/application.rb
13
+ config.filter_parameters += [:password, :email, :ssn, :credit_card]
14
+
15
+ # E11y will filter these fields when PII filtering middleware is enabled
16
+ ```
17
+
18
+ ## Explicit PII Strategies
19
+
20
+ Configure PII filtering per event:
21
+
22
+ ```ruby
23
+ class PaymentEvent < E11y::Event::Base
24
+ contains_pii true
25
+
26
+ pii_filtering do
27
+ masks :card_number # Replace with "[FILTERED]"
28
+ hashes :user_email # SHA256 hash (searchable)
29
+ allows :amount # No filtering
30
+ end
31
+ end
32
+ ```
33
+
34
+ Available strategies:
35
+
36
+ - `masks` - Replace with "[FILTERED]"
37
+ - `hashes` - SHA256 hash (preserves searchability)
38
+ - `partials` - Show first/last characters
39
+ - `redacts` - Remove completely
40
+ - `allows` - No filtering
data/docs/PRESETS.md ADDED
@@ -0,0 +1,65 @@
1
+ # Presets
2
+
3
+ > Back to [README](../README.md#documentation)
4
+
5
+ E11y provides presets for common event types.
6
+
7
+ ## HighValueEvent
8
+
9
+ For financial transactions and critical business events:
10
+
11
+ ```ruby
12
+ class PaymentProcessedEvent < E11y::Event::Base
13
+ include E11y::Presets::HighValueEvent
14
+
15
+ schema do
16
+ required(:transaction_id).filled(:string)
17
+ required(:amount).filled(:decimal)
18
+ end
19
+ end
20
+
21
+ # Configured with:
22
+ # - severity: :success
23
+ # - sample_rate: 1.0 (always sampled)
24
+ # - adapters: [:logs, :errors_tracker]
25
+ # - rate_limit: unlimited
26
+ ```
27
+
28
+ ## AuditEvent
29
+
30
+ For compliance and audit trails:
31
+
32
+ ```ruby
33
+ class UserDeletedEvent < E11y::Event::Base
34
+ include E11y::Presets::AuditEvent
35
+
36
+ schema do
37
+ required(:user_id).filled(:string)
38
+ required(:deleted_by).filled(:string)
39
+ end
40
+ end
41
+
42
+ # Configured with:
43
+ # - sample_rate: 1.0 (never sampled)
44
+ # - rate_limit: unlimited
45
+ # Note: Set severity based on event criticality
46
+ ```
47
+
48
+ ## DebugEvent
49
+
50
+ For development and troubleshooting:
51
+
52
+ ```ruby
53
+ class SlowQueryEvent < E11y::Event::Base
54
+ include E11y::Presets::DebugEvent
55
+
56
+ schema do
57
+ required(:query).filled(:string)
58
+ required(:duration_ms).filled(:integer)
59
+ end
60
+ end
61
+
62
+ # Configured with:
63
+ # - severity: :debug
64
+ # - adapters: [:logs]
65
+ ```