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
data/lib/e11y.rb CHANGED
@@ -7,14 +7,27 @@ require "active_support/core_ext/numeric/time" # For 30.days, 7.years, etc.
7
7
  loader = Zeitwerk::Loader.for_gem
8
8
  # Configure inflector for acronyms
9
9
  loader.inflector.inflect(
10
+ "documentation" => "Documentation",
11
+ "debug" => "Debug",
12
+ "opentelemetry_collector" => "OpenTelemetryCollector",
13
+ "otel_span" => "OtelSpan",
10
14
  "pii" => "PII",
11
15
  "pii_filter" => "PIIFilter",
12
16
  "otel_logs" => "OTelLogs",
13
17
  "slo" => "SLO",
14
- "dlq" => "DLQ"
18
+ "dlq" => "DLQ",
19
+ "net_http_patch" => "NetHTTPPatch",
20
+ "rspec_matchers" => "RSpecMatchers",
21
+ "have_tracked_event_matcher" => "HaveTrackedEventMatcher",
22
+ "snapshot_matcher" => "SnapshotMatcher"
15
23
  )
16
24
  # Don't autoload railtie - it will be required manually when Rails is available
17
25
  loader.do_not_eager_load("#{__dir__}/e11y/railtie.rb")
26
+ # Generators live under lib/generators/ — not part of the autoloaded tree
27
+ loader.ignore("#{__dir__}/generators")
28
+ # Optional HTTP tracing files require external gems (faraday, net/http) — loaded on demand only
29
+ loader.ignore("#{__dir__}/e11y/tracing/faraday_middleware.rb")
30
+ loader.ignore("#{__dir__}/e11y/tracing/net_http_patch.rb")
18
31
  loader.setup
19
32
 
20
33
  # E11y - Event-Driven Observability for Ruby on Rails
@@ -24,8 +37,6 @@ loader.setup
24
37
  # config.adapters = [:loki, :sentry]
25
38
  # end
26
39
  #
27
- # E11y.track(Events::UserSignup.new(user_id: 123))
28
- #
29
40
  # @see https://e11y.dev Documentation
30
41
  module E11y
31
42
  class Error < StandardError; end
@@ -33,6 +44,9 @@ module E11y
33
44
  class ZoneViolationError < Error; end
34
45
  class InvalidPipelineError < Error; end
35
46
 
47
+ # Raised when PII key is blocked in baggage (ADR-006 §5.5). Used by BaggageProtection and E11y::Current.add_baggage.
48
+ class BaggagePiiError < Error; end
49
+
36
50
  class << self
37
51
  # Configure E11y
38
52
  #
@@ -56,301 +70,163 @@ module E11y
56
70
  end
57
71
  alias config configuration
58
72
 
59
- # Track an event
73
+ # Test adapter for specs (InMemoryTest in unit tests, InMemory in integration).
74
+ # Returns :test adapter (unit tests) or :memory adapter (integration tests from dummy config).
60
75
  #
61
- # @param event [Event] event instance to track
62
- # @return [void]
63
- #
64
- # @example
65
- # E11y.track(Events::UserSignup.new(user_id: 123))
66
- def track(event)
67
- # TODO: Implement in Phase 1
68
- raise NotImplementedError, "E11y.track will be implemented in Phase 1"
76
+ # @return [E11y::Adapters::InMemory, E11y::Adapters::InMemoryTest, nil]
77
+ def test_adapter
78
+ configuration.adapters[:test] || configuration.adapters[:memory]
69
79
  end
70
80
 
71
- # Get logger instance
81
+ # Trace an event through the pipeline (debug utility).
82
+ # Delegates to PipelineInspector.trace_event. Loads the inspector on demand.
72
83
  #
73
- # @return [Logger] logger instance
74
- def logger
75
- require "logger"
76
- @logger ||= ::Logger.new($stdout)
84
+ # @param event_class [Class] event class (e.g., Events::OrderCreated)
85
+ # @param payload [Hash] keyword arguments for the event payload
86
+ # @return [Hash] event_data after pipeline
87
+ #
88
+ # @example
89
+ # E11y.trace(Events::OrderCreated, order_id: "123", amount: 99.99)
90
+ def trace(event_class, **payload)
91
+ require "e11y/debug/pipeline_inspector"
92
+ E11y::Debug::PipelineInspector.trace_event(event_class, **payload)
77
93
  end
