e11y 0.2.0 → 1.0.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 (230) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +130 -10
  3. data/CHANGELOG.md +56 -1
  4. data/CLAUDE.md +168 -0
  5. data/CONTRIBUTING.md +640 -0
  6. data/README.md +134 -702
  7. data/RELEASE.md +18 -3
  8. data/Rakefile +108 -29
  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 +29 -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} +35 -64
  30. data/docs/{ADR-002-metrics-yabeda.md → architecture/ADR-002-metrics-yabeda.md} +62 -236
  31. data/docs/{ADR-003-slo-observability.md → architecture/ADR-003-slo-observability.md} +27 -466
  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} +209 -339
  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} +41 -83
  40. data/docs/{ADR-013-reliability-error-handling.md → architecture/ADR-013-reliability-error-handling.md} +37 -12
  41. data/docs/{ADR-014-event-driven-slo.md → architecture/ADR-014-event-driven-slo.md} +12 -24
  42. data/docs/{ADR-015-middleware-order.md → architecture/ADR-015-middleware-order.md} +23 -41
  43. data/docs/{ADR-016-self-monitoring-slo.md → architecture/ADR-016-self-monitoring-slo.md} +52 -349
  44. data/docs/{ADR-017-multi-rails-compatibility.md → architecture/ADR-017-multi-rails-compatibility.md} +4 -11
  45. data/docs/architecture/ADR-018-memory-optimization.md +366 -0
  46. data/docs/{ADR-INDEX.md → architecture/ADR-INDEX.md} +11 -6
  47. data/docs/{00-ICP-AND-TIMELINE.md → prd/00-ICP-AND-TIMELINE.md} +6 -6
  48. data/docs/{01-SCALE-REQUIREMENTS.md → prd/01-SCALE-REQUIREMENTS.md} +6 -6
  49. data/docs/prd/01-overview-vision.md +19 -14
  50. data/docs/use_cases/README.md +22 -23
  51. data/docs/use_cases/UC-001-request-scoped-debug-buffering.md +50 -44
  52. data/docs/use_cases/UC-002-business-event-tracking.md +26 -95
  53. data/docs/use_cases/UC-003-event-metrics.md +66 -0
  54. data/docs/use_cases/UC-004-zero-config-slo-tracking.md +42 -101
  55. data/docs/use_cases/UC-005-sentry-integration.md +13 -15
  56. data/docs/use_cases/UC-006-trace-context-management.md +30 -28
  57. data/docs/use_cases/UC-007-pii-filtering.md +35 -87
  58. data/docs/use_cases/UC-008-opentelemetry-integration.md +51 -89
  59. data/docs/use_cases/UC-009-multi-service-tracing.md +4 -4
  60. data/docs/use_cases/UC-010-background-job-tracking.md +5 -5
  61. data/docs/use_cases/UC-011-rate-limiting.md +95 -168
  62. data/docs/use_cases/UC-012-audit-trail.md +21 -46
  63. data/docs/use_cases/UC-013-high-cardinality-protection.md +29 -167
  64. data/docs/use_cases/UC-014-adaptive-sampling.md +2 -2
  65. data/docs/use_cases/UC-015-cost-optimization.md +46 -99
  66. data/docs/use_cases/UC-016-rails-logger-migration.md +39 -213
  67. data/docs/use_cases/UC-017-local-development.md +203 -777
  68. data/docs/use_cases/UC-018-testing-events.md +3 -3
  69. data/docs/use_cases/UC-019-retention-based-routing.md +53 -106
  70. data/docs/use_cases/UC-020-event-versioning.md +8 -9
  71. data/docs/use_cases/UC-021-error-handling-retry-dlq.md +18 -22
  72. data/docs/use_cases/UC-022-event-registry.md +15 -21
  73. data/docs/use_cases/backlog.md +119 -87
  74. data/e11y.gemspec +2 -2
  75. data/gems/e11y-devtools/README.md +136 -0
  76. data/gems/e11y-devtools/config/routes.rb +8 -0
  77. data/gems/e11y-devtools/e11y-devtools.gemspec +25 -0
  78. data/gems/e11y-devtools/exe/e11y +34 -0
  79. data/gems/e11y-devtools/lib/e11y/devtools/mcp/server.rb +96 -0
  80. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tool_base.rb +25 -0
  81. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/clear.rb +31 -0
  82. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/errors.rb +35 -0
  83. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/event_detail.rb +33 -0
  84. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/events_by_trace.rb +33 -0
  85. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/interactions.rb +40 -0
  86. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/recent_events.rb +34 -0
  87. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/search.rb +34 -0
  88. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/stats.rb +30 -0
  89. data/gems/e11y-devtools/lib/e11y/devtools/overlay/assets/overlay.js +115 -0
  90. data/gems/e11y-devtools/lib/e11y/devtools/overlay/controller.rb +54 -0
  91. data/gems/e11y-devtools/lib/e11y/devtools/overlay/engine.rb +26 -0
  92. data/gems/e11y-devtools/lib/e11y/devtools/overlay/middleware.rb +80 -0
  93. data/gems/e11y-devtools/lib/e11y/devtools/overlay/rails_controller.rb +42 -0
  94. data/gems/e11y-devtools/lib/e11y/devtools/tui/app.rb +262 -0
  95. data/gems/e11y-devtools/lib/e11y/devtools/tui/grouping.rb +66 -0
  96. data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/event_detail.rb +62 -0
  97. data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/event_list.rb +70 -0
  98. data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/interaction_list.rb +47 -0
  99. data/gems/e11y-devtools/lib/e11y/devtools/version.rb +8 -0
  100. data/gems/e11y-devtools/lib/e11y/devtools.rb +13 -0
  101. data/gems/e11y-devtools/spec/e11y/devtools/mcp/tools_spec.rb +107 -0
  102. data/gems/e11y-devtools/spec/e11y/devtools/overlay/controller_spec.rb +58 -0
  103. data/gems/e11y-devtools/spec/e11y/devtools/overlay/middleware_spec.rb +46 -0
  104. data/gems/e11y-devtools/spec/e11y/devtools/tui/app_spec.rb +85 -0
  105. data/gems/e11y-devtools/spec/e11y/devtools/tui/grouping_spec.rb +64 -0
  106. data/gems/e11y-devtools/spec/spec_helper.rb +5 -0
  107. data/gems/e11y-devtools/spec/tui/widgets/event_list_spec.rb +44 -0
  108. data/gems/e11y-devtools/spec/tui/widgets/interaction_list_spec.rb +62 -0
  109. data/lib/e11y/adapters/audit_encrypted.rb +53 -11
  110. data/lib/e11y/adapters/base.rb +33 -34
  111. data/lib/e11y/adapters/dev_log/file_store.rb +143 -0
  112. data/lib/e11y/adapters/dev_log/query.rb +219 -0
  113. data/lib/e11y/adapters/dev_log.rb +118 -0
  114. data/lib/e11y/adapters/file.rb +3 -6
  115. data/lib/e11y/adapters/in_memory.rb +52 -5
  116. data/lib/e11y/adapters/in_memory_test.rb +29 -0
  117. data/lib/e11y/adapters/loki.rb +58 -23
  118. data/lib/e11y/adapters/null.rb +82 -0
  119. data/lib/e11y/adapters/opentelemetry_collector.rb +183 -0
  120. data/lib/e11y/adapters/otel_logs.rb +136 -23
  121. data/lib/e11y/adapters/sentry.rb +4 -7
  122. data/lib/e11y/adapters/stdout.rb +73 -7
  123. data/lib/e11y/adapters/yabeda.rb +153 -29
  124. data/lib/e11y/buffers/adaptive_buffer.rb +3 -17
  125. data/lib/e11y/buffers/{request_scoped_buffer.rb → ephemeral_buffer.rb} +72 -58
  126. data/lib/e11y/buffers/ring_buffer.rb +3 -16
  127. data/lib/e11y/configuration.rb +272 -0
  128. data/lib/e11y/console.rb +10 -17
  129. data/lib/e11y/current.rb +53 -1
  130. data/lib/e11y/debug/pipeline_inspector.rb +96 -0
  131. data/lib/e11y/documentation/generator.rb +48 -0
  132. data/lib/e11y/event/base.rb +176 -82
  133. data/lib/e11y/event/value_sampling_config.rb +1 -5
  134. data/lib/e11y/events/rails/database/query.rb +1 -4
  135. data/lib/e11y/events/rails/job/failed.rb +2 -0
  136. data/lib/e11y/instruments/active_job.rb +46 -12
  137. data/lib/e11y/instruments/rails_instrumentation.rb +49 -24
  138. data/lib/e11y/instruments/sidekiq.rb +137 -31
  139. data/lib/e11y/linters/base.rb +11 -0
  140. data/lib/e11y/linters/pii/pii_declaration_linter.rb +120 -0
  141. data/lib/e11y/linters/slo/config_consistency_linter.rb +76 -0
  142. data/lib/e11y/linters/slo/explicit_declaration_linter.rb +36 -0
  143. data/lib/e11y/linters/slo/slo_status_from_linter.rb +41 -0
  144. data/lib/e11y/logger/bridge.rb +26 -7
  145. data/lib/e11y/metrics/cardinality_protection.rb +10 -15
  146. data/lib/e11y/metrics/cardinality_tracker.rb +16 -6
  147. data/lib/e11y/metrics/registry.rb +3 -5
  148. data/lib/e11y/metrics/test_backend.rb +62 -0
  149. data/lib/e11y/metrics.rb +56 -10
  150. data/lib/e11y/middleware/adapter_resolver.rb +40 -0
  151. data/lib/e11y/middleware/audit_signing.rb +43 -6
  152. data/lib/e11y/middleware/baggage_protection.rb +75 -0
  153. data/lib/e11y/middleware/dev_log_source.rb +24 -0
  154. data/lib/e11y/middleware/event_slo.rb +23 -9
  155. data/lib/e11y/middleware/otel_span.rb +23 -0
  156. data/lib/e11y/middleware/pii_filter.rb +104 -75
  157. data/lib/e11y/middleware/rate_limiting.rb +54 -27
  158. data/lib/e11y/middleware/request.rb +70 -23
  159. data/lib/e11y/middleware/routing.rb +78 -21
  160. data/lib/e11y/middleware/sampling.rb +66 -17
  161. data/lib/e11y/middleware/self_monitoring_emit.rb +39 -0
  162. data/lib/e11y/middleware/trace_context.rb +45 -10
  163. data/lib/e11y/middleware/track_latency.rb +34 -0
  164. data/lib/e11y/middleware/validation.rb +7 -16
  165. data/lib/e11y/middleware/versioning.rb +26 -22
  166. data/lib/e11y/opentelemetry/semantic_conventions.rb +109 -0
  167. data/lib/e11y/opentelemetry/span_creator.rb +142 -0
  168. data/lib/e11y/pii/patterns.rb +12 -1
  169. data/lib/e11y/pipeline/builder.rb +1 -1
  170. data/lib/e11y/presets/audit_event.rb +13 -2
  171. data/lib/e11y/railtie.rb +52 -15
  172. data/lib/e11y/registry.rb +306 -0
  173. data/lib/e11y/reliability/circuit_breaker.rb +19 -21
  174. data/lib/e11y/reliability/dlq/base.rb +71 -0
  175. data/lib/e11y/reliability/dlq/file_adapter.rb +301 -0
  176. data/lib/e11y/reliability/dlq/file_storage.rb +63 -34
  177. data/lib/e11y/reliability/dlq/filter.rb +37 -54
  178. data/lib/e11y/reliability/retry_handler.rb +26 -29
  179. data/lib/e11y/reliability/retry_rate_limiter.rb +3 -11
  180. data/lib/e11y/sampling/error_spike_detector.rb +0 -2
  181. data/lib/e11y/sampling/load_monitor.rb +5 -9
  182. data/lib/e11y/sampling/stratified_tracker.rb +18 -0
  183. data/lib/e11y/self_monitoring/buffer_monitor.rb +2 -0
  184. data/lib/e11y/self_monitoring/performance_monitor.rb +19 -61
  185. data/lib/e11y/self_monitoring/reliability_monitor.rb +4 -74
  186. data/lib/e11y/slo/config_loader.rb +40 -0
  187. data/lib/e11y/slo/config_validator.rb +58 -0
  188. data/lib/e11y/slo/dashboard_generator.rb +122 -0
  189. data/lib/e11y/slo/event_driven.rb +8 -0
  190. data/lib/e11y/slo/tracker.rb +31 -4
  191. data/lib/e11y/testing/have_tracked_event_matcher.rb +190 -0
  192. data/lib/e11y/testing/rspec_matchers.rb +21 -0
  193. data/lib/e11y/testing/snapshot_matcher.rb +86 -0
  194. data/lib/e11y/trace_context/sampler.rb +35 -0
  195. data/lib/e11y/tracing/faraday_middleware.rb +31 -0
  196. data/lib/e11y/tracing/net_http_patch.rb +33 -0
  197. data/lib/e11y/tracing/propagator.rb +116 -0
  198. data/lib/e11y/tracing.rb +47 -0
  199. data/lib/e11y/version.rb +1 -1
  200. data/lib/e11y/versioning/version_extractor.rb +32 -0
  201. data/lib/e11y.rb +141 -265
  202. data/lib/generators/e11y/event/event_generator.rb +22 -0
  203. data/lib/generators/e11y/event/templates/event.rb.tt +16 -0
  204. data/lib/generators/e11y/grafana_dashboard/grafana_dashboard_generator.rb +30 -0
  205. data/lib/generators/e11y/grafana_dashboard/templates/e11y_dashboard.json +81 -0
  206. data/lib/generators/e11y/install/install_generator.rb +34 -0
  207. data/lib/generators/e11y/install/templates/e11y.rb +239 -0
  208. data/lib/generators/e11y/prometheus_alerts/prometheus_alerts_generator.rb +29 -0
  209. data/lib/generators/e11y/prometheus_alerts/templates/e11y_alerts.yml +28 -0
  210. data/lib/tasks/e11y_docs.rake +30 -0
  211. data/lib/tasks/e11y_events.rake +71 -0
  212. data/lib/tasks/e11y_lint.rake +91 -0
  213. data/lib/tasks/e11y_slo.rake +29 -0
  214. metadata +129 -39
  215. data/docs/ADR-010-developer-experience.md +0 -2166
  216. data/docs/API-REFERENCE-L28.md +0 -914
  217. data/docs/COMPREHENSIVE-CONFIGURATION.md +0 -2366
  218. data/docs/CONTRIBUTING.md +0 -312
  219. data/docs/IMPLEMENTATION_NOTES.md +0 -2804
  220. data/docs/IMPLEMENTATION_PLAN.md +0 -1971
  221. data/docs/IMPLEMENTATION_PLAN_ARCHITECTURE.md +0 -586
  222. data/docs/PLAN.md +0 -148
  223. data/docs/README.md +0 -296
  224. data/docs/design/00-memory-optimization.md +0 -593
  225. data/docs/guides/MIGRATION-L27-L28.md +0 -692
  226. data/docs/guides/PERFORMANCE-BENCHMARKS.md +0 -434
  227. data/docs/guides/README.md +0 -44
  228. data/docs/use_cases/UC-003-pattern-based-metrics.md +0 -1627
  229. data/lib/e11y/adapters/registry.rb +0 -141
  230. /data/docs/{ADR-012-event-evolution.md → architecture/ADR-012-event-evolution.md} +0 -0
