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