78
94
 
79
- # Reset configuration (primarily for testing)
95
+ # Track an event
96
+ #
97
+ # Accepts either an event instance or an event class with an optional payload.
98
+ # Delegates to the event class's `.track` method.
80
99
  #
100
+ # @param event_or_class [E11y::Event::Base, Class] event instance or event class
101
+ # @param payload [Hash] keyword arguments forwarded to EventClass.track (used with class form)
81
102
  # @return [void]
82
- # @api private
83
- def reset!
84
- @configuration = nil
85
- @logger = nil
103
+ #
104
+ # @example Pass an event instance
105
+ # E11y.track(Events::UserSignup.new)
106
+ #
107
+ # @example Pass an event class with payload
108
+ # E11y.track(Events::UserSignup, user_id: 123)
109
+ def track(event_or_class, **payload)
110
+ event_class = event_or_class.is_a?(Class) ? event_or_class : event_or_class.class
111
+ event_class.track(**payload)
86
112
  end
87
- end
88
113
 
89
- # Configuration class for E11y
90
- #
91
- # Adapters are referenced by name (e.g., :logs, :errors_tracker).
92
- # The actual implementation (Loki, Sentry, etc.) is configured separately.
93
- #
94
- # @example Configure adapters
95
- # E11y.configure do |config|
96
- # # Register adapter instances
97
- # config.adapters[:logs] = E11y::Adapters::Loki.new(url: "...")
98
- # config.adapters[:errors_tracker] = E11y::Adapters::Sentry.new(dsn: "...")
99
- # end
100
- #
101
- # @example Configure severity => adapter mapping
102
- # E11y.configure do |config|
103
- # config.adapter_mapping[:error] = [:logs, :errors_tracker]
104
- # config.adapter_mapping[:info] = [:logs]
105
- # end
106
- #
107
- # @example Configure middleware pipeline
108
- # E11y.configure do |config|
109
- # config.pipeline.use E11y::Middleware::Sampling, default_sample_rate: 0.1
110
- # end
111
- class Configuration
112
- attr_accessor :adapters, :log_level, :enabled, :environment, :service_name, :default_retention_period,
113
- :routing_rules, :fallback_adapters
114
- attr_reader :adapter_mapping, :pipeline, :rails_instrumentation, :logger_bridge, :request_buffer, :active_job,
115
- :sidekiq, :error_handling, :dlq_storage, :dlq_filter, :rate_limiting, :slo_tracking
116
-
117
- def initialize
118
- initialize_basic_config
119
- initialize_routing_config
120
- initialize_feature_configs
121
- configure_default_pipeline
122
- end
114
+ # Get logger instance.
115
+ # Priority: config.logger > Rails.logger (when in Rails) > $stdout.
116
+ # Set config.logger = Logger.new(nil) in tests to suppress output.
117
+ #
118
+ # @return [Logger] logger instance
119
+ def logger
120
+ return configuration.logger if configuration&.logger
123
121
 
124
- private
122
+ return @logger if defined?(@logger) && !@logger.nil?
125
123
 
126
- def initialize_basic_config
127
- @adapters = {} # Hash of adapter_name => adapter_instance
128
- @log_level = :info
129
- @pipeline = E11y::Pipeline::Builder.new
130
- @enabled = true
131
- @environment = nil
132
- @service_name = nil
124
+ require "logger"
125
+ @logger = if defined?(Rails) && Rails.respond_to?(:application) && Rails.application
126
+ Rails.logger
127
+ else
128
+ ::Logger.new($stdout)
129
+ end
133
130
  end
134
131
 
135
- def initialize_routing_config
136
- @adapter_mapping = default_adapter_mapping
137
- @default_retention_period = 30.days # Default: 30 days retention
138
- @routing_rules = [] # Array of lambdas for retention-based routing
139
- @fallback_adapters = [:stdout] # Fallback if no routing rule matches
140
- end
132
+ # Initialize E11y and all configured adapters.
133
+ # Call after the configure block at application startup.
134
+ #
135
+ # @return [void]
136
+ def start!
137
+ return unless configuration.enabled
141
138
 