@@ -0,0 +1,239 @@
1
+ # frozen_string_literal: true
2
+
3
+ # =============================================================================
4
+ # E11y Configuration
5
+ # =============================================================================
6
+ #
7
+ # This file is generated by `rails g e11y:install`.
8
+ # Options matching code defaults are commented out. Uncomment and modify to override.
9
+ # "Default:" in comments = value from E11y::Configuration when option is not set.
10
+ #
11
+ # Docs: https://github.com/arturseletskiy/e11y
12
+
13
+ E11y.configure do |config|
14
+ # =============================================================================
15
+ # BASIC
16
+ # =============================================================================
17
+
18
+ # Service name for traces and logs. Used in distributed tracing and log aggregation.
19
+ # Default: nil (Railtie sets from Rails.application.class.module_parent_name)
20
+ # config.service_name = ENV["SERVICE_NAME"] || Rails.application.class.module_parent_name.underscore
21
+
22
+ # Environment (development, test, production). Affects sampling and log levels.
23
+ # Default: nil (Railtie sets from Rails.env.to_s)
24
+ # config.environment = Rails.env.to_s
25
+
26
+ # Master switch. When false, E11y does not process events (adapters not called).
27
+ # Default: nil (Railtie sets !Rails.env.test?)
28
+ # config.enabled = !Rails.env.test?
29
+
30
+ # Default retention period for events routed to adapters. Used by retention-based routing.
31
+ # Default: 30.days
32
+ # config.default_retention_period = 30.days
33
+
34
+ # Internal E11y log level (:debug, :info, :warn, :error).
35
+ # Default: :info
36
+ # config.log_level = :info
37
+
38
+ # =============================================================================
39
+ # ADAPTERS
40
+ # =============================================================================
41
+ #
42
+ # Adapters receive events. Register by name (:logs, :errors_tracker, etc.).
43
+ # Severity mapping: error/fatal → logs + errors_tracker; others → logs only.
44
+ # Default: {} (empty; you must register at least one adapter)
45
+
46
+ config.adapters[:logs] = E11y::Adapters::Stdout.new(colorize: true)
47
+
48
+ # Uncomment for production:
49
+ # config.adapters[:logs] = E11y::Adapters::Loki.new(url: ENV.fetch("LOKI_URL", "http://localhost:3100"))
50
+ # config.adapters[:errors_tracker] = E11y::Adapters::Sentry.new(dsn: ENV["SENTRY_DSN"])
51
+
52
+ # =============================================================================
53
+ # RAILS INTEGRATION
54
+ # =============================================================================
55
+
56
+ # Subscribe to ActiveSupport::Notifications (process_action, sql, etc.) and emit E11y events.
57
+ # Default: false. Uncomment and set true for Rails ASN integration.
58
+ config.rails_instrumentation_enabled = true
59
+
60
+ # Override event class for specific ASN pattern. Hash: pattern => EventClass.
61
+ # Default: {}
62
+ # config.rails_instrumentation_custom_mappings["sql.active_record"] = MyCustomEvent
63
+
64
+ # Ignore specific ASN events. Array of pattern strings.
65
+ # Default: []
66
+ # config.rails_instrumentation_ignore_events = []
67
+
68
+ # Sidekiq client/server middleware for trace propagation and job context.
69
+ # Default: false. Uncomment to auto-enable when Sidekiq is loaded.
70
+ config.sidekiq_enabled = defined?(Sidekiq)
71
+
72
+ # ActiveJob callbacks for job lifecycle events (enqueued, started, completed, failed).
73
+ # Default: false. Uncomment and set true for Rails ActiveJob integration.
74
+ config.active_job_enabled = true
75
+
76
+ # Wrap Rails.logger and send log calls as E11y events. Useful for migrating to structured logging.
77
+ # Default: false
78
+ # config.logger_bridge_enabled = false
79
+
80
+ # Severities to track when logger_bridge is enabled. nil = all (:debug, :info, :warn, :error, :fatal).
81
+ # Default: nil
82
+ # config.logger_bridge_track_severities = nil
83
+
84
+ # Regex patterns to ignore in logger bridge messages.
85
+ # Default: []
86
+ # config.logger_bridge_ignore_patterns = [/Started GET/, /Completed \d+ OK/]
87
+
88
+ # =============================================================================
89
+ # EPHEMERAL BUFFER (request/job-scoped debug events)
90
+ # =============================================================================
91
+ #
92
+ # Debug events accumulate in memory per request/job. On success: discarded. On error: flushed to adapters.
93
+ # Reduces noise by ~90% when only failures need full context.
94
+
95
+ # Enable request-scoped and job-scoped debug buffering.
96
+ # Default: false
97
+ # config.ephemeral_buffer_enabled = false
98
+
99
+ # Flush buffer on 5xx server errors (HTTP) or job failures.
100
+ # Default: true
101
+ # config.ephemeral_buffer_flush_on_error = true
102
+
103
+ # Additional HTTP statuses that trigger a flush (e.g. [403] for Forbidden).
104
+ # Default: []
105
+ # config.ephemeral_buffer_flush_on_statuses = []
106
+
107
+ # Adapter names to receive flushed debug events. nil = use fallback_adapters.
108
+ # Default: nil
109
+ # config.ephemeral_buffer_debug_adapters = nil
110
+
111
+ # Max debug events per job buffer. nil = use default (100).
112
+ # Default: nil
113
+ # config.ephemeral_buffer_job_buffer_limit = nil
114
+
115
+ # =============================================================================
116
+ # ERROR HANDLING
117
+ # =============================================================================
118
+
119
+ # When true, event tracking failures raise exceptions. When false, errors are logged and swallowed.
120
+ # Default: true. Set false for background jobs to avoid failing business logic.
121
+ # config.error_handling_fail_on_error = true
122
+
123
+ # =============================================================================
124
+ # SLO TRACKING
125
+ # =============================================================================
126
+ #
127
+ # Zero-config SLO metrics for HTTP requests and background jobs (availability, latency, success rate).
128
+
129
+ # Enable SLO tracking.
130
+ # Default: true
131
+ # config.slo_tracking_enabled = true
132
+
133
+ # HTTP status codes to exclude from SLO calculations.
134
+ # Default: []
135
+ # config.slo_tracking_http_ignore_statuses = [404, 401]
136
+
137
+ # Latency percentiles to emit (e.g. p50, p95, p99).
138
+ # Default: [50, 95, 99]
139
+ # config.slo_tracking_latency_percentiles = [50, 95, 99]
140
+
141
+ # Per-controller SLO targets. Use add_slo_controller for per-action or per-controller config.
142
+ # config.add_slo_controller "OrdersController", action: "create" do
143
+ # slo_target 0.999 # 99.9% availability
144
+ # latency_target 200 # ms
145
+ # end
146
+
147
+ # Per-job SLO config. Use add_slo_job to exclude jobs from tracking.
148
+ # config.add_slo_job "ProcessPaymentJob" do
149
+ # ignore true # exclude from SLO tracking
150
+ # end
151
+
152
+ # =============================================================================
153
+ # RATE LIMITING
154
+ # =============================================================================
155
+ #
156
+ # Token-bucket rate limiting to protect adapters from event floods. Supports global and per-event limits.
157
+
158
+ # Enable rate limiting.
159
+ # Default: false
160
+ # config.rate_limiting_enabled = false
161
+
162
+ # Global max events per window (seconds).
163
+ # Default: 10_000
164
+ # config.rate_limiting_global_limit = 10_000
165
+ # Default: 1.0
166
+ # config.rate_limiting_global_window = 1.0
167
+
168
+ # Default per-event limit when no per-event rule matches.
169
+ # Default: 1_000
170
+ # config.rate_limiting_per_event_limit = 1_000
171
+
172
+ # Per-event or per-pattern limits. Use add_rate_limit_per_event.
173
+ # config.add_rate_limit_per_event "payment.*", limit: 500, window: 60
174
+ # config.add_rate_limit_per_event "user.login.failed", limit: 100, window: 60
175
+
176
+ # =============================================================================
177
+ # SECURITY (Baggage PII protection)
178
+ # =============================================================================
179
+ #
180
+ # Blocks PII from OpenTelemetry Baggage and E11y::Current.baggage. Prevents propagation via W3C headers.
181
+
182
+ # Enable baggage protection.
183
+ # Default: true
184
+ # config.security_baggage_protection_enabled = true
185
+
186
+ # Allowed keys in baggage. Only these propagate to downstream services.
187
+ # Default: %w[trace_id span_id environment version service_name deployment_id request_id experiment experiment_id tenant feature_flag]
188
+ # config.security_baggage_protection_allowed_keys = %w[trace_id span_id ...]
189
+
190
+ # Block mode when PII key is set: :silent (drop), :warn (log + drop), :raise (raise).
191
+ # Default: :silent
192
+ # config.security_baggage_protection_block_mode = :silent
193
+
194
+ # =============================================================================
195
+ # TRACING (OpenTelemetry)
196
+ # =============================================================================
197
+
198
+ # Trace context source: :e11y (internal) or :opentelemetry (OTel SDK).
199
+ # Default: :e11y
200
+ # config.tracing_source = :e11y
201
+
202
+ # Default sample rate for traces (0.0..1.0).
203
+ # Default: 0.1
204
+ # config.tracing_default_sample_rate = 0.1
205
+
206
+ # Respect parent span sampling decision when OTel SDK provides context.
207
+ # Default: true
208
+ # config.tracing_respect_parent_sampling = true
209
+
210
+ # Per-event sample rates. Hash: event_name => rate.
211
+ # Default: {}
212
+ # config.tracing_per_event_sample_rates = {}
213
+
214
+ # Proc to force 100% sampling. Receives E11y::Current.to_context. Return true to always sample.
215
+ # Default: nil
216
+ # config.tracing_always_sample_if = ->(ctx) { [123, 456].include?(ctx[:user_id]) }
217
+
218
+ # Event name patterns that create OpenTelemetry spans. Glob: "order.*", "payment.*".
219
+ # Default: []
220
+ # config.opentelemetry_span_creation_patterns = ["order.*", "payment.*"]
221
+
222
+ # =============================================================================
223
+ # CARDINALITY PROTECTION (metrics label limits)
224
+ # =============================================================================
225
+ #
226
+ # Limits unique label values to prevent metric explosion. Used by adapters that support it (e.g. Yabeda).
227
+
228
+ # Max unique values per label before overflow strategy applies.
229
+ # Default: 1000
230
+ # config.cardinality_protection_max_cardinality_limit = 1000
231
+
232
+ # Label keys to exclude from cardinality protection (e.g. high-cardinality IDs).
233
+ # Default: []
234
+ # config.cardinality_protection_denylist = []
235
+
236
+ # Overflow strategy: :relabel (aggregate) or :drop.
237
+ # Default: :relabel
238
+ # config.cardinality_protection_overflow_strategy = :relabel
239
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module E11y
6
+ module Generators
7
+ # Generates Prometheus alerting rules for E11y metrics.
8
+ #
9
+ # @example
10
+ # rails g e11y:prometheus_alerts
11
+ # # => creates config/prometheus/e11y_alerts.yml
12
+ class PrometheusAlertsGenerator < Rails::Generators::Base
13
+ source_root File.expand_path("templates", __dir__)
14
+
15
+ desc "Creates Prometheus alerting rules for E11y in config/prometheus/."
16
+
17
+ def create_alerts
18
+ empty_directory "config/prometheus"
19
+ template "e11y_alerts.yml", "config/prometheus/e11y_alerts.yml"
20
+ end
21
+
22
+ def show_readme
23
+ say "\n✅ Prometheus alerts created: config/prometheus/e11y_alerts.yml", :green
24
+ say " Load via prometheus.yml rule_files section:\n"
25
+ say " rule_files:\n - config/prometheus/e11y_alerts.yml\n"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,28 @@
1
+ groups:
2
+ - name: e11y
3
+ rules:
4
+ # ── High adapter error rate ─────────────────────────────────────────
5
+ - alert: E11yHighAdapterErrorRate
6
+ expr: |
7
+ sum(rate(e11y_adapter_writes_total{status="failure"}[5m]))
8
+ / sum(rate(e11y_adapter_writes_total[5m])) > 0.05
9
+ for: 2m
10
+ labels:
11
+ severity: warning
12
+ annotations:
13
+ summary: "E11y adapter write error rate above 5%"
14
+ description: "Adapter error rate is {{ $value | humanizePercentage }} over the last 5 minutes."
15
+
16
+ # ── High validation failure rate ────────────────────────────────────
17
+ - alert: E11yHighValidationFailureRate
18
+ expr: |
19
+ rate(e11y_middleware_validation_total{result="failed"}[5m])
20
+ / (rate(e11y_middleware_validation_total{result="passed"}[5m]) + rate(e11y_middleware_validation_total{result="failed"}[5m])) > 0.1
21
+ for: 2m
22
+ labels:
23
+ severity: warning
24
+ annotations:
25
+ summary: "E11y validation failure rate above 10%"
26
+
27
+ # Note: E11yRateLimitDrops, E11yCircuitBreakerOpen, E11yDLQGrowing, E11yAdapterUnhealthy
28
+ # require metrics not yet implemented. Add when rate_limiting, circuit_breaker, DLQ metrics are wired.
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :e11y do
4
+ namespace :docs do
5
+ desc "Generate event docs (Markdown). Filter: SEVERITY=error ADAPTER=logs GREP=order"
6
+ task generate: :environment do
7
+ require "e11y/documentation/generator"
8
+
9
+ begin
10
+ Rails.application.eager_load! if defined?(Rails) && Rails.application.respond_to?(:eager_load!)
11
+ rescue Zeitwerk::SetupRequired
12
+ # Zeitwerk not ready (e.g. dummy app with eager_load=false); use already-loaded events
13
+ end
14
+
15
+ criteria = {}
16
+ criteria[:severity] = ENV["SEVERITY"]&.to_sym if ENV["SEVERITY"]
17
+ criteria[:adapter] = ENV["ADAPTER"]&.to_sym if ENV["ADAPTER"]
18
+ grep = ENV.fetch("GREP", nil)
19
+
20
+ out = if defined?(Rails) && Rails.respond_to?(:root)
21
+ Rails.root.join("docs", "events")
22
+ else
23
+ Pathname.new(File.join(Dir.pwd, "docs", "events"))
24
+ end
25
+
26
+ E11y::Documentation::Generator.generate(out.to_s, criteria: criteria, grep: grep)
27
+ puts "✅ Documentation generated in #{out}"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :e11y do
4
+ desc "List registered events (like rake routes). Filter: SEVERITY=error ADAPTER=logs GREP=order"
5
+ task events: :environment do
6
+ require "e11y/registry"
7
+
8
+ begin
9
+ Rails.application.eager_load! if defined?(Rails) && Rails.application.respond_to?(:eager_load!)
10
+ rescue Zeitwerk::SetupRequired
11
+ # Zeitwerk not ready (e.g. dummy app with eager_load=false); use already-loaded events
12
+ end
13
+
14
+ criteria = {}
15
+ criteria[:severity] = ENV["SEVERITY"]&.to_sym if ENV["SEVERITY"]
16
+ criteria[:adapter] = ENV["ADAPTER"]&.to_sym if ENV["ADAPTER"]
17
+ grep = ENV.fetch("GREP", nil)
18
+
19
+ classes = criteria.any? ? E11y::Registry.where(**criteria) : E11y::Registry.event_classes
20
+ classes = classes.select { |c| (c.respond_to?(:event_name) ? c.event_name : c.name).to_s.include?(grep) } if grep
21
+
22
+ # Column widths (rake routes style)
23
+ name_width = [classes.map { |c| (c.respond_to?(:event_name) ? c.event_name : c.name).to_s.length }.max || 20, 24].max
24
+ class_width = [classes.map { |c| c.name.to_s.length }.max || 30, 36].max
25
+ schema_max = 40
26
+ schema_width = [classes.map { |c| schema_str(c).length }.max || 20, schema_max].min
27
+
28
+ header = "#{'Event Name'.ljust(name_width)} #{'Class'.ljust(class_width)} Ver Sev Adapters #{'Schema'.ljust(schema_width)} PII Audit"
29
+ sep_len = header.length
30
+
31
+ puts header
32
+ puts "-" * sep_len
33
+
34
+ classes.each do |klass|
35
+ name = (klass.respond_to?(:event_name) ? klass.event_name : klass.name).to_s
36
+ version = klass.respond_to?(:version) ? "v#{klass.version}" : "—"
37
+ severity = (klass.respond_to?(:severity) ? klass.severity : "—").to_s
38
+ adapters = (klass.respond_to?(:adapters) && Array(klass.adapters).any? ? Array(klass.adapters).join(",") : "—").to_s
39
+ schema = schema_str(klass)
40
+ schema = "#{schema[0...(schema_max - 3)]}..." if schema.length > schema_max
41
+ pii = pii_str(klass)
42
+ audit = klass.respond_to?(:audit_event?) && klass.audit_event? ? "✓" : "—"
43
+ row = [name.ljust(name_width), klass.name.to_s.ljust(class_width), version.ljust(4),
44
+ severity.ljust(5), adapters.ljust(12), schema.ljust(schema_width), pii.ljust(6), audit]
45
+ puts row.join(" ")
46
+ end
47
+
48
+ puts "-" * sep_len
49
+ puts "#{classes.size} events"
50
+ end
51
+ end
52
+
53
+ # Helpers for e11y:events (rake routes style)
54
+ def schema_str(klass)
55
+ return "—" unless klass.respond_to?(:compiled_schema)
56
+
57
+ schema = klass.compiled_schema
58
+ return "—" if schema.nil? || !schema.respond_to?(:key_map)
59
+
60
+ schema.key_map.keys.map(&:name).join(", ")
61
+ rescue StandardError
62
+ "—"
63
+ end
64
+
65
+ def pii_str(klass)
66
+ return "—" unless klass.respond_to?(:pii_filtering_mode)
67
+
68
+ klass.pii_filtering_mode.to_s.tr("_", " ")
69
+ rescue StandardError
70
+ "—"
71
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :e11y do
4
+ desc "Validate slo.yml, SLO linters, and PII declarations (all-in-one)"
5
+ task lint: :environment do
6
+ all_ok = true
7
+
8
+ # 1. SLO config validation + SLO linters
9
+ begin
10
+ require "e11y/slo/config_loader"
11
+ require "e11y/slo/config_validator"
12
+
13
+ config = E11y::SLO::ConfigLoader.load
14
+ if config.nil?
15
+ puts "⚠️ slo.yml not found (optional, skipping SLO checks)"
16
+ else
17
+ errors = E11y::SLO::ConfigValidator.validate(config)
18
+ if errors.any?
19
+ puts "❌ slo.yml validation failed:"
20
+ errors.each { |e| puts " #{e}" }
21
+ all_ok = false
22
+ else
23
+ require "e11y/linters/slo/explicit_declaration_linter"
24
+ require "e11y/linters/slo/slo_status_from_linter"
25
+ require "e11y/linters/slo/config_consistency_linter"
26
+
27
+ E11y::Linters::SLO::ExplicitDeclarationLinter.validate!
28
+ E11y::Linters::SLO::SloStatusFromLinter.validate!
29
+ E11y::Linters::SLO::ConfigConsistencyLinter.validate!
30
+ puts "✅ SLO config and linters OK"
31
+ end
32
+ end
33
+ rescue E11y::Linters::LinterError => e
34
+ puts "❌ SLO linter failed: #{e.message}"
35
+ all_ok = false
36
+ end
37
+
38
+ # 2. PII linter
39
+ begin
40
+ require "e11y/linters/pii/pii_declaration_linter"
41
+
42
+ E11y::Linters::PII::PiiDeclarationLinter.validate_all!
43
+ puts "✅ PII declarations OK"
44
+ rescue E11y::Linters::PII::PiiDeclarationError => e
45
+ puts "❌ PII linter failed:\n\n#{e.message}"
46
+ all_ok = false
47
+ end
48
+
49
+ # 3. Schema check (each event has compiled_schema)
50
+ begin
51
+ require "e11y/registry"
52
+ begin
53
+ Rails.application.eager_load! if defined?(Rails) && Rails.application.respond_to?(:eager_load!)
54
+ rescue Zeitwerk::SetupRequired
55
+ # Zeitwerk not ready (e.g. dummy app with eager_load=false); use already-loaded events
56
+ end
57
+ schema_errors = []
58
+ E11y::Registry.event_classes.each do |klass|
59
+ next if klass.respond_to?(:compiled_schema) && klass.compiled_schema
60
+
61
+ name = klass.respond_to?(:event_name) ? klass.event_name : klass.name
62
+ schema_errors << "#{klass.name} (#{name}): missing schema"
63
+ end
64
+ if schema_errors.any?
65
+ puts "❌ Schema check failed:"
66
+ schema_errors.each { |e| puts " #{e}" }
67
+ all_ok = false
68
+ else
69
+ puts "✅ Schema check OK"
70
+ end
71
+ rescue StandardError => e
72
+ puts "❌ Schema check failed: #{e.message}"
73
+ all_ok = false
74
+ end
75
+
76
+ exit 1 unless all_ok
77
+ end
78
+ end
79
+
80
+ # Backwards compatibility: old tasks invoke e11y:lint
81
+ task "e11y:slo:validate" do
82
+ Rake::Task["e11y:lint"].invoke
83
+ end
84
+
85
+ namespace :e11y do
86
+ namespace :lint do
87
+ task pii: :environment do
88
+ Rake::Task["e11y:lint"].invoke
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :e11y do
4
+ namespace :slo do
5
+ desc "Generate Grafana dashboard from slo.yml"
6
+ task dashboard: :environment do
7
+ require "e11y/slo/config_loader"
8
+ require "e11y/slo/dashboard_generator"
9
+
10
+ config = E11y::SLO::ConfigLoader.load
11
+ if config.nil?
12
+ puts "⚠️ slo.yml not found, generating empty dashboard"
13
+ config = {}
14
+ end
15
+
16
+ json = E11y::SLO::DashboardGenerator.generate(config)
17
+ out = if defined?(Rails) && Rails.respond_to?(:root)
18
+ Rails.root.join("config", "grafana",
19
+ "e11y_slo_dashboard.json")
20
+ else
21
+ Pathname.new(File.join(Dir.pwd, "config",
22
+ "grafana", "e11y_slo_dashboard.json"))
23
+ end
24
+ FileUtils.mkdir_p(out.dirname)
25
+ File.write(out, json)
26
+ puts "✅ Dashboard written to #{out}"
27
+ end
28
+ end
29
+ end