e11y 0.2.0 → 1.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 +4 -4
- data/.rubocop.yml +130 -10
- data/CHANGELOG.md +80 -1
- data/CLAUDE.md +168 -0
- data/CONTRIBUTING.md +640 -0
- data/README.md +165 -701
- data/RELEASE.md +41 -12
- data/Rakefile +249 -57
- 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 +79 -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} +36 -65
- data/docs/{ADR-002-metrics-yabeda.md → architecture/ADR-002-metrics-yabeda.md} +62 -236
- data/docs/architecture/ADR-003-slo-observability.md +1402 -0
- 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} +182 -743
- 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} +44 -86
- data/docs/{ADR-012-event-evolution.md → architecture/ADR-012-event-evolution.md} +11 -11
- 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} +43 -59
- data/docs/{ADR-016-self-monitoring-slo.md → architecture/ADR-016-self-monitoring-slo.md} +58 -355
- 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/plans/2026-03-20-browser-overlay-svelte.md +281 -0
- 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 +33 -684
- 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 +30 -178
- data/docs/use_cases/UC-010-background-job-tracking.md +24 -91
- 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 +158 -0
- data/gems/e11y-devtools/config/routes.rb +15 -0
- data/gems/e11y-devtools/e11y-devtools.gemspec +25 -0
- data/gems/e11y-devtools/exe/e11y +34 -0
- data/gems/e11y-devtools/frontend/.gitignore +24 -0
- data/gems/e11y-devtools/frontend/README.md +51 -0
- data/gems/e11y-devtools/frontend/index.html +14 -0
- data/gems/e11y-devtools/frontend/package-lock.json +3707 -0
- data/gems/e11y-devtools/frontend/package.json +28 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/events/recent.json +4205 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/interactions.json +194 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/0a2e04027cfa22d014bc22e8b27cd913/events.json +86 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/0e1543af6a630fb3af6b52283154b3e0/events.json +169 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/1838b691faa49564f97db8592ff3978d/events.json +78 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/29f198f6588dacffb687777eb5f8f118/events.json +197 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/34bc3c9c0097de28a7a6f99b90a8e7bc/events.json +194 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/3ba6c20d068ab9cee00e51b180e66444/events.json +184 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/435bfd8f17b9009146a79812d7c3726d/events.json +144 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/4c7676e3fe668e99edb2b94d7d5678a9/events.json +222 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/6daf0d47974bedfc55d5de7004a3ea9f/events.json +194 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/8a81ada42834d15f287bb40010043605/events.json +194 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/8c0a98900edaae105469df8daedccf02/events.json +198 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/8e4f645180f8a7d1dce426b07380466b/events.json +222 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/93db346fa5d44a032605a13b627f4b80/events.json +128 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/98ff6146faf7bd9be8bd03a8275817ba/events.json +223 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/9997ddd0247bc7e25f2ca7a5c415c93d/events.json +197 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/99e35f8ef3baedd798cc4fd085980ad9/events.json +194 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/b4f3095c1909924cbc98889a86c83d6d/events.json +131 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/b54b7fc32b7575a7110de809d11ccda0/events.json +128 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/c0b48033fa06746bcc5886745e053cff/events.json +169 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/c44649ac76701b4558927cd2305ab535/events.json +169 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/d601ae3320057580a39dbdac2edfdf4a/events.json +248 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/e67e724bab422d2b52eeb49635e512e1/events.json +194 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/e6c72765a28f158a8485b35fa63f73da/events.json +194 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/f541b87405c9a54819b18ebe529f6419/events.json +194 -0
- data/gems/e11y-devtools/frontend/scripts/generate_mocks.rb +397 -0
- data/gems/e11y-devtools/frontend/src/App.svelte +827 -0
- data/gems/e11y-devtools/frontend/src/components/Fab.svelte +19 -0
- data/gems/e11y-devtools/frontend/src/components/FilterBar.svelte +38 -0
- data/gems/e11y-devtools/frontend/src/components/FullscreenPanel.svelte +82 -0
- data/gems/e11y-devtools/frontend/src/components/InteractionsTimeline.svelte +264 -0
- data/gems/e11y-devtools/frontend/src/components/RecentHistogram.svelte +354 -0
- data/gems/e11y-devtools/frontend/src/lib/api.ts +37 -0
- data/gems/e11y-devtools/frontend/src/lib/eventIdentity.ts +12 -0
- data/gems/e11y-devtools/frontend/src/lib/format.ts +37 -0
- data/gems/e11y-devtools/frontend/src/lib/listFilter.ts +43 -0
- data/gems/e11y-devtools/frontend/src/lib/recentVolume.ts +80 -0
- data/gems/e11y-devtools/frontend/src/lib/router.ts +12 -0
- data/gems/e11y-devtools/frontend/src/lib/transitions.ts +34 -0
- data/gems/e11y-devtools/frontend/src/lib/viewportOrigin.ts +25 -0
- data/gems/e11y-devtools/frontend/src/main.ts +8 -0
- data/gems/e11y-devtools/frontend/src/overlay-entry.ts +24 -0
- data/gems/e11y-devtools/frontend/src/overlay.css +1080 -0
- data/gems/e11y-devtools/frontend/svelte.config.js +2 -0
- data/gems/e11y-devtools/frontend/test_puppeteer.js +41 -0
- data/gems/e11y-devtools/frontend/test_scale.js +3 -0
- data/gems/e11y-devtools/frontend/tsconfig.app.json +21 -0
- data/gems/e11y-devtools/frontend/tsconfig.json +7 -0
- data/gems/e11y-devtools/frontend/tsconfig.node.json +26 -0
- data/gems/e11y-devtools/frontend/vite.config.ts +36 -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 +20 -0
- data/gems/e11y-devtools/lib/e11y/devtools/overlay/controller.rb +94 -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 +67 -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 +91 -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 +44 -12
- data/lib/e11y/instruments/rails_instrumentation.rb +49 -24
- data/lib/e11y/instruments/sidekiq.rb +135 -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 +4 -4
- data/lib/e11y/presets/audit_event.rb +13 -2
- data/lib/e11y/railtie.rb +52 -14
- 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 +144 -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 +123 -266
- 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 +186 -39
- data/docs/ADR-003-slo-observability.md +0 -3337
- 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
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Rails Integration
|
|
2
|
+
|
|
3
|
+
> Back to [README](../README.md#documentation)
|
|
4
|
+
|
|
5
|
+
E11y integrates with Rails via `E11y::Railtie` (`lib/e11y/railtie.rb`). After `bundle install`, requiring the gem in a Rails app loads the Railtie automatically.
|
|
6
|
+
|
|
7
|
+
## Request middleware
|
|
8
|
+
|
|
9
|
+
`E11y::Middleware::Request` is inserted into the Rack stack (before `Rails::Rack::Logger` when present). It sets trace and request context on `E11y::Current`, optionally starts the **ephemeral (request-scoped) buffer** for debug events, and adds `X-E11y-Trace-Id` / `X-E11y-Span-Id` response headers.
|
|
10
|
+
|
|
11
|
+
## Rails instrumentation (`ActiveSupport::Notifications`)
|
|
12
|
+
|
|
13
|
+
When **`config.rails_instrumentation_enabled = true`**, E11y subscribes to Rails instrumentation and maps notifications to typed events (see `lib/e11y/instruments/rails_instrumentation.rb`).
|
|
14
|
+
|
|
15
|
+
| Area | Event classes (under `E11y::Events::Rails::`) |
|
|
16
|
+
|------|-----------------------------------------------|
|
|
17
|
+
| HTTP | `Http::Request`, `Http::StartProcessing`, `Http::Redirect`, `Http::SendFile` |
|
|
18
|
+
| Database | `Database::Query` |
|
|
19
|
+
| Active Job (notification names) | `Job::Enqueued`, `Job::Scheduled`, `Job::Started`, `Job::Completed`, `Job::Failed` |
|
|
20
|
+
| Cache | `Cache::Read`, `Cache::Write`, `Cache::Delete` |
|
|
21
|
+
| Views | `View::Render` |
|
|
22
|
+
|
|
23
|
+
This is **independent** of the Sidekiq and Active Job toggles below: instrumentation listens to Rails; the job toggles add **extra** process integration (buffer lifecycle, middleware, callbacks).
|
|
24
|
+
|
|
25
|
+
## Sidekiq
|
|
26
|
+
|
|
27
|
+
Enable **only if** you use Sidekiq:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
E11y.configure do |config|
|
|
31
|
+
config.sidekiq_enabled = true
|
|
32
|
+
end
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The Railtie registers client and server middleware (`E11y::Instruments::Sidekiq`) so jobs participate in the same **ephemeral buffer** semantics as HTTP requests when `config.ephemeral_buffer_enabled` is true.
|
|
36
|
+
|
|
37
|
+
On enqueue, **`E11y::Current.user_id`** (when set, e.g. from request middleware) is merged into **`e11y_baggage`** together with any allowed `Current.baggage` keys. The worker restores **`E11y::Current.baggage`** and **`E11y::Current.user_id`** from that hash. Key **`user_id`** is in the default baggage allowlist (`E11y::BAGGAGE_PROTECTION_DEFAULT_ALLOWED_KEYS`).
|
|
38
|
+
|
|
39
|
+
## Active Job
|
|
40
|
+
|
|
41
|
+
Enable when you want callbacks and buffer handling on **`ActiveJob::Base`** (and **`ApplicationJob`** when that constant is already defined at hook time):
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
E11y.configure do |config|
|
|
45
|
+
config.active_job_enabled = true
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
You can use **both** `rails_instrumentation_enabled` and `active_job_enabled`; they complement each other. If you only enqueue via Sidekiq without Active Job, you may rely on `sidekiq_enabled` alone.
|
|
50
|
+
|
|
51
|
+
The **`before_enqueue`** callback applies the same **`e11y_baggage`** merge as Sidekiq (including **`user_id`** from `E11y::Current`).
|
|
52
|
+
|
|
53
|
+
## Rails.logger bridge
|
|
54
|
+
|
|
55
|
+
Optional wrapper that still delegates to the original logger but also emits **`E11y::Events::Rails::Log::*`** events (`lib/e11y/events/rails/log.rb`):
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
E11y.configure do |config|
|
|
59
|
+
config.logger_bridge_enabled = true
|
|
60
|
+
# Optional: only these severities (Symbol or String); nil / empty = all
|
|
61
|
+
config.logger_bridge_track_severities = %i[warn error fatal]
|
|
62
|
+
# Optional: skip noisy lines (String substrings or Regexp)
|
|
63
|
+
config.logger_bridge_ignore_patterns = [%r{health}]
|
|
64
|
+
end
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Filtering uses **`logger_bridge_track_severities`** and **`logger_bridge_ignore_patterns`** only.
|
|
68
|
+
|
|
69
|
+
## Configuration reference
|
|
70
|
+
|
|
71
|
+
| Flag | Purpose |
|
|
72
|
+
|------|---------|
|
|
73
|
+
| `rails_instrumentation_enabled` | Map `ActiveSupport::Notifications` to E11y events |
|
|
74
|
+
| `sidekiq_enabled` | Sidekiq client/server middleware |
|
|
75
|
+
| `active_job_enabled` | `ActiveJob::Base` / `ApplicationJob` callbacks |
|
|
76
|
+
| `logger_bridge_enabled` | Wrap `Rails.logger` with `E11y::Logger::Bridge` |
|
|
77
|
+
| `ephemeral_buffer_enabled` | Request/job-scoped debug buffer (see README) |
|
|
78
|
+
|
|
79
|
+
Further detail: [ADR-008: Rails integration](architecture/ADR-008-rails-integration.md), [Quick Start](QUICK-START.md).
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Schema Validation
|
|
2
|
+
|
|
3
|
+
> Back to [README](../README.md#documentation)
|
|
4
|
+
|
|
5
|
+
E11y validates event data using [dry-schema](https://dry-rb.org/gems/dry-schema/).
|
|
6
|
+
|
|
7
|
+
## Basic Example
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
class OrderCreatedEvent < E11y::Event::Base
|
|
11
|
+
schema do
|
|
12
|
+
required(:order_id).filled(:string)
|
|
13
|
+
required(:total).filled(:float, gt?: 0)
|
|
14
|
+
required(:currency).filled(:string, included_in?: %w[USD EUR GBP])
|
|
15
|
+
optional(:coupon_code).maybe(:string)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Valid event
|
|
20
|
+
OrderCreatedEvent.track(order_id: "123", total: 99.99, currency: "USD")
|
|
21
|
+
|
|
22
|
+
# Invalid event raises E11y::ValidationError
|
|
23
|
+
OrderCreatedEvent.track(order_id: nil, total: -10, currency: "INVALID")
|
|
24
|
+
# => ValidationError: order_id is missing, total must be > 0
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Validation Modes
|
|
28
|
+
|
|
29
|
+
For high-frequency events, you can configure validation behavior:
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
class HighFrequencyEvent < E11y::Event::Base
|
|
33
|
+
# Always validate (default)
|
|
34
|
+
validation_mode :always
|
|
35
|
+
|
|
36
|
+
# Sampled validation (validate 1% of events)
|
|
37
|
+
validation_mode :sampled, sample_rate: 0.01
|
|
38
|
+
|
|
39
|
+
# Never validate (use with caution)
|
|
40
|
+
validation_mode :never
|
|
41
|
+
end
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Use `:always` for user input and critical events. Use `:sampled` for high-frequency internal events. Use `:never` only for trusted, typed input.
|
|
45
|
+
|
|
46
|
+
## Validation Behavior
|
|
47
|
+
|
|
48
|
+
By default, invalid events raise `E11y::ValidationError`:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
OrderEvent.track(order_id: nil)
|
|
52
|
+
# => E11y::ValidationError
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
To handle validation errors gracefully:
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
begin
|
|
59
|
+
OrderEvent.track(order_id: nil)
|
|
60
|
+
rescue E11y::ValidationError => e
|
|
61
|
+
Rails.logger.warn "Invalid event: #{e.message}"
|
|
62
|
+
end
|
|
63
|
+
```
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# SLO: PromQL Queries and Alert Rules
|
|
2
|
+
|
|
3
|
+
> Back to [README](../README.md#documentation)
|
|
4
|
+
|
|
5
|
+
E11y emits SLO metrics to Prometheus via Yabeda. Use these PromQL queries and alert rules in Grafana dashboards and Prometheus.
|
|
6
|
+
|
|
7
|
+
**Metrics emitted:**
|
|
8
|
+
- `slo_http_requests_total{controller, action, status}` — HTTP request count
|
|
9
|
+
- `slo_http_request_duration_seconds` — HTTP latency histogram
|
|
10
|
+
- `slo_background_jobs_total{job_class, status, queue}` — Job count
|
|
11
|
+
- `slo_event_result_total{slo_name, slo_status}` — Event-driven SLO (EventSlo middleware)
|
|
12
|
+
- `e11y_track_duration_seconds` — E11y pipeline latency (TrackLatency middleware)
|
|
13
|
+
- `e11y_events_tracked_total{result, event_name}` — E11y delivery success/drop
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## HTTP Availability SLO
|
|
18
|
+
|
|
19
|
+
**Success rate (30d window):**
|
|
20
|
+
```promql
|
|
21
|
+
sum(rate(slo_http_requests_total{status=~"2..|3.."}[30d])) by (controller, action)
|
|
22
|
+
/
|
|
23
|
+
sum(rate(slo_http_requests_total[30d])) by (controller, action)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Error rate (5m, for alerts):**
|
|
27
|
+
```promql
|
|
28
|
+
sum(rate(slo_http_requests_total{status=~"4..|5.."}[5m])) by (controller, action)
|
|
29
|
+
/
|
|
30
|
+
sum(rate(slo_http_requests_total[5m])) by (controller, action)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Per-endpoint availability (99.9% target):**
|
|
34
|
+
```promql
|
|
35
|
+
# Replace OrdersController, create with your controller#action
|
|
36
|
+
sum(rate(slo_http_requests_total{controller="OrdersController",action="create",status=~"2..|3.."}[30d]))
|
|
37
|
+
/
|
|
38
|
+
sum(rate(slo_http_requests_total{controller="OrdersController",action="create"}[30d]))
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## HTTP Latency SLO
|
|
44
|
+
|
|
45
|
+
**p99 latency (30d):**
|
|
46
|
+
```promql
|
|
47
|
+
histogram_quantile(0.99,
|
|
48
|
+
sum(rate(slo_http_request_duration_seconds_bucket[30d])) by (le, controller, action)
|
|
49
|
+
)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**p99 > 500ms alert:**
|
|
53
|
+
```promql
|
|
54
|
+
histogram_quantile(0.99,
|
|
55
|
+
sum(rate(slo_http_request_duration_seconds_bucket[5m])) by (le, controller, action)
|
|
56
|
+
) > 0.5
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Event-Driven SLO (EventSlo)
|
|
62
|
+
|
|
63
|
+
**Success rate by slo_name (30d):**
|
|
64
|
+
```promql
|
|
65
|
+
sum(rate(slo_event_result_total{slo_status="success"}[30d])) by (slo_name)
|
|
66
|
+
/
|
|
67
|
+
sum(rate(slo_event_result_total[30d])) by (slo_name)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Example — payment success rate:**
|
|
71
|
+
```promql
|
|
72
|
+
sum(rate(slo_event_result_total{slo_name="payment_success_rate",slo_status="success"}[30d]))
|
|
73
|
+
/
|
|
74
|
+
sum(rate(slo_event_result_total{slo_name="payment_success_rate"}[30d]))
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## E11y Self-Monitoring
|
|
80
|
+
|
|
81
|
+
**E11y pipeline latency p99 (<1ms target):**
|
|
82
|
+
```promql
|
|
83
|
+
histogram_quantile(0.99,
|
|
84
|
+
sum(rate(e11y_track_duration_seconds_bucket[30d])) by (le)
|
|
85
|
+
)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**E11y delivery success rate (99.9% target):**
|
|
89
|
+
```promql
|
|
90
|
+
sum(rate(e11y_events_tracked_total{result="success"}[30d]))
|
|
91
|
+
/
|
|
92
|
+
sum(rate(e11y_events_tracked_total[30d]))
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Prometheus Alert Rules
|
|
98
|
+
|
|
99
|
+
Save as `prometheus/alerts/e11y_slo.yml`:
|
|
100
|
+
|
|
101
|
+
```yaml
|
|
102
|
+
groups:
|
|
103
|
+
- name: e11y_slo_http
|
|
104
|
+
rules:
|
|
105
|
+
- alert: SLOHttpAvailabilityLow
|
|
106
|
+
expr: |
|
|
107
|
+
sum(rate(slo_http_requests_total{status=~"4..|5.."}[5m])) by (controller, action)
|
|
108
|
+
/
|
|
109
|
+
sum(rate(slo_http_requests_total[5m])) by (controller, action)
|
|
110
|
+
> 0.01
|
|
111
|
+
for: 5m
|
|
112
|
+
labels:
|
|
113
|
+
severity: warning
|
|
114
|
+
annotations:
|
|
115
|
+
summary: "HTTP availability below 99%"
|
|
116
|
+
description: "Error rate > 1% for 5 minutes"
|
|
117
|
+
|
|
118
|
+
- alert: SLOHttpLatencyHigh
|
|
119
|
+
expr: |
|
|
120
|
+
histogram_quantile(0.99,
|
|
121
|
+
sum(rate(slo_http_request_duration_seconds_bucket[5m])) by (le, controller, action)
|
|
122
|
+
) > 0.5
|
|
123
|
+
for: 5m
|
|
124
|
+
labels:
|
|
125
|
+
severity: warning
|
|
126
|
+
annotations:
|
|
127
|
+
summary: "HTTP p99 latency > 500ms"
|
|
128
|
+
|
|
129
|
+
- name: e11y_self_monitoring
|
|
130
|
+
rules:
|
|
131
|
+
- alert: E11yTrackLatencyHigh
|
|
132
|
+
expr: |
|
|
133
|
+
histogram_quantile(0.99,
|
|
134
|
+
sum(rate(e11y_track_duration_seconds_bucket[5m])) by (le)
|
|
135
|
+
) > 0.001
|
|
136
|
+
for: 5m
|
|
137
|
+
labels:
|
|
138
|
+
severity: warning
|
|
139
|
+
annotations:
|
|
140
|
+
summary: "E11y track() p99 > 1ms"
|
|
141
|
+
|
|
142
|
+
- alert: E11yDeliveryRateLow
|
|
143
|
+
expr: |
|
|
144
|
+
sum(rate(e11y_events_tracked_total{result="success"}[1h]))
|
|
145
|
+
/
|
|
146
|
+
sum(rate(e11y_events_tracked_total[1h]))
|
|
147
|
+
< 0.999
|
|
148
|
+
for: 5m
|
|
149
|
+
labels:
|
|
150
|
+
severity: warning
|
|
151
|
+
annotations:
|
|
152
|
+
summary: "E11y delivery rate below 99.9%"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Grafana Dashboard
|
|
158
|
+
|
|
159
|
+
Use `rake e11y:slo:dashboard` to generate a dashboard from `slo.yml`, or add panels manually with the PromQL above.
|
|
160
|
+
|
|
161
|
+
**Metric name prefix:** Yabeda exports with `yabeda_` prefix. If queries return no data, try `yabeda_e11y_slo_http_requests_total` or check your Prometheus scrape config.
|
data/docs/TESTING.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Testing
|
|
2
|
+
|
|
3
|
+
> Back to [README](../README.md#documentation)
|
|
4
|
+
|
|
5
|
+
Use the **InMemoryTest** adapter for testing. It extends `InMemory` and overrides `last_event` to skip Rails auto-instrumentation events (`E11y::Events::Rails::*`), so your business events aren't obscured by request lifecycle events.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
# spec/rails_helper.rb or spec/spec_helper.rb
|
|
11
|
+
RSpec.configure do |config|
|
|
12
|
+
config.before(:each) do
|
|
13
|
+
E11y.configure do |e11y_config|
|
|
14
|
+
e11y_config.adapters[:test] = E11y::Adapters::InMemoryTest.new
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
config.after(:each) do
|
|
19
|
+
E11y.configuration.adapters[:test]&.clear!
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Test Events
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
RSpec.describe OrdersController do
|
|
28
|
+
let(:test_adapter) { E11y.configuration.adapters[:test] }
|
|
29
|
+
|
|
30
|
+
it "tracks order creation" do
|
|
31
|
+
post :create, params: { item: "Book", price: 29.99 }
|
|
32
|
+
|
|
33
|
+
events = test_adapter.events
|
|
34
|
+
expect(events).to include(
|
|
35
|
+
a_hash_including(
|
|
36
|
+
event_name: "OrderCreatedEvent",
|
|
37
|
+
payload: hash_including(item: "Book", price: 29.99)
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "does not track payment for free orders" do
|
|
43
|
+
post :create, params: { item: "Free Book", price: 0 }
|
|
44
|
+
|
|
45
|
+
payment_events = test_adapter.events.select { |e| e[:event_name] == "PaymentProcessedEvent" }
|
|
46
|
+
expect(payment_events).to be_empty
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## InMemoryTest Adapter API
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
test_adapter = E11y::Adapters::InMemoryTest.new
|
|
55
|
+
|
|
56
|
+
# Get all events
|
|
57
|
+
test_adapter.events # => Array<Hash>
|
|
58
|
+
|
|
59
|
+
# Count events
|
|
60
|
+
test_adapter.event_count # => Integer
|
|
61
|
+
|
|
62
|
+
# Find last event (skips Rails instrumentation events)
|
|
63
|
+
test_adapter.last_event # => Hash
|
|
64
|
+
|
|
65
|
+
# Clear all events
|
|
66
|
+
test_adapter.clear!
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
> **Note:** Use `InMemoryTest` in test suites; use `InMemory` in production configs (e.g. benchmarks).
|
|
@@ -66,12 +66,12 @@ Modern Rails applications need:
|
|
|
66
66
|
- ✅ Zero-allocation event tracking (class methods only)
|
|
67
67
|
- ✅ <1ms p99 latency @ 1000 events/sec
|
|
68
68
|
- ✅ <100MB memory footprint
|
|
69
|
-
- ✅ Rails
|
|
69
|
+
- ✅ Rails 7.0+ (7.x, 8.x)
|
|
70
70
|
- ✅ Open-source extensibility
|
|
71
71
|
|
|
72
72
|
**Non-Goals:**
|
|
73
73
|
- ❌ Plain Ruby support (Rails only)
|
|
74
|
-
- ❌
|
|
74
|
+
- ❌ Rails 6.x and earlier
|
|
75
75
|
- ❌ Hot configuration reload
|
|
76
76
|
- ❌ Distributed tracing coordination (only propagation)
|
|
77
77
|
|
|
@@ -452,9 +452,9 @@ graph TB
|
|
|
452
452
|
end
|
|
453
453
|
|
|
454
454
|
subgraph "Thread-Local Storage"
|
|
455
|
-
TL1[Current.trace_id<br/>
|
|
456
|
-
TL2[Current.trace_id<br/>
|
|
457
|
-
TL3[Current.trace_id<br/>
|
|
455
|
+
TL1[Current.trace_id<br/>EphemeralBuffer<br/>Thread.current]
|
|
456
|
+
TL2[Current.trace_id<br/>EphemeralBuffer<br/>Thread.current]
|
|
457
|
+
TL3[Current.trace_id<br/>EphemeralBuffer<br/>Thread.current]
|
|
458
458
|
end
|
|
459
459
|
|
|
460
460
|
subgraph "Shared Resources"
|
|
@@ -805,7 +805,7 @@ class RoutingMiddleware < E11y::Middleware
|
|
|
805
805
|
|
|
806
806
|
if severity == :debug
|
|
807
807
|
# Route to request-scoped buffer
|
|
808
|
-
|
|
808
|
+
EphemeralBuffer.add_event(event_data)
|
|
809
809
|
else
|
|
810
810
|
# Route to main buffer
|
|
811
811
|
MainBuffer.add(event_data)
|
|
@@ -1239,74 +1239,46 @@ end
|
|
|
1239
1239
|
|
|
1240
1240
|
### 3.4. Request-Scoped Buffer
|
|
1241
1241
|
|
|
1242
|
-
**Design Decision:** Thread-local storage
|
|
1242
|
+
**Design Decision:** Thread-local storage via `EphemeralBuffer` with `Thread.current[:e11y_ephemeral_buffer]`. Context (trace_id, request_id) in `E11y::Current` (ActiveSupport::CurrentAttributes); buffer is separate.
|
|
1243
1243
|
|
|
1244
1244
|
```ruby
|
|
1245
1245
|
module E11y
|
|
1246
1246
|
class Current < ActiveSupport::CurrentAttributes
|
|
1247
|
-
# Thread-local attributes
|
|
1247
|
+
# Thread-local context attributes (no buffer here)
|
|
1248
1248
|
attribute :trace_id
|
|
1249
1249
|
attribute :user_id
|
|
1250
1250
|
attribute :request_id
|
|
1251
|
-
attribute :request_buffer # Debug events buffer
|
|
1252
1251
|
attribute :sampled # Sampling decision
|
|
1253
|
-
|
|
1254
|
-
def request_buffer
|
|
1255
|
-
attributes[:request_buffer] ||= []
|
|
1256
|
-
end
|
|
1257
|
-
|
|
1258
|
-
def add_debug_event(event_data)
|
|
1259
|
-
request_buffer << event_data if request_buffer.size < Config.request_buffer_limit
|
|
1260
|
-
end
|
|
1261
|
-
|
|
1262
|
-
def flush_debug_events
|
|
1263
|
-
events = request_buffer.dup
|
|
1264
|
-
request_buffer.clear
|
|
1265
|
-
events
|
|
1266
|
-
end
|
|
1267
1252
|
end
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1253
|
+
|
|
1254
|
+
module Buffers
|
|
1255
|
+
class EphemeralBuffer
|
|
1256
|
+
THREAD_KEY_BUFFER = :e11y_ephemeral_buffer
|
|
1257
|
+
|
|
1258
|
+
def self.initialize!(request_id: nil, buffer_limit: nil)
|
|
1259
|
+
Thread.current[THREAD_KEY_BUFFER] = []
|
|
1274
1260
|
end
|
|
1275
|
-
|
|
1276
|
-
def
|
|
1277
|
-
|
|
1261
|
+
|
|
1262
|
+
def self.add_event(event_data)
|
|
1263
|
+
buf = Thread.current[THREAD_KEY_BUFFER]
|
|
1264
|
+
return false unless buf
|
|
1265
|
+
buf << event_data if buf.size < (buffer_limit || Config.buffer (job_buffer_limit))
|
|
1278
1266
|
end
|
|
1279
|
-
|
|
1280
|
-
def
|
|
1281
|
-
#
|
|
1282
|
-
ActiveSupport::Notifications.subscribe('process_action.action_controller') do |*args|
|
|
1283
|
-
event = ActiveSupport::Notifications::Event.new(*args)
|
|
1284
|
-
|
|
1285
|
-
# Flush on error
|
|
1286
|
-
if event.payload[:exception]
|
|
1287
|
-
flush_to_adapters
|
|
1288
|
-
else
|
|
1289
|
-
# Discard on success
|
|
1290
|
-
flush
|
|
1291
|
-
end
|
|
1292
|
-
end
|
|
1267
|
+
|
|
1268
|
+
def self.flush_on_error
|
|
1269
|
+
# Flush buffered events to adapters
|
|
1293
1270
|
end
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
def flush_to_adapters
|
|
1298
|
-
events = flush
|
|
1299
|
-
|
|
1300
|
-
# Send debug events to adapters
|
|
1301
|
-
events.each do |event_data|
|
|
1302
|
-
MainBuffer.add(event_data)
|
|
1303
|
-
end
|
|
1271
|
+
|
|
1272
|
+
def self.discard
|
|
1273
|
+
Thread.current[THREAD_KEY_BUFFER] = nil
|
|
1304
1274
|
end
|
|
1305
1275
|
end
|
|
1306
1276
|
end
|
|
1307
1277
|
end
|
|
1308
1278
|
```
|
|
1309
1279
|
|
|
1280
|
+
Rails integration: `Middleware::Request` and Sidekiq/ActiveJob instruments call `EphemeralBuffer.initialize!`, `flush_on_error`, `discard` at request/job boundaries.
|
|
1281
|
+
|
|
1310
1282
|
---
|
|
1311
1283
|
|
|
1312
1284
|
### 3.5. Adapter Base Class
|
|
@@ -1608,7 +1580,7 @@ Pipeline.process(event_data)
|
|
|
1608
1580
|
└─ next
|
|
1609
1581
|
↓
|
|
1610
1582
|
7. RoutingMiddleware
|
|
1611
|
-
├─ severity == :debug? →
|
|
1583
|
+
├─ severity == :debug? → EphemeralBuffer
|
|
1612
1584
|
└─ severity == :info+? → MainBuffer
|
|
1613
1585
|
↓
|
|
1614
1586
|
Buffer → Adapters (receive normalized event_name)
|
|
@@ -1636,6 +1608,9 @@ Buffer → Adapters (receive normalized event_name)
|
|
|
1636
1608
|
|
|
1637
1609
|
## 5. Memory Optimization Strategy
|
|
1638
1610
|
|
|
1611
|
+
> **📖 For full design, implementation details, and trade-offs, see:**
|
|
1612
|
+
> **[ADR-018: Memory Optimization](ADR-018-memory-optimization.md)**
|
|
1613
|
+
|
|
1639
1614
|
### 5.1. Zero-Allocation Pattern
|
|
1640
1615
|
|
|
1641
1616
|
**Key Principle:** No object instances, only hashes.
|
|
@@ -1754,8 +1729,8 @@ end
|
|
|
1754
1729
|
**Components:**
|
|
1755
1730
|
|
|
1756
1731
|
1. **Thread-local (no sync needed):**
|
|
1757
|
-
- Request-scoped buffer (
|
|
1758
|
-
- Context (Current.trace_id, etc.)
|
|
1732
|
+
- Request-scoped buffer (EphemeralBuffer + Thread.current[:e11y_ephemeral_buffer])
|
|
1733
|
+
- Context (Current.trace_id, request_id, etc.)
|
|
1759
1734
|
|
|
1760
1735
|
2. **Concurrent (thread-safe):**
|
|
1761
1736
|
- Main ring buffer (Concurrent::AtomicFixnum)
|
|
@@ -2037,7 +2012,7 @@ Gem::Specification.new do |spec|
|
|
|
2037
2012
|
spec.required_ruby_version = '>= 3.3.0'
|
|
2038
2013
|
|
|
2039
2014
|
# Required
|
|
2040
|
-
spec.add_dependency 'rails', '>=
|
|
2015
|
+
spec.add_dependency 'rails', '>= 7.0'
|
|
2041
2016
|
spec.add_dependency 'dry-schema', '~> 1.13'
|
|
2042
2017
|
spec.add_dependency 'dry-configurable', '~> 1.1'
|
|
2043
2018
|
spec.add_dependency 'concurrent-ruby', '~> 1.2'
|
|
@@ -2376,7 +2351,7 @@ end
|
|
|
2376
2351
|
**Middleware checks opt-out flag before processing:**
|
|
2377
2352
|
|
|
2378
2353
|
```ruby
|
|
2379
|
-
# E11y::Middleware::
|
|
2354
|
+
# E11y::Middleware::PIIFilter
|
|
2380
2355
|
def call(event_data)
|
|
2381
2356
|
event_class = event_data[:event_class]
|
|
2382
2357
|
|
|
@@ -2602,10 +2577,6 @@ end
|
|
|
2602
2577
|
- **[ADR-006: Security & Compliance](ADR-006-security-compliance.md)** - PII filtering, rate limiting
|
|
2603
2578
|
- **[ADR-011: Testing Strategy](ADR-011-testing-strategy.md)** - Testing approach
|
|
2604
2579
|
|
|
2605
|
-
**Configuration:**
|
|
2606
|
-
- **[COMPREHENSIVE-CONFIGURATION.md](COMPREHENSIVE-CONFIGURATION.md)** - Complete configuration examples
|
|
2607
|
-
- **[CONFLICT-ANALYSIS.md](CONFLICT-ANALYSIS.md)** - Feature conflict resolutions
|
|
2608
|
-
|
|
2609
2580
|
**Use Cases:**
|
|
2610
2581
|
- **[docs/use_cases/](use_cases/)** - All 22 use cases documented
|
|
2611
2582
|
|