142
- def initialize_feature_configs
143
- @rails_instrumentation = RailsInstrumentationConfig.new
144
- @logger_bridge = LoggerBridgeConfig.new
145
- @request_buffer = RequestBufferConfig.new
146
- @active_job = ActiveJobConfig.new
147
- @sidekiq = SidekiqConfig.new
148
- @error_handling = ErrorHandlingConfig.new # ✅ C18 Resolution
149
- @dlq_storage = nil # Set by user (e.g., DLQ::FileStorage instance)
150
- @dlq_filter = nil # Set by user (e.g., DLQ::Filter instance)
151
- @rate_limiting = RateLimitingConfig.new
152
- @slo_tracking = SLOTrackingConfig.new # ✅ L3.14.1
139
+ configuration.adapters.each_value do |adapter|
140
+ adapter.start! if adapter.respond_to?(:start!)
141
+ end
142
+ logger.info("[E11y] Started (#{configuration.adapters.size} adapters)")
153
143
  end
154
144
 
155
- public
156
-
157
- # Get adapters for given severity
145
+ # Gracefully shut down E11y, flushing pending events.
158
146
  #
159
- # @param severity [Symbol] Severity level
160
- # @return [Array<Symbol>] Adapter names (e.g., [:logs, :errors_tracker])
161
- def adapters_for_severity(severity)
162
- @adapter_mapping[severity] || @adapter_mapping[:default] || []
147
+ # @param timeout [Integer] Seconds to wait for each adapter flush (default: 5)
148
+ # @return [void]
149
+ def stop!(timeout: 5)
150
+ require "timeout"
151
+ configuration.adapters.each_value do |adapter|
152
+ if adapter.respond_to?(:stop!)
153
+ adapter.stop!(timeout: timeout)
154
+ elsif adapter.respond_to?(:flush!)
155
+ Timeout.timeout(timeout) { adapter.flush! }
156
+ end
157
+ rescue StandardError => e
158
+ logger.warn("[E11y] Adapter stop error: #{e.message}")
159
+ end
160
+ logger.info("[E11y] Stopped")
161
+ end
162
+
163
+ # Check whether E11y will process events with the given severity.
164
+ # Returns false if no healthy adapter is registered for that severity.
165
+ #
166
+ # @param severity [Symbol] e.g. :debug, :info, :error
167
+ # @return [Boolean]
168
+ def enabled_for?(severity)
169
+ return false unless configuration.enabled
170
+
171
+ configuration.adapters_for_severity(severity).any? do |name|
172
+ configuration.adapters[name]&.healthy?
173
+ end
174
+ rescue StandardError
175
+ false
163
176
  end
164
177
 
165
- # Get built pipeline (cached after first call)
178
+ # Current size of the request-scoped debug buffer for this thread.
166
179
  #
167
- # @return [#call] Built middleware pipeline
168
- def built_pipeline
169
- @built_pipeline ||= @pipeline.build(->(_event_data) {})
180
+ # @return [Integer]
181
+ def buffer_size
182
+ buffer = Thread.current[:e11y_ephemeral_buffer]
183
+ buffer.respond_to?(:size) ? buffer.size : 0
170
184
  end
171
185
 
172
- private
173
-
174
- # Default adapter mapping (convention-based)
186
+ # Circuit breaker states for all adapters.
175
187
  #
176
- # Adapter names represent PURPOSE, not implementation:
177
- # - :logs → centralized logging (implementation: Loki, Elasticsearch, CloudWatch, etc.)
178
- # - :errors_tracker → error tracking with alerting (implementation: Sentry, Rollbar, Bugsnag, etc.)
188
+ # @return [Hash{Symbol => Symbol}] adapter_name => :closed / :open / :half_open
189
+ def circuit_breaker_state
190
+ configuration.adapters.transform_values do |adapter|
191
+ if adapter.respond_to?(:circuit_breaker_state)
192
+ adapter.circuit_breaker_state
193
+ else
194
+ :closed
195
+ end
196
+ end
197
+ end
198
+
199
+ # Access the global Event Registry singleton.
179
200
  #
180
- # @return [Hash{Symbol => Array<Symbol>}] Default mapping (severity => adapter names)
181
- def default_adapter_mapping
182
- {
183
- error: %i[logs errors_tracker], # Errors: both logging + alerting
184
- fatal: %i[logs errors_tracker], # Fatal: both logging + alerting
185
- default: [:logs] # Others: logging only
186
- }
187
- end
188
-
189
- # Setup default middleware pipeline
201
+ # The registry auto-populates as event classes are defined (via the `event_name` DSL setter).
202
+ # Useful for introspection, documentation generation, and admin dashboards.
190
203
  #
