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.
- checksums.yaml +4 -4
- data/.rubocop.yml +130 -10
- data/CHANGELOG.md +56 -1
- data/CLAUDE.md +168 -0
- data/CONTRIBUTING.md +640 -0
- data/README.md +134 -702
- data/RELEASE.md +18 -3
- data/Rakefile +108 -29
- data/config/README.md +1 -1
- data/config/loki-local-config.yaml +12 -0
- data/config/otel-collector-config.yaml +44 -0
- data/cucumber.yml +1 -0
- data/docker-compose.yml +18 -2
- data/docs/ADAPTERS.md +76 -0
- data/docs/ADAPTIVE_SAMPLING.md +59 -0
- data/docs/COMPARISON.md +104 -0
- data/docs/CONFIGURATION.md +52 -0
- data/docs/DISTRIBUTED_TRACING.md +44 -0
- data/docs/LIMITATIONS.md +13 -0
- data/docs/METRICS_DSL.md +84 -0
- data/docs/PERFORMANCE.md +60 -0
- data/docs/PII_FILTERING.md +40 -0
- data/docs/PRESETS.md +65 -0
- data/docs/QUICK-START.md +546 -587
- data/docs/RAILS_INTEGRATION.md +29 -0
- data/docs/SCHEMA_VALIDATION.md +63 -0
- data/docs/SLO-PROMQL-ALERTS.md +161 -0
- data/docs/TESTING.md +69 -0
- data/docs/{ADR-001-architecture.md → architecture/ADR-001-architecture.md} +35 -64
- data/docs/{ADR-002-metrics-yabeda.md → architecture/ADR-002-metrics-yabeda.md} +62 -236
- data/docs/{ADR-003-slo-observability.md → architecture/ADR-003-slo-observability.md} +27 -466
- data/docs/{ADR-004-adapter-architecture.md → architecture/ADR-004-adapter-architecture.md} +163 -146
- data/docs/{ADR-005-tracing-context.md → architecture/ADR-005-tracing-context.md} +10 -9
- data/docs/{ADR-006-security-compliance.md → architecture/ADR-006-security-compliance.md} +184 -191
- data/docs/{ADR-007-opentelemetry-integration.md → architecture/ADR-007-opentelemetry-integration.md} +3 -21
- data/docs/{ADR-008-rails-integration.md → architecture/ADR-008-rails-integration.md} +209 -339
- data/docs/{ADR-009-cost-optimization.md → architecture/ADR-009-cost-optimization.md} +45 -54
- data/docs/architecture/ADR-010-developer-experience.md +522 -0
- data/docs/{ADR-011-testing-strategy.md → architecture/ADR-011-testing-strategy.md} +41 -83
- data/docs/{ADR-013-reliability-error-handling.md → architecture/ADR-013-reliability-error-handling.md} +37 -12
- data/docs/{ADR-014-event-driven-slo.md → architecture/ADR-014-event-driven-slo.md} +12 -24
- data/docs/{ADR-015-middleware-order.md → architecture/ADR-015-middleware-order.md} +23 -41
- data/docs/{ADR-016-self-monitoring-slo.md → architecture/ADR-016-self-monitoring-slo.md} +52 -349
- data/docs/{ADR-017-multi-rails-compatibility.md → architecture/ADR-017-multi-rails-compatibility.md} +4 -11
- data/docs/architecture/ADR-018-memory-optimization.md +366 -0
- data/docs/{ADR-INDEX.md → architecture/ADR-INDEX.md} +11 -6
- data/docs/{00-ICP-AND-TIMELINE.md → prd/00-ICP-AND-TIMELINE.md} +6 -6
- data/docs/{01-SCALE-REQUIREMENTS.md → prd/01-SCALE-REQUIREMENTS.md} +6 -6
- data/docs/prd/01-overview-vision.md +19 -14
- data/docs/use_cases/README.md +22 -23
- data/docs/use_cases/UC-001-request-scoped-debug-buffering.md +50 -44
- data/docs/use_cases/UC-002-business-event-tracking.md +26 -95
- data/docs/use_cases/UC-003-event-metrics.md +66 -0
- data/docs/use_cases/UC-004-zero-config-slo-tracking.md +42 -101
- data/docs/use_cases/UC-005-sentry-integration.md +13 -15
- data/docs/use_cases/UC-006-trace-context-management.md +30 -28
- data/docs/use_cases/UC-007-pii-filtering.md +35 -87
- data/docs/use_cases/UC-008-opentelemetry-integration.md +51 -89
- data/docs/use_cases/UC-009-multi-service-tracing.md +4 -4
- data/docs/use_cases/UC-010-background-job-tracking.md +5 -5
- data/docs/use_cases/UC-011-rate-limiting.md +95 -168
- data/docs/use_cases/UC-012-audit-trail.md +21 -46
- data/docs/use_cases/UC-013-high-cardinality-protection.md +29 -167
- data/docs/use_cases/UC-014-adaptive-sampling.md +2 -2
- data/docs/use_cases/UC-015-cost-optimization.md +46 -99
- data/docs/use_cases/UC-016-rails-logger-migration.md +39 -213
- data/docs/use_cases/UC-017-local-development.md +203 -777
- data/docs/use_cases/UC-018-testing-events.md +3 -3
- data/docs/use_cases/UC-019-retention-based-routing.md +53 -106
- data/docs/use_cases/UC-020-event-versioning.md +8 -9
- data/docs/use_cases/UC-021-error-handling-retry-dlq.md +18 -22
- data/docs/use_cases/UC-022-event-registry.md +15 -21
- data/docs/use_cases/backlog.md +119 -87
- data/e11y.gemspec +2 -2
- data/gems/e11y-devtools/README.md +136 -0
- data/gems/e11y-devtools/config/routes.rb +8 -0
- data/gems/e11y-devtools/e11y-devtools.gemspec +25 -0
- data/gems/e11y-devtools/exe/e11y +34 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/server.rb +96 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tool_base.rb +25 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/clear.rb +31 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/errors.rb +35 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/event_detail.rb +33 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/events_by_trace.rb +33 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/interactions.rb +40 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/recent_events.rb +34 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/search.rb +34 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/stats.rb +30 -0
- data/gems/e11y-devtools/lib/e11y/devtools/overlay/assets/overlay.js +115 -0
- data/gems/e11y-devtools/lib/e11y/devtools/overlay/controller.rb +54 -0
- data/gems/e11y-devtools/lib/e11y/devtools/overlay/engine.rb +26 -0
- data/gems/e11y-devtools/lib/e11y/devtools/overlay/middleware.rb +80 -0
- data/gems/e11y-devtools/lib/e11y/devtools/overlay/rails_controller.rb +42 -0
- data/gems/e11y-devtools/lib/e11y/devtools/tui/app.rb +262 -0
- data/gems/e11y-devtools/lib/e11y/devtools/tui/grouping.rb +66 -0
- data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/event_detail.rb +62 -0
- data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/event_list.rb +70 -0
- data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/interaction_list.rb +47 -0
- data/gems/e11y-devtools/lib/e11y/devtools/version.rb +8 -0
- data/gems/e11y-devtools/lib/e11y/devtools.rb +13 -0
- data/gems/e11y-devtools/spec/e11y/devtools/mcp/tools_spec.rb +107 -0
- data/gems/e11y-devtools/spec/e11y/devtools/overlay/controller_spec.rb +58 -0
- data/gems/e11y-devtools/spec/e11y/devtools/overlay/middleware_spec.rb +46 -0
- data/gems/e11y-devtools/spec/e11y/devtools/tui/app_spec.rb +85 -0
- data/gems/e11y-devtools/spec/e11y/devtools/tui/grouping_spec.rb +64 -0
- data/gems/e11y-devtools/spec/spec_helper.rb +5 -0
- data/gems/e11y-devtools/spec/tui/widgets/event_list_spec.rb +44 -0
- data/gems/e11y-devtools/spec/tui/widgets/interaction_list_spec.rb +62 -0
- data/lib/e11y/adapters/audit_encrypted.rb +53 -11
- data/lib/e11y/adapters/base.rb +33 -34
- data/lib/e11y/adapters/dev_log/file_store.rb +143 -0
- data/lib/e11y/adapters/dev_log/query.rb +219 -0
- data/lib/e11y/adapters/dev_log.rb +118 -0
- data/lib/e11y/adapters/file.rb +3 -6
- data/lib/e11y/adapters/in_memory.rb +52 -5
- data/lib/e11y/adapters/in_memory_test.rb +29 -0
- data/lib/e11y/adapters/loki.rb +58 -23
- data/lib/e11y/adapters/null.rb +82 -0
- data/lib/e11y/adapters/opentelemetry_collector.rb +183 -0
- data/lib/e11y/adapters/otel_logs.rb +136 -23
- data/lib/e11y/adapters/sentry.rb +4 -7
- data/lib/e11y/adapters/stdout.rb +73 -7
- data/lib/e11y/adapters/yabeda.rb +153 -29
- data/lib/e11y/buffers/adaptive_buffer.rb +3 -17
- data/lib/e11y/buffers/{request_scoped_buffer.rb → ephemeral_buffer.rb} +72 -58
- data/lib/e11y/buffers/ring_buffer.rb +3 -16
- data/lib/e11y/configuration.rb +272 -0
- data/lib/e11y/console.rb +10 -17
- data/lib/e11y/current.rb +53 -1
- data/lib/e11y/debug/pipeline_inspector.rb +96 -0
- data/lib/e11y/documentation/generator.rb +48 -0
- data/lib/e11y/event/base.rb +176 -82
- data/lib/e11y/event/value_sampling_config.rb +1 -5
- data/lib/e11y/events/rails/database/query.rb +1 -4
- data/lib/e11y/events/rails/job/failed.rb +2 -0
- data/lib/e11y/instruments/active_job.rb +46 -12
- data/lib/e11y/instruments/rails_instrumentation.rb +49 -24
- data/lib/e11y/instruments/sidekiq.rb +137 -31
- data/lib/e11y/linters/base.rb +11 -0
- data/lib/e11y/linters/pii/pii_declaration_linter.rb +120 -0
- data/lib/e11y/linters/slo/config_consistency_linter.rb +76 -0
- data/lib/e11y/linters/slo/explicit_declaration_linter.rb +36 -0
- data/lib/e11y/linters/slo/slo_status_from_linter.rb +41 -0
- data/lib/e11y/logger/bridge.rb +26 -7
- data/lib/e11y/metrics/cardinality_protection.rb +10 -15
- data/lib/e11y/metrics/cardinality_tracker.rb +16 -6
- data/lib/e11y/metrics/registry.rb +3 -5
- data/lib/e11y/metrics/test_backend.rb +62 -0
- data/lib/e11y/metrics.rb +56 -10
- data/lib/e11y/middleware/adapter_resolver.rb +40 -0
- data/lib/e11y/middleware/audit_signing.rb +43 -6
- data/lib/e11y/middleware/baggage_protection.rb +75 -0
- data/lib/e11y/middleware/dev_log_source.rb +24 -0
- data/lib/e11y/middleware/event_slo.rb +23 -9
- data/lib/e11y/middleware/otel_span.rb +23 -0
- data/lib/e11y/middleware/pii_filter.rb +104 -75
- data/lib/e11y/middleware/rate_limiting.rb +54 -27
- data/lib/e11y/middleware/request.rb +70 -23
- data/lib/e11y/middleware/routing.rb +78 -21
- data/lib/e11y/middleware/sampling.rb +66 -17
- data/lib/e11y/middleware/self_monitoring_emit.rb +39 -0
- data/lib/e11y/middleware/trace_context.rb +45 -10
- data/lib/e11y/middleware/track_latency.rb +34 -0
- data/lib/e11y/middleware/validation.rb +7 -16
- data/lib/e11y/middleware/versioning.rb +26 -22
- data/lib/e11y/opentelemetry/semantic_conventions.rb +109 -0
- data/lib/e11y/opentelemetry/span_creator.rb +142 -0
- data/lib/e11y/pii/patterns.rb +12 -1
- data/lib/e11y/pipeline/builder.rb +1 -1
- data/lib/e11y/presets/audit_event.rb +13 -2
- data/lib/e11y/railtie.rb +52 -15
- data/lib/e11y/registry.rb +306 -0
- data/lib/e11y/reliability/circuit_breaker.rb +19 -21
- data/lib/e11y/reliability/dlq/base.rb +71 -0
- data/lib/e11y/reliability/dlq/file_adapter.rb +301 -0
- data/lib/e11y/reliability/dlq/file_storage.rb +63 -34
- data/lib/e11y/reliability/dlq/filter.rb +37 -54
- data/lib/e11y/reliability/retry_handler.rb +26 -29
- data/lib/e11y/reliability/retry_rate_limiter.rb +3 -11
- data/lib/e11y/sampling/error_spike_detector.rb +0 -2
- data/lib/e11y/sampling/load_monitor.rb +5 -9
- data/lib/e11y/sampling/stratified_tracker.rb +18 -0
- data/lib/e11y/self_monitoring/buffer_monitor.rb +2 -0
- data/lib/e11y/self_monitoring/performance_monitor.rb +19 -61
- data/lib/e11y/self_monitoring/reliability_monitor.rb +4 -74
- data/lib/e11y/slo/config_loader.rb +40 -0
- data/lib/e11y/slo/config_validator.rb +58 -0
- data/lib/e11y/slo/dashboard_generator.rb +122 -0
- data/lib/e11y/slo/event_driven.rb +8 -0
- data/lib/e11y/slo/tracker.rb +31 -4
- data/lib/e11y/testing/have_tracked_event_matcher.rb +190 -0
- data/lib/e11y/testing/rspec_matchers.rb +21 -0
- data/lib/e11y/testing/snapshot_matcher.rb +86 -0
- data/lib/e11y/trace_context/sampler.rb +35 -0
- data/lib/e11y/tracing/faraday_middleware.rb +31 -0
- data/lib/e11y/tracing/net_http_patch.rb +33 -0
- data/lib/e11y/tracing/propagator.rb +116 -0
- data/lib/e11y/tracing.rb +47 -0
- data/lib/e11y/version.rb +1 -1
- data/lib/e11y/versioning/version_extractor.rb +32 -0
- data/lib/e11y.rb +141 -265
- data/lib/generators/e11y/event/event_generator.rb +22 -0
- data/lib/generators/e11y/event/templates/event.rb.tt +16 -0
- data/lib/generators/e11y/grafana_dashboard/grafana_dashboard_generator.rb +30 -0
- data/lib/generators/e11y/grafana_dashboard/templates/e11y_dashboard.json +81 -0
- data/lib/generators/e11y/install/install_generator.rb +34 -0
- data/lib/generators/e11y/install/templates/e11y.rb +239 -0
- data/lib/generators/e11y/prometheus_alerts/prometheus_alerts_generator.rb +29 -0
- data/lib/generators/e11y/prometheus_alerts/templates/e11y_alerts.yml +28 -0
- data/lib/tasks/e11y_docs.rake +30 -0
- data/lib/tasks/e11y_events.rake +71 -0
- data/lib/tasks/e11y_lint.rake +91 -0
- data/lib/tasks/e11y_slo.rake +29 -0
- metadata +129 -39
- data/docs/ADR-010-developer-experience.md +0 -2166
- data/docs/API-REFERENCE-L28.md +0 -914
- data/docs/COMPREHENSIVE-CONFIGURATION.md +0 -2366
- data/docs/CONTRIBUTING.md +0 -312
- data/docs/IMPLEMENTATION_NOTES.md +0 -2804
- data/docs/IMPLEMENTATION_PLAN.md +0 -1971
- data/docs/IMPLEMENTATION_PLAN_ARCHITECTURE.md +0 -586
- data/docs/PLAN.md +0 -148
- data/docs/README.md +0 -296
- data/docs/design/00-memory-optimization.md +0 -593
- data/docs/guides/MIGRATION-L27-L28.md +0 -692
- data/docs/guides/PERFORMANCE-BENCHMARKS.md +0 -434
- data/docs/guides/README.md +0 -44
- data/docs/use_cases/UC-003-pattern-based-metrics.md +0 -1627
- data/lib/e11y/adapters/registry.rb +0 -141
- /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
|
-
#
|
|
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
|
-
# @
|
|
62
|
-
|
|
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
|
-
#
|
|
81
|
+
# Trace an event through the pipeline (debug utility).
|
|
82
|
+
# Delegates to PipelineInspector.trace_event. Loads the inspector on demand.
|
|
72
83
|
#
|
|
73
|
-
# @
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
122
|
+
return @logger if defined?(@logger) && !@logger.nil?
|
|
125
123
|
|
|
126
|
-
|
|
127
|
-
@
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
# Get adapters for given severity
|
|
145
|
+
# Gracefully shut down E11y, flushing pending events.
|
|
158
146
|
#
|
|
159
|
-
# @param
|
|
160
|
-
# @return [
|
|
161
|
-
def
|
|
162
|
-
|
|
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
|
-
#
|
|
178
|
+
# Current size of the request-scoped debug buffer for this thread.
|
|
166
179
|
#
|
|
167
|
-
# @return [
|
|
168
|
-
def
|
|
169
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
# Default adapter mapping (convention-based)
|
|
186
|
+
# Circuit breaker states for all adapters.
|
|
175
187
|
#
|
|
176
|
-
#
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
#
|
|
181
|
-
|
|
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
|
-
#
|
|
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
|
-
# @
|
|
200
|
-
#
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
#
|
|
237
|
-
#
|
|
213
|
+
# Reset configuration (primarily for testing)
|
|
214
|
+
#
|
|
238
215
|
# @return [void]
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
#
|
|
338
|
-
#
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|