e11y 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +4 -0
- data/.rubocop.yml +69 -0
- data/CHANGELOG.md +26 -0
- data/CODE_OF_CONDUCT.md +64 -0
- data/LICENSE.txt +21 -0
- data/README.md +179 -0
- data/Rakefile +37 -0
- data/benchmarks/run_all.rb +33 -0
- data/config/README.md +83 -0
- data/config/loki-local-config.yaml +35 -0
- data/config/prometheus.yml +15 -0
- data/docker-compose.yml +78 -0
- data/docs/00-ICP-AND-TIMELINE.md +483 -0
- data/docs/01-SCALE-REQUIREMENTS.md +858 -0
- data/docs/ADR-001-architecture.md +2617 -0
- data/docs/ADR-002-metrics-yabeda.md +1395 -0
- data/docs/ADR-003-slo-observability.md +3337 -0
- data/docs/ADR-004-adapter-architecture.md +2385 -0
- data/docs/ADR-005-tracing-context.md +1372 -0
- data/docs/ADR-006-security-compliance.md +4143 -0
- data/docs/ADR-007-opentelemetry-integration.md +1385 -0
- data/docs/ADR-008-rails-integration.md +1911 -0
- data/docs/ADR-009-cost-optimization.md +2993 -0
- data/docs/ADR-010-developer-experience.md +2166 -0
- data/docs/ADR-011-testing-strategy.md +1836 -0
- data/docs/ADR-012-event-evolution.md +958 -0
- data/docs/ADR-013-reliability-error-handling.md +2750 -0
- data/docs/ADR-014-event-driven-slo.md +1533 -0
- data/docs/ADR-015-middleware-order.md +1061 -0
- data/docs/ADR-016-self-monitoring-slo.md +1234 -0
- data/docs/API-REFERENCE-L28.md +914 -0
- data/docs/COMPREHENSIVE-CONFIGURATION.md +2366 -0
- data/docs/IMPLEMENTATION_NOTES.md +2804 -0
- data/docs/IMPLEMENTATION_PLAN.md +1971 -0
- data/docs/IMPLEMENTATION_PLAN_ARCHITECTURE.md +586 -0
- data/docs/PLAN.md +148 -0
- data/docs/QUICK-START.md +934 -0
- data/docs/README.md +296 -0
- data/docs/design/00-memory-optimization.md +593 -0
- data/docs/guides/MIGRATION-L27-L28.md +692 -0
- data/docs/guides/PERFORMANCE-BENCHMARKS.md +434 -0
- data/docs/guides/README.md +44 -0
- data/docs/prd/01-overview-vision.md +440 -0
- data/docs/use_cases/README.md +119 -0
- data/docs/use_cases/UC-001-request-scoped-debug-buffering.md +813 -0
- data/docs/use_cases/UC-002-business-event-tracking.md +1953 -0
- data/docs/use_cases/UC-003-pattern-based-metrics.md +1627 -0
- data/docs/use_cases/UC-004-zero-config-slo-tracking.md +728 -0
- data/docs/use_cases/UC-005-sentry-integration.md +759 -0
- data/docs/use_cases/UC-006-trace-context-management.md +905 -0
- data/docs/use_cases/UC-007-pii-filtering.md +2648 -0
- data/docs/use_cases/UC-008-opentelemetry-integration.md +1153 -0
- data/docs/use_cases/UC-009-multi-service-tracing.md +1043 -0
- data/docs/use_cases/UC-010-background-job-tracking.md +1018 -0
- data/docs/use_cases/UC-011-rate-limiting.md +1906 -0
- data/docs/use_cases/UC-012-audit-trail.md +2301 -0
- data/docs/use_cases/UC-013-high-cardinality-protection.md +2127 -0
- data/docs/use_cases/UC-014-adaptive-sampling.md +1940 -0
- data/docs/use_cases/UC-015-cost-optimization.md +735 -0
- data/docs/use_cases/UC-016-rails-logger-migration.md +785 -0
- data/docs/use_cases/UC-017-local-development.md +867 -0
- data/docs/use_cases/UC-018-testing-events.md +1081 -0
- data/docs/use_cases/UC-019-tiered-storage-migration.md +562 -0
- data/docs/use_cases/UC-020-event-versioning.md +708 -0
- data/docs/use_cases/UC-021-error-handling-retry-dlq.md +956 -0
- data/docs/use_cases/UC-022-event-registry.md +648 -0
- data/docs/use_cases/backlog.md +226 -0
- data/e11y.gemspec +76 -0
- data/lib/e11y/adapters/adaptive_batcher.rb +207 -0
- data/lib/e11y/adapters/audit_encrypted.rb +239 -0
- data/lib/e11y/adapters/base.rb +580 -0
- data/lib/e11y/adapters/file.rb +224 -0
- data/lib/e11y/adapters/in_memory.rb +216 -0
- data/lib/e11y/adapters/loki.rb +333 -0
- data/lib/e11y/adapters/otel_logs.rb +203 -0
- data/lib/e11y/adapters/registry.rb +141 -0
- data/lib/e11y/adapters/sentry.rb +230 -0
- data/lib/e11y/adapters/stdout.rb +108 -0
- data/lib/e11y/adapters/yabeda.rb +370 -0
- data/lib/e11y/buffers/adaptive_buffer.rb +339 -0
- data/lib/e11y/buffers/base_buffer.rb +40 -0
- data/lib/e11y/buffers/request_scoped_buffer.rb +246 -0
- data/lib/e11y/buffers/ring_buffer.rb +267 -0
- data/lib/e11y/buffers.rb +14 -0
- data/lib/e11y/console.rb +122 -0
- data/lib/e11y/current.rb +48 -0
- data/lib/e11y/event/base.rb +894 -0
- data/lib/e11y/event/value_sampling_config.rb +84 -0
- data/lib/e11y/events/base_audit_event.rb +43 -0
- data/lib/e11y/events/base_payment_event.rb +33 -0
- data/lib/e11y/events/rails/cache/delete.rb +21 -0
- data/lib/e11y/events/rails/cache/read.rb +23 -0
- data/lib/e11y/events/rails/cache/write.rb +22 -0
- data/lib/e11y/events/rails/database/query.rb +45 -0
- data/lib/e11y/events/rails/http/redirect.rb +21 -0
- data/lib/e11y/events/rails/http/request.rb +26 -0
- data/lib/e11y/events/rails/http/send_file.rb +21 -0
- data/lib/e11y/events/rails/http/start_processing.rb +26 -0
- data/lib/e11y/events/rails/job/completed.rb +22 -0
- data/lib/e11y/events/rails/job/enqueued.rb +22 -0
- data/lib/e11y/events/rails/job/failed.rb +22 -0
- data/lib/e11y/events/rails/job/scheduled.rb +23 -0
- data/lib/e11y/events/rails/job/started.rb +22 -0
- data/lib/e11y/events/rails/log.rb +56 -0
- data/lib/e11y/events/rails/view/render.rb +23 -0
- data/lib/e11y/events.rb +18 -0
- data/lib/e11y/instruments/active_job.rb +201 -0
- data/lib/e11y/instruments/rails_instrumentation.rb +141 -0
- data/lib/e11y/instruments/sidekiq.rb +175 -0
- data/lib/e11y/logger/bridge.rb +205 -0
- data/lib/e11y/metrics/cardinality_protection.rb +172 -0
- data/lib/e11y/metrics/cardinality_tracker.rb +134 -0
- data/lib/e11y/metrics/registry.rb +234 -0
- data/lib/e11y/metrics/relabeling.rb +226 -0
- data/lib/e11y/metrics.rb +102 -0
- data/lib/e11y/middleware/audit_signing.rb +174 -0
- data/lib/e11y/middleware/base.rb +140 -0
- data/lib/e11y/middleware/event_slo.rb +167 -0
- data/lib/e11y/middleware/pii_filter.rb +266 -0
- data/lib/e11y/middleware/pii_filtering.rb +280 -0
- data/lib/e11y/middleware/rate_limiting.rb +214 -0
- data/lib/e11y/middleware/request.rb +163 -0
- data/lib/e11y/middleware/routing.rb +157 -0
- data/lib/e11y/middleware/sampling.rb +254 -0
- data/lib/e11y/middleware/slo.rb +168 -0
- data/lib/e11y/middleware/trace_context.rb +131 -0
- data/lib/e11y/middleware/validation.rb +118 -0
- data/lib/e11y/middleware/versioning.rb +132 -0
- data/lib/e11y/middleware.rb +12 -0
- data/lib/e11y/pii/patterns.rb +90 -0
- data/lib/e11y/pii.rb +13 -0
- data/lib/e11y/pipeline/builder.rb +155 -0
- data/lib/e11y/pipeline/zone_validator.rb +110 -0
- data/lib/e11y/pipeline.rb +12 -0
- data/lib/e11y/presets/audit_event.rb +65 -0
- data/lib/e11y/presets/debug_event.rb +34 -0
- data/lib/e11y/presets/high_value_event.rb +51 -0
- data/lib/e11y/presets.rb +19 -0
- data/lib/e11y/railtie.rb +138 -0
- data/lib/e11y/reliability/circuit_breaker.rb +216 -0
- data/lib/e11y/reliability/dlq/file_storage.rb +277 -0
- data/lib/e11y/reliability/dlq/filter.rb +117 -0
- data/lib/e11y/reliability/retry_handler.rb +207 -0
- data/lib/e11y/reliability/retry_rate_limiter.rb +117 -0
- data/lib/e11y/sampling/error_spike_detector.rb +225 -0
- data/lib/e11y/sampling/load_monitor.rb +161 -0
- data/lib/e11y/sampling/stratified_tracker.rb +92 -0
- data/lib/e11y/sampling/value_extractor.rb +82 -0
- data/lib/e11y/self_monitoring/buffer_monitor.rb +79 -0
- data/lib/e11y/self_monitoring/performance_monitor.rb +97 -0
- data/lib/e11y/self_monitoring/reliability_monitor.rb +146 -0
- data/lib/e11y/slo/event_driven.rb +150 -0
- data/lib/e11y/slo/tracker.rb +119 -0
- data/lib/e11y/version.rb +9 -0
- data/lib/e11y.rb +283 -0
- metadata +452 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "e11y/metrics"
|
|
4
|
+
|
|
5
|
+
module E11y
|
|
6
|
+
module SLO
|
|
7
|
+
# Event-Driven SLO for business logic reliability (ADR-014).
|
|
8
|
+
#
|
|
9
|
+
# Provides DSL for Event classes to opt-in to SLO tracking, auto-calculate
|
|
10
|
+
# `slo_status` from payload, and emit metrics for custom business SLO.
|
|
11
|
+
#
|
|
12
|
+
# **Key Features:**
|
|
13
|
+
# - Explicit opt-in via `slo { enabled true }` in Event class
|
|
14
|
+
# - Auto-calculation of `slo_status` from payload (e.g., status == 'completed' → 'success')
|
|
15
|
+
# - Explicit override: `track(status: 'completed', slo_status: 'failure')`
|
|
16
|
+
# - Metrics export: `event_result_total{slo_status="success|failure"}`
|
|
17
|
+
# - Custom SLO configuration in `slo.yml` (optional)
|
|
18
|
+
#
|
|
19
|
+
# **ADR References:**
|
|
20
|
+
# - ADR-014 §3 (Event SLO DSL)
|
|
21
|
+
# - ADR-014 §4 (SLO Status Calculation)
|
|
22
|
+
# - ADR-014 §6 (Metrics Export)
|
|
23
|
+
#
|
|
24
|
+
# **Use Case:** UC-014 (Event-Driven SLO)
|
|
25
|
+
#
|
|
26
|
+
# @example Event with SLO enabled
|
|
27
|
+
# module Events
|
|
28
|
+
# class PaymentProcessed < E11y::Event::Base
|
|
29
|
+
# schema do
|
|
30
|
+
# required(:payment_id).filled(:string)
|
|
31
|
+
# required(:status).filled(:string)
|
|
32
|
+
# optional(:slo_status).filled(:string) # Explicit override
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
# slo do
|
|
36
|
+
# enabled true
|
|
37
|
+
# slo_status_from do |payload|
|
|
38
|
+
# return payload[:slo_status] if payload[:slo_status]
|
|
39
|
+
# case payload[:status]
|
|
40
|
+
# when 'completed' then 'success'
|
|
41
|
+
# when 'failed' then 'failure'
|
|
42
|
+
# else nil # Not counted in SLO
|
|
43
|
+
# end
|
|
44
|
+
# end
|
|
45
|
+
# end
|
|
46
|
+
# end
|
|
47
|
+
# end
|
|
48
|
+
#
|
|
49
|
+
# @example Tracking with auto-calculated slo_status
|
|
50
|
+
# Events::PaymentProcessed.track(
|
|
51
|
+
# payment_id: 'p123',
|
|
52
|
+
# status: 'completed' # → slo_status = 'success'
|
|
53
|
+
# )
|
|
54
|
+
#
|
|
55
|
+
# @example Tracking with explicit override
|
|
56
|
+
# Events::PaymentProcessed.track(
|
|
57
|
+
# payment_id: 'p456',
|
|
58
|
+
# status: 'completed',
|
|
59
|
+
# slo_status: 'failure' # Explicit override (e.g., fraud detected)
|
|
60
|
+
# )
|
|
61
|
+
#
|
|
62
|
+
# @see ADR-014 for complete architecture
|
|
63
|
+
module EventDriven
|
|
64
|
+
# SLO configuration for an Event class.
|
|
65
|
+
class SLOConfig
|
|
66
|
+
attr_reader :slo_status_proc, :contributes_to_value, :group_by_field
|
|
67
|
+
|
|
68
|
+
def initialize
|
|
69
|
+
@enabled = false
|
|
70
|
+
@slo_status_proc = nil
|
|
71
|
+
@contributes_to_value = nil
|
|
72
|
+
@group_by_field = nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# DSL method: Enable or disable SLO tracking.
|
|
76
|
+
#
|
|
77
|
+
# @param value [Boolean] true to enable, false to disable
|
|
78
|
+
def enabled(value = nil)
|
|
79
|
+
return @enabled if value.nil?
|
|
80
|
+
|
|
81
|
+
@enabled = value
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Check if SLO is enabled.
|
|
85
|
+
#
|
|
86
|
+
# @return [Boolean]
|
|
87
|
+
def enabled?
|
|
88
|
+
@enabled
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# DSL method: Define how to calculate slo_status from payload.
|
|
92
|
+
#
|
|
93
|
+
# @yieldparam payload [Hash] Event payload
|
|
94
|
+
# @yieldreturn [String, nil] 'success', 'failure', or nil (not counted)
|
|
95
|
+
def slo_status_from(&block)
|
|
96
|
+
@slo_status_proc = block
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# DSL method: Define which custom SLO this event contributes to.
|
|
100
|
+
#
|
|
101
|
+
# @param slo_name [String] Name of custom SLO (from slo.yml)
|
|
102
|
+
def contributes_to(slo_name = nil)
|
|
103
|
+
return @contributes_to_value if slo_name.nil?
|
|
104
|
+
|
|
105
|
+
@contributes_to_value = slo_name
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# DSL method: Group SLO metrics by a specific field.
|
|
109
|
+
#
|
|
110
|
+
# @param field [Symbol] Field name to group by (e.g., :payment_method)
|
|
111
|
+
def group_by(field = nil)
|
|
112
|
+
return @group_by_field if field.nil?
|
|
113
|
+
|
|
114
|
+
@group_by_field = field
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# DSL methods for Event classes (extend with this module).
|
|
119
|
+
module DSL
|
|
120
|
+
# DSL method: Configure SLO for this Event class.
|
|
121
|
+
#
|
|
122
|
+
# @yieldparam config [SLOConfig] SLO configuration object
|
|
123
|
+
# @return [void]
|
|
124
|
+
#
|
|
125
|
+
# @example Enable SLO
|
|
126
|
+
# slo do
|
|
127
|
+
# enabled true
|
|
128
|
+
# slo_status_from { |payload| payload[:status] == 'success' ? 'success' : 'failure' }
|
|
129
|
+
# end
|
|
130
|
+
#
|
|
131
|
+
# @example Disable SLO (explicit)
|
|
132
|
+
# slo do
|
|
133
|
+
# enabled false
|
|
134
|
+
# end
|
|
135
|
+
def slo(&)
|
|
136
|
+
@slo_config ||= SLOConfig.new
|
|
137
|
+
@slo_config.instance_eval(&) if block_given?
|
|
138
|
+
@slo_config
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Get SLO configuration for this Event class.
|
|
142
|
+
#
|
|
143
|
+
# @return [SLOConfig, nil] SLO config or nil if not defined
|
|
144
|
+
def slo_config
|
|
145
|
+
@slo_config
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "e11y/metrics"
|
|
4
|
+
|
|
5
|
+
module E11y
|
|
6
|
+
module SLO
|
|
7
|
+
# Zero-Config SLO Tracker for HTTP requests and background jobs.
|
|
8
|
+
#
|
|
9
|
+
# Automatically tracks Service Level Indicators (SLIs):
|
|
10
|
+
# - HTTP request success rate (availability)
|
|
11
|
+
# - HTTP request latency (p95, p99)
|
|
12
|
+
# - Background job success rate
|
|
13
|
+
# - Background job duration
|
|
14
|
+
#
|
|
15
|
+
# @see UC-004 (Zero-Config SLO Tracking)
|
|
16
|
+
# @see ADR-003 §3 (Multi-Level SLO Strategy)
|
|
17
|
+
#
|
|
18
|
+
# @example Enable SLO tracking
|
|
19
|
+
# E11y.configure do |config|
|
|
20
|
+
# config.slo_tracking.enabled = true
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# @example Track HTTP request
|
|
24
|
+
# E11y::SLO::Tracker.track_http_request(
|
|
25
|
+
# controller: 'OrdersController',
|
|
26
|
+
# action: 'create',
|
|
27
|
+
# status: 200,
|
|
28
|
+
# duration_ms: 42.5
|
|
29
|
+
# )
|
|
30
|
+
#
|
|
31
|
+
# @note C11 Resolution (Sampling Correction): Not yet implemented.
|
|
32
|
+
# Requires Phase 2.8 (Stratified Sampling) for accurate SLO with sampling.
|
|
33
|
+
module Tracker
|
|
34
|
+
class << self
|
|
35
|
+
# Track HTTP request for SLO metrics.
|
|
36
|
+
#
|
|
37
|
+
# @param controller [String] Controller name
|
|
38
|
+
# @param action [String] Action name
|
|
39
|
+
# @param status [Integer] HTTP status code
|
|
40
|
+
# @param duration_ms [Numeric] Request duration in milliseconds
|
|
41
|
+
# @return [void]
|
|
42
|
+
def track_http_request(controller:, action:, status:, duration_ms:)
|
|
43
|
+
return unless enabled?
|
|
44
|
+
|
|
45
|
+
labels = {
|
|
46
|
+
controller: controller,
|
|
47
|
+
action: action,
|
|
48
|
+
status: normalize_status(status)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# Track request count
|
|
52
|
+
E11y::Metrics.increment(:slo_http_requests_total, labels)
|
|
53
|
+
|
|
54
|
+
# Track request duration
|
|
55
|
+
E11y::Metrics.histogram(
|
|
56
|
+
:slo_http_request_duration_seconds,
|
|
57
|
+
duration_ms / 1000.0,
|
|
58
|
+
labels.except(:status),
|
|
59
|
+
buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Track background job for SLO metrics.
|
|
64
|
+
#
|
|
65
|
+
# @param job_class [String] Job class name
|
|
66
|
+
# @param status [Symbol] Job status (:success or :failed)
|
|
67
|
+
# @param duration_ms [Numeric] Job duration in milliseconds
|
|
68
|
+
# @param queue [String, nil] Queue name (optional)
|
|
69
|
+
# @return [void]
|
|
70
|
+
def track_background_job(job_class:, status:, duration_ms:, queue: nil)
|
|
71
|
+
return unless enabled?
|
|
72
|
+
|
|
73
|
+
labels = {
|
|
74
|
+
job_class: job_class,
|
|
75
|
+
status: status.to_s
|
|
76
|
+
}
|
|
77
|
+
labels[:queue] = queue if queue
|
|
78
|
+
|
|
79
|
+
# Track job count
|
|
80
|
+
E11y::Metrics.increment(:slo_background_jobs_total, labels)
|
|
81
|
+
|
|
82
|
+
# Track job duration (only for successful jobs)
|
|
83
|
+
return unless status == :success
|
|
84
|
+
|
|
85
|
+
E11y::Metrics.histogram(
|
|
86
|
+
:slo_background_job_duration_seconds,
|
|
87
|
+
duration_ms / 1000.0,
|
|
88
|
+
labels.except(:status),
|
|
89
|
+
buckets: [0.1, 0.5, 1, 5, 10, 30, 60, 300, 600]
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Check if SLO tracking is enabled.
|
|
94
|
+
#
|
|
95
|
+
# @return [Boolean] true if enabled
|
|
96
|
+
def enabled?
|
|
97
|
+
E11y.config.respond_to?(:slo_tracking) && E11y.config.slo_tracking&.enabled
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Normalize HTTP status code to category (2xx, 3xx, 4xx, 5xx).
|
|
101
|
+
#
|
|
102
|
+
# @param status [Integer] HTTP status code
|
|
103
|
+
# @return [String] Status category
|
|
104
|
+
# @api private
|
|
105
|
+
def normalize_status(status)
|
|
106
|
+
case status
|
|
107
|
+
when 200..299 then "2xx"
|
|
108
|
+
when 300..399 then "3xx"
|
|
109
|
+
when 400..499 then "4xx"
|
|
110
|
+
when 500..599 then "5xx"
|
|
111
|
+
else "unknown"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
private :normalize_status
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
data/lib/e11y/version.rb
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module E11y
|
|
4
|
+
# Semantic versioning: MAJOR.MINOR.PATCH
|
|
5
|
+
# - MAJOR: Breaking changes (incompatible API changes)
|
|
6
|
+
# - MINOR: New features (backwards-compatible)
|
|
7
|
+
# - PATCH: Bug fixes (backwards-compatible)
|
|
8
|
+
VERSION = "0.1.0"
|
|
9
|
+
end
|
data/lib/e11y.rb
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "zeitwerk"
|
|
4
|
+
|
|
5
|
+
# Zeitwerk autoloader setup
|
|
6
|
+
loader = Zeitwerk::Loader.for_gem
|
|
7
|
+
# Configure inflector for acronyms
|
|
8
|
+
loader.inflector.inflect(
|
|
9
|
+
"pii" => "PII",
|
|
10
|
+
"pii_filter" => "PIIFilter"
|
|
11
|
+
)
|
|
12
|
+
loader.setup
|
|
13
|
+
|
|
14
|
+
# E11y - Event-Driven Observability for Ruby on Rails
|
|
15
|
+
#
|
|
16
|
+
# @example Basic usage
|
|
17
|
+
# E11y.configure do |config|
|
|
18
|
+
# config.adapters = [:loki, :sentry]
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# E11y.track(Events::UserSignup.new(user_id: 123))
|
|
22
|
+
#
|
|
23
|
+
# @see https://e11y.dev Documentation
|
|
24
|
+
module E11y
|
|
25
|
+
class Error < StandardError; end
|
|
26
|
+
class ValidationError < Error; end
|
|
27
|
+
class ZoneViolationError < Error; end
|
|
28
|
+
class InvalidPipelineError < Error; end
|
|
29
|
+
|
|
30
|
+
class << self
|
|
31
|
+
# Configure E11y
|
|
32
|
+
#
|
|
33
|
+
# @yield [Configuration] configuration object
|
|
34
|
+
# @return [void]
|
|
35
|
+
#
|
|
36
|
+
# @example
|
|
37
|
+
# E11y.configure do |config|
|
|
38
|
+
# config.adapters = [:loki, :stdout]
|
|
39
|
+
# config.log_level = :debug
|
|
40
|
+
# end
|
|
41
|
+
def configure
|
|
42
|
+
yield configuration if block_given?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Get current configuration
|
|
46
|
+
#
|
|
47
|
+
# @return [Configuration] current configuration instance
|
|
48
|
+
def configuration
|
|
49
|
+
@configuration ||= Configuration.new
|
|
50
|
+
end
|
|
51
|
+
alias config configuration
|
|
52
|
+
|
|
53
|
+
# Track an event
|
|
54
|
+
#
|
|
55
|
+
# @param event [Event] event instance to track
|
|
56
|
+
# @return [void]
|
|
57
|
+
#
|
|
58
|
+
# @example
|
|
59
|
+
# E11y.track(Events::UserSignup.new(user_id: 123))
|
|
60
|
+
def track(event)
|
|
61
|
+
# TODO: Implement in Phase 1
|
|
62
|
+
raise NotImplementedError, "E11y.track will be implemented in Phase 1"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Get logger instance
|
|
66
|
+
#
|
|
67
|
+
# @return [Logger] logger instance
|
|
68
|
+
def logger
|
|
69
|
+
require "logger"
|
|
70
|
+
@logger ||= ::Logger.new($stdout)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Reset configuration (primarily for testing)
|
|
74
|
+
#
|
|
75
|
+
# @return [void]
|
|
76
|
+
# @api private
|
|
77
|
+
def reset!
|
|
78
|
+
@configuration = nil
|
|
79
|
+
@logger = nil
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Configuration class for E11y
|
|
84
|
+
#
|
|
85
|
+
# Adapters are referenced by name (e.g., :logs, :errors_tracker).
|
|
86
|
+
# The actual implementation (Loki, Sentry, etc.) is configured separately.
|
|
87
|
+
#
|
|
88
|
+
# @example Configure adapters
|
|
89
|
+
# E11y.configure do |config|
|
|
90
|
+
# # Register adapter instances
|
|
91
|
+
# config.adapters[:logs] = E11y::Adapters::Loki.new(url: "...")
|
|
92
|
+
# config.adapters[:errors_tracker] = E11y::Adapters::Sentry.new(dsn: "...")
|
|
93
|
+
# end
|
|
94
|
+
#
|
|
95
|
+
# @example Configure severity => adapter mapping
|
|
96
|
+
# E11y.configure do |config|
|
|
97
|
+
# config.adapter_mapping[:error] = [:logs, :errors_tracker]
|
|
98
|
+
# config.adapter_mapping[:info] = [:logs]
|
|
99
|
+
# end
|
|
100
|
+
#
|
|
101
|
+
# @example Configure middleware pipeline
|
|
102
|
+
# E11y.configure do |config|
|
|
103
|
+
# config.pipeline.use E11y::Middleware::Sampling, default_sample_rate: 0.1
|
|
104
|
+
# end
|
|
105
|
+
class Configuration
|
|
106
|
+
attr_accessor :adapters, :log_level, :enabled, :environment, :service_name
|
|
107
|
+
attr_reader :adapter_mapping, :pipeline, :rails_instrumentation, :logger_bridge, :request_buffer, :error_handling,
|
|
108
|
+
:dlq_storage, :dlq_filter, :rate_limiting, :slo_tracking
|
|
109
|
+
|
|
110
|
+
def initialize
|
|
111
|
+
@adapters = {} # Hash of adapter_name => adapter_instance
|
|
112
|
+
@log_level = :info
|
|
113
|
+
@adapter_mapping = default_adapter_mapping
|
|
114
|
+
@pipeline = E11y::Pipeline::Builder.new
|
|
115
|
+
@enabled = true
|
|
116
|
+
@environment = nil
|
|
117
|
+
@service_name = nil
|
|
118
|
+
@rails_instrumentation = RailsInstrumentationConfig.new
|
|
119
|
+
@logger_bridge = LoggerBridgeConfig.new
|
|
120
|
+
@request_buffer = RequestBufferConfig.new
|
|
121
|
+
@error_handling = ErrorHandlingConfig.new # ✅ C18 Resolution
|
|
122
|
+
@dlq_storage = nil # Set by user (e.g., DLQ::FileStorage instance)
|
|
123
|
+
@dlq_filter = nil # Set by user (e.g., DLQ::Filter instance)
|
|
124
|
+
@rate_limiting = RateLimitingConfig.new
|
|
125
|
+
@slo_tracking = SLOTrackingConfig.new # ✅ L3.14.1
|
|
126
|
+
configure_default_pipeline
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Get adapters for given severity
|
|
130
|
+
#
|
|
131
|
+
# @param severity [Symbol] Severity level
|
|
132
|
+
# @return [Array<Symbol>] Adapter names (e.g., [:logs, :errors_tracker])
|
|
133
|
+
def adapters_for_severity(severity)
|
|
134
|
+
@adapter_mapping[severity] || @adapter_mapping[:default] || []
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
|
|
139
|
+
# Default adapter mapping (convention-based)
|
|
140
|
+
#
|
|
141
|
+
# Adapter names represent PURPOSE, not implementation:
|
|
142
|
+
# - :logs → centralized logging (implementation: Loki, Elasticsearch, CloudWatch, etc.)
|
|
143
|
+
# - :errors_tracker → error tracking with alerting (implementation: Sentry, Rollbar, Bugsnag, etc.)
|
|
144
|
+
#
|
|
145
|
+
# @return [Hash{Symbol => Array<Symbol>}] Default mapping (severity => adapter names)
|
|
146
|
+
def default_adapter_mapping
|
|
147
|
+
{
|
|
148
|
+
error: %i[logs errors_tracker], # Errors: both logging + alerting
|
|
149
|
+
fatal: %i[logs errors_tracker], # Fatal: both logging + alerting
|
|
150
|
+
default: [:logs] # Others: logging only
|
|
151
|
+
}
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Setup default middleware pipeline
|
|
155
|
+
#
|
|
156
|
+
# Default pipeline order (per ADR-015):
|
|
157
|
+
# 1. TraceContext - Add trace_id, span_id, timestamp (zone: :pre_processing)
|
|
158
|
+
# 2. Validation - Schema validation (zone: :pre_processing)
|
|
159
|
+
# 3. PIIFilter - PII filtering (zone: :security)
|
|
160
|
+
# 4. AuditSigning - Audit event signing (zone: :security)
|
|
161
|
+
# 5. Sampling - Adaptive sampling (zone: :routing)
|
|
162
|
+
# 6. Routing - Buffer routing (zone: :adapters)
|
|
163
|
+
#
|
|
164
|
+
# @return [void]
|
|
165
|
+
# @see ADR-015 Middleware Execution Order
|
|
166
|
+
def configure_default_pipeline
|
|
167
|
+
# Zone: :pre_processing
|
|
168
|
+
@pipeline.use E11y::Middleware::TraceContext
|
|
169
|
+
@pipeline.use E11y::Middleware::Validation
|
|
170
|
+
|
|
171
|
+
# Zone: :security
|
|
172
|
+
@pipeline.use E11y::Middleware::PIIFilter
|
|
173
|
+
@pipeline.use E11y::Middleware::AuditSigning
|
|
174
|
+
|
|
175
|
+
# Zone: :routing
|
|
176
|
+
@pipeline.use E11y::Middleware::Sampling
|
|
177
|
+
|
|
178
|
+
# Zone: :adapters
|
|
179
|
+
@pipeline.use E11y::Middleware::Routing
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Rails Instrumentation configuration
|
|
184
|
+
class RailsInstrumentationConfig
|
|
185
|
+
attr_accessor :enabled, :custom_mappings, :ignore_events
|
|
186
|
+
|
|
187
|
+
def initialize
|
|
188
|
+
@enabled = false # Disabled by default, enabled by Railtie
|
|
189
|
+
@custom_mappings = {}
|
|
190
|
+
@ignore_events = []
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Override event class for specific ASN pattern (Devise-style)
|
|
194
|
+
# @param pattern [String] ActiveSupport::Notifications pattern
|
|
195
|
+
# @param event_class [Class] E11y event class
|
|
196
|
+
# @return [void]
|
|
197
|
+
def event_class_for(pattern, event_class)
|
|
198
|
+
@custom_mappings[pattern] = event_class
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Ignore specific ASN event
|
|
202
|
+
# @param pattern [String] ActiveSupport::Notifications pattern
|
|
203
|
+
# @return [void]
|
|
204
|
+
def ignore_event(pattern)
|
|
205
|
+
@ignore_events << pattern
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Logger Bridge configuration
|
|
210
|
+
class LoggerBridgeConfig
|
|
211
|
+
attr_accessor :enabled, :dual_logging
|
|
212
|
+
|
|
213
|
+
def initialize
|
|
214
|
+
@enabled = false # Opt-in
|
|
215
|
+
@dual_logging = true # Keep writing to original Rails.logger
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Request Buffer configuration
|
|
220
|
+
class RequestBufferConfig
|
|
221
|
+
attr_accessor :enabled
|
|
222
|
+
|
|
223
|
+
def initialize
|
|
224
|
+
@enabled = false # Disabled by default
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Error Handling configuration (C18 Resolution)
|
|
229
|
+
#
|
|
230
|
+
# Controls whether event tracking failures should raise exceptions.
|
|
231
|
+
# Default: true (for web requests - fast feedback)
|
|
232
|
+
# Exception: false (for background jobs - don't fail business logic)
|
|
233
|
+
#
|
|
234
|
+
# @see ADR-013 §3.6 (Event Tracking in Background Jobs)
|
|
235
|
+
class ErrorHandlingConfig
|
|
236
|
+
attr_accessor :fail_on_error
|
|
237
|
+
|
|
238
|
+
def initialize
|
|
239
|
+
@fail_on_error = true # Default: raise errors (fast feedback for web requests)
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Rate Limiting configuration (UC-011, C02 Resolution)
|
|
244
|
+
#
|
|
245
|
+
# Protects adapters from event floods using token bucket algorithm.
|
|
246
|
+
#
|
|
247
|
+
# @see UC-011 (Rate Limiting - DoS Protection)
|
|
248
|
+
# @see ADR-013 §4.6 (C02 Resolution)
|
|
249
|
+
class RateLimitingConfig
|
|
250
|
+
attr_accessor :enabled, :global_limit, :per_event_limit, :window
|
|
251
|
+
|
|
252
|
+
def initialize
|
|
253
|
+
@enabled = false # Opt-in (enable explicitly)
|
|
254
|
+
@global_limit = 10_000 # Max 10K events/sec globally
|
|
255
|
+
@per_event_limit = 1_000 # Max 1K events/sec per event type
|
|
256
|
+
@window = 1.0 # 1 second window
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# SLO Tracking configuration (UC-004, ADR-003)
|
|
261
|
+
#
|
|
262
|
+
# Zero-config SLO tracking for HTTP requests and background jobs.
|
|
263
|
+
# Automatically emits SLO metrics (availability, latency, success rate).
|
|
264
|
+
#
|
|
265
|
+
# @see UC-004 (Zero-Config SLO Tracking)
|
|
266
|
+
# @see ADR-003 (SLO & Observability)
|
|
267
|
+
#
|
|
268
|
+
# @note C11 Resolution (Sampling Correction): Requires Phase 2.8 (Stratified Sampling).
|
|
269
|
+
# Without stratified sampling, SLO metrics may be inaccurate when adaptive sampling is enabled.
|
|
270
|
+
class SLOTrackingConfig
|
|
271
|
+
attr_accessor :enabled
|
|
272
|
+
|
|
273
|
+
def initialize
|
|
274
|
+
@enabled = false # Opt-in (enable explicitly)
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Load Railtie if Rails is present
|
|
280
|
+
require "e11y/railtie" if defined?(Rails::Railtie)
|
|
281
|
+
|
|
282
|
+
# Eager load for production (optional - uncomment if needed)
|
|
283
|
+
# loader.eager_load if ENV["RAILS_ENV"] == "production"
|