191
- # Default pipeline order (per ADR-015):
192
- # 1. TraceContext - Add trace_id, span_id, timestamp (zone: :pre_processing)
193
- # 2. Validation - Schema validation (zone: :pre_processing)
194
- # 3. PIIFilter - PII filtering (zone: :security)
195
- # 4. AuditSigning - Audit event signing (zone: :security)
196
- # 5. Sampling - Adaptive sampling (zone: :routing)
197
- # 6. Routing - Buffer routing (zone: :adapters)
204
+ # @return [E11y::Registry]
198
205
  #
199
- # @return [void]
200
- # @see ADR-015 Middleware Execution Order
201
- def configure_default_pipeline
202
- # Zone: :pre_processing
203
- @pipeline.use E11y::Middleware::TraceContext
204
- @pipeline.use E11y::Middleware::Validation
205
-
206
- # Zone: :security
207
- @pipeline.use E11y::Middleware::PIIFilter
208
- @pipeline.use E11y::Middleware::AuditSigning
209
-
210
- # Zone: :routing
211
- @pipeline.use E11y::Middleware::Sampling
212
-
213
- # Zone: :adapters
214
- @pipeline.use E11y::Middleware::Routing
215
- end
216
- end
217
-
218
- # Rails Instrumentation configuration
219
- class RailsInstrumentationConfig
220
- attr_accessor :enabled, :custom_mappings, :ignore_events
221
-
222
- def initialize
223
- @enabled = false # Disabled by default, enabled by Railtie
224
- @custom_mappings = {}
225
- @ignore_events = []
226
- end
227
-
228
- # Override event class for specific ASN pattern (Devise-style)
229
- # @param pattern [String] ActiveSupport::Notifications pattern
230
- # @param event_class [Class] E11y event class
231
- # @return [void]
232
- def event_class_for(pattern, event_class)
233
- @custom_mappings[pattern] = event_class
206
+ # @example
207
+ # E11y.registry.event_classes
208
+ # E11y.registry.find("order.created")
209
+ def registry
210
+ Registry.instance
234
211
  end
235
212
 
236
- # Ignore specific ASN event
237
- # @param pattern [String] ActiveSupport::Notifications pattern
213
+ # Reset configuration (primarily for testing)
214
+ #
238
215
  # @return [void]
239
- def ignore_event(pattern)
240
- @ignore_events << pattern
241
- end
242
- end
243
-
244
- # Logger Bridge configuration
245
- #
246
- # Controls Rails.logger integration:
247
- # - When enabled = true: wraps Rails.logger and sends logs to E11y
248
- # - When enabled = false: no integration (default)
249
- #
250
- # @example Enable logger bridge
251
- # E11y.configure do |config|
252
- # config.logger_bridge.enabled = true # Wrap Rails.logger + send to E11y
253
- # end
254
- #
255
- # @see lib/e11y/logger/bridge.rb
256
- class LoggerBridgeConfig
257
- attr_accessor :enabled
258
-
259
- def initialize
260
- @enabled = false # Opt-in: disabled by default
261
- end
262
- end
263
-
264
- # Request Buffer configuration
265
- class RequestBufferConfig
266
- attr_accessor :enabled
267
-
268
- def initialize
269
- @enabled = false # Disabled by default
270
- end
271
- end
272
-
273
- # ActiveJob configuration
274
- #
275
- # Controls ActiveJob integration (callbacks for event tracking).
276
- # When enabled, E11y will automatically track job lifecycle events:
277
- # - job.enqueued
278
- # - job.started
279
- # - job.completed
280
- # - job.failed
281
- #
282
- # @see lib/e11y/instruments/active_job.rb
283
- class ActiveJobConfig
284
- attr_accessor :enabled
285
-
286
- def initialize
287
- @enabled = false # Disabled by default, enabled by Railtie
288
- end
289
- end
290
-
291
- # Sidekiq configuration
292
- #
293
- # Controls Sidekiq middleware integration for trace propagation and context setup.
294
- # Automatically enabled by Railtie when Sidekiq is detected.
295
- #
296
- # @see ADR-008 §9 (Sidekiq Integration)
297
- class SidekiqConfig
298
- attr_accessor :enabled
299
-
300
- def initialize
301
- @enabled = false # Disabled by default, enabled by Railtie when Sidekiq is present
302
- end
303
- end
304
-
305
- # Error Handling configuration (C18 Resolution)
306
- #
307
- # Controls whether event tracking failures should raise exceptions.
308
- # Default: true (for web requests - fast feedback)
309
- # Exception: false (for background jobs - don't fail business logic)
310
- #
311
- # @see ADR-013 §3.6 (Event Tracking in Background Jobs)
312
- class ErrorHandlingConfig
313
- attr_accessor :fail_on_error
314
-
315
- def initialize
316
- @fail_on_error = true # Default: raise errors (fast feedback for web requests)
317
- end
318
- end
319
-
320
- # Rate Limiting configuration (UC-011, C02 Resolution)
321
- #
322
- # Protects adapters from event floods using token bucket algorithm.
323
- #
324
- # @see UC-011 (Rate Limiting - DoS Protection)
325
- # @see ADR-013 §4.6 (C02 Resolution)
326
- class RateLimitingConfig
327
- attr_accessor :enabled, :global_limit, :per_event_limit, :window
328
-
329
- def initialize
330
- @enabled = false # Opt-in (enable explicitly)
331
- @global_limit = 10_000 # Max 10K events/sec globally
332
- @per_event_limit = 1_000 # Max 1K events/sec per event type
333
- @window = 1.0 # 1 second window
216
+ # @api private
217
+ def reset!
218
+ @configuration = nil
219
+ @logger = nil
220
+ E11y::Metrics.reset_backend!
334
221
  end
335
222
  end
336
223
 
337
- # SLO Tracking configuration (UC-004, ADR-003)
338
- #
339
- # Zero-config SLO tracking for HTTP requests and background jobs.
340
- # Automatically emits SLO metrics (availability, latency, success rate).
341
- #
342
- # @see UC-004 (Zero-Config SLO Tracking)
343
- # @see ADR-003 (SLO & Observability)
344
- #
345
- # @note C11 Resolution (Sampling Correction): Requires Phase 2.8 (Stratified Sampling).
346
- # Without stratified sampling, SLO metrics may be inaccurate when adaptive sampling is enabled.
347
- class SLOTrackingConfig
348
- attr_accessor :enabled
349
-
350
- def initialize
351
- @enabled = false # Opt-in (enable explicitly)
352
- end
353
- end
224
+ # Default allowed keys for baggage protection (ADR-006 §5.5).
225
+ # Used when security_baggage_protection_allowed_keys is not set.
226
+ BAGGAGE_PROTECTION_DEFAULT_ALLOWED_KEYS = %w[
227
+ trace_id span_id environment version service_name deployment_id request_id
228
+ experiment experiment_id tenant feature_flag
229
+ ].freeze
354
230
  end
355
231
 
356
232
  # Load Railtie if Rails is present
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module E11y
6
+ module Generators
7
+ # Generates an event class under app/events/.
8
+ #
9
+ # @example
10
+ # rails g e11y:event OrderPaid
11
+ # # => creates app/events/events/order_paid.rb
12
+ class EventGenerator < Rails::Generators::NamedBase
13
+ source_root File.expand_path("templates", __dir__)
14
+
15
+ desc "Creates an E11y event class in app/events/."
16
+
17
+ def create_event_file
18
+ template "event.rb.tt", File.join("app/events/events", "#{file_name}.rb")
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Events
4
+ class <%= class_name %> < E11y::Event::Base
5
+ # severity :info # auto-inferred from class name; override if needed
6
+
7
+ schema do
8
+ # required(:field_name).filled(:string)
9
+ # optional(:other_field).maybe(:integer)
10
+ end
11
+
12
+ # metrics do
13
+ # counter :<%= file_name.gsub("/", "_") %>_total
14
+ # end
15
+ end
16
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module E11y
6
+ module Generators
7
+ # Generates a Grafana dashboard JSON for E11y metrics.
8
+ #
9
+ # Requires Yabeda/Prometheus integration.
10
+ #
11
+ # @example
12
+ # rails g e11y:grafana_dashboard
13
+ # # => creates config/grafana/e11y_dashboard.json
14
+ class GrafanaDashboardGenerator < Rails::Generators::Base
15
+ source_root File.expand_path("templates", __dir__)
16
+
17
+ desc "Creates a Grafana dashboard JSON for E11y metrics in config/grafana/."
18
+
19
+ def create_dashboard
20
+ empty_directory "config/grafana"
21
+ template "e11y_dashboard.json", "config/grafana/e11y_dashboard.json"
22
+ end
23
+
24
+ def show_readme
25
+ say "\n✅ Grafana dashboard created: config/grafana/e11y_dashboard.json", :green
26
+ say " Import it via Grafana → Dashboards → Import → Upload JSON file\n"
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,81 @@
1
+ {
2
+ "title": "E11y Observability",
3
+ "uid": "e11y-overview",
4
+ "version": 1,
5
+ "schemaVersion": 36,
6
+ "tags": ["e11y", "ruby", "observability"],
7
+ "panels": [
8
+ {
9
+ "id": 1,
10
+ "type": "stat",
11
+ "title": "Events / sec",
12
+ "targets": [
13
+ {
14
+ "expr": "rate(e11y_events_total[1m])",
15
+ "legendFormat": "{{event_name}}"
16
+ }
17
+ ],
18
+ "gridPos": { "x": 0, "y": 0, "w": 6, "h": 4 }
19
+ },
20
+ {
21
+ "id": 2,
22
+ "type": "stat",
23
+ "title": "Error rate",
24
+ "targets": [
25
+ {
26
+ "expr": "rate(e11y_events_total{severity=\"error\"}[1m]) / rate(e11y_events_total[1m])",
27
+ "legendFormat": "error rate"
28
+ }
29
+ ],
30
+ "gridPos": { "x": 6, "y": 0, "w": 6, "h": 4 }
31
+ },
32
+ {
33
+ "id": 3,
34
+ "type": "timeseries",
35
+ "title": "Events by severity",
36
+ "targets": [
37
+ {
38
+ "expr": "rate(e11y_events_total[1m])",
39
+ "legendFormat": "{{severity}}"
40
+ }
41
+ ],
42
+ "gridPos": { "x": 0, "y": 4, "w": 12, "h": 8 }
43
+ },
44
+ {
45
+ "id": 4,
46
+ "type": "stat",
47
+ "title": "Rate limit drops",
48
+ "targets": [
49
+ {
50
+ "expr": "rate(e11y_rate_limit_dropped_total[1m])",
51
+ "legendFormat": "dropped"
52
+ }
53
+ ],
54
+ "gridPos": { "x": 12, "y": 0, "w": 6, "h": 4 }
55
+ },
56
+ {
57
+ "id": 5,
58
+ "type": "stat",
59
+ "title": "Circuit breaker trips",
60
+ "targets": [
61
+ {
62
+ "expr": "e11y_circuit_breaker_transitions_total{event=\"opened\"}",
63
+ "legendFormat": "{{adapter}}"
64
+ }
65
+ ],
66
+ "gridPos": { "x": 18, "y": 0, "w": 6, "h": 4 }
67
+ },
68
+ {
69
+ "id": 6,
70
+ "type": "timeseries",
71
+ "title": "DLQ queue depth",
72
+ "targets": [
73
+ {
74
+ "expr": "e11y_dlq_size",
75
+ "legendFormat": "DLQ"
76
+ }
77
+ ],
78
+ "gridPos": { "x": 12, "y": 4, "w": 12, "h": 8 }
79
+ }
80
+ ]
81
+ }
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module E11y
6
+ module Generators
7
+ # Creates config/initializers/e11y.rb and app/events/ directory scaffold.
8
+ #
9
+ # @example
10
+ # rails g e11y:install
11
+ class InstallGenerator < Rails::Generators::Base
12
+ source_root File.expand_path("templates", __dir__)
13
+
14
+ desc "Creates an E11y initializer and the app/events/ directory."
15
+
16
+ def create_initializer
17
+ template "e11y.rb", "config/initializers/e11y.rb"
18
+ end
19
+
20
+ def create_events_directory
21
+ empty_directory "app/events"
22
+ end
23
+
24
+ def show_readme
25
+ say "\n✅ E11y installed!", :green
26
+ say " • config/initializers/e11y.rb — configure adapters here"
27
+ say " • app/events/ — put your event classes here"
28
+ say "\nNext steps:"
29
+ say " rails g e11y:event OrderPaid # generate an event class"
30
+ say " E11y.start! # call after configure in production\n"
31
+ end
32
+ end
33
+ end
34
+ end