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
|
@@ -184,164 +184,17 @@ sequenceDiagram
|
|
|
184
184
|
|
|
185
185
|
## 3. Railtie & Initialization
|
|
186
186
|
|
|
187
|
-
### 3.1. Railtie
|
|
187
|
+
### 3.1. Railtie implementation
|
|
188
188
|
|
|
189
|
-
|
|
190
|
-
# lib/e11y/railtie.rb
|
|
191
|
-
module E11y
|
|
192
|
-
class Railtie < Rails::Railtie
|
|
193
|
-
# Run before framework initialization
|
|
194
|
-
config.before_initialize do
|
|
195
|
-
# Set up basic configuration
|
|
196
|
-
E11y.configure do |config|
|
|
197
|
-
config.environment = Rails.env
|
|
198
|
-
config.service_name = Rails.application.class.module_parent_name.underscore
|
|
199
|
-
end
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
# Run after framework initialization
|
|
203
|
-
config.after_initialize do
|
|
204
|
-
next unless E11y.config.enabled
|
|
205
|
-
|
|
206
|
-
# Setup instruments (each can be enabled/disabled separately)
|
|
207
|
-
if E11y.config.instruments.active_support_notifications.enabled
|
|
208
|
-
E11y::Instruments::ActiveSupportNotifications.setup!
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
if E11y.config.instruments.sidekiq.enabled
|
|
212
|
-
E11y::Instruments::Sidekiq.setup!
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
if E11y.config.instruments.active_job.enabled
|
|
216
|
-
E11y::Instruments::ActiveJob.setup!
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
if E11y.config.instruments.rack_middleware.enabled
|
|
220
|
-
E11y::Instruments::RackMiddleware.setup!
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
# Setup logger bridge
|
|
224
|
-
E11y::Logger::Bridge.setup! if E11y.config.logger_bridge.enabled
|
|
225
|
-
|
|
226
|
-
# Setup development tools
|
|
227
|
-
E11y::Console.setup! if Rails.env.development?
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
# Middleware insertion
|
|
231
|
-
initializer 'e11y.middleware' do |app|
|
|
232
|
-
app.middleware.insert_before(
|
|
233
|
-
Rails::Rack::Logger,
|
|
234
|
-
E11y::Middleware::Request
|
|
235
|
-
)
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
# ActiveSupport::Notifications subscribers
|
|
239
|
-
initializer 'e11y.notifications' do
|
|
240
|
-
ActiveSupport::Notifications.subscribe(/.*/) do |name, start, finish, id, payload|
|
|
241
|
-
E11y::Instruments::NotificationSubscriber.handle(
|
|
242
|
-
name: name,
|
|
243
|
-
started_at: start,
|
|
244
|
-
finished_at: finish,
|
|
245
|
-
transaction_id: id,
|
|
246
|
-
payload: payload
|
|
247
|
-
)
|
|
248
|
-
end
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
# Sidekiq integration
|
|
252
|
-
initializer 'e11y.sidekiq' do
|
|
253
|
-
if defined?(Sidekiq)
|
|
254
|
-
require 'e11y/instruments/sidekiq'
|
|
255
|
-
|
|
256
|
-
Sidekiq.configure_server do |config|
|
|
257
|
-
config.server_middleware do |chain|
|
|
258
|
-
chain.add E11y::Instruments::Sidekiq::ServerMiddleware
|
|
259
|
-
end
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
Sidekiq.configure_client do |config|
|
|
263
|
-
config.client_middleware do |chain|
|
|
264
|
-
chain.add E11y::Instruments::Sidekiq::ClientMiddleware
|
|
265
|
-
end
|
|
266
|
-
end
|
|
267
|
-
end
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
# ActiveJob integration
|
|
271
|
-
initializer 'e11y.active_job' do
|
|
272
|
-
ActiveSupport.on_load(:active_job) do
|
|
273
|
-
require 'e11y/instruments/active_job'
|
|
274
|
-
|
|
275
|
-
include E11y::Instruments::ActiveJob::Callbacks
|
|
276
|
-
end
|
|
277
|
-
end
|
|
278
|
-
|
|
279
|
-
# Console helpers
|
|
280
|
-
console do
|
|
281
|
-
E11y::Console.enable!
|
|
282
|
-
|
|
283
|
-
puts "E11y loaded. Try: E11y.stats"
|
|
284
|
-
end
|
|
285
|
-
|
|
286
|
-
# Rake task helpers
|
|
287
|
-
rake_tasks do
|
|
288
|
-
load 'e11y/tasks.rake'
|
|
289
|
-
end
|
|
290
|
-
end
|
|
291
|
-
end
|
|
292
|
-
```
|
|
189
|
+
**Source of truth:** `lib/e11y/railtie.rb` (read the file in the repo; do not paste stale excerpts).
|
|
293
190
|
|
|
294
|
-
|
|
191
|
+
Shipped behavior in short: `before_initialize` sets `environment`, `service_name`, and default `enabled`; after `load_config_initializers`, `e11y.setup_instrumentation` wires Rails instrumentation, optional logger bridge, and optional Sidekiq / Active Job when the corresponding **boolean** flags are set; `e11y.middleware` inserts `E11y::Middleware::Request`; development/test may register the DevLog adapter and `DevLogSource` middleware; optional `e11y.http_tracing` patches Net::HTTP; console loads `E11y::Console`; Rake tasks load the `lib/e11y/tasks/*.rake` files.
|
|
295
192
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
def initialize
|
|
302
|
-
super
|
|
303
|
-
|
|
304
|
-
# Rails-specific defaults
|
|
305
|
-
@environment = ::Rails.env
|
|
306
|
-
@service_name = derive_service_name
|
|
307
|
-
@enabled = !::Rails.env.test? # Disabled in tests by default
|
|
308
|
-
|
|
309
|
-
# Auto-detect adapters
|
|
310
|
-
@adapters = auto_detect_adapters
|
|
311
|
-
|
|
312
|
-
# Rails logger bridge
|
|
313
|
-
@logger_bridge = LoggerBridgeConfig.new
|
|
314
|
-
end
|
|
315
|
-
|
|
316
|
-
private
|
|
317
|
-
|
|
318
|
-
def derive_service_name
|
|
319
|
-
::Rails.application.class.module_parent_name.underscore
|
|
320
|
-
rescue
|
|
321
|
-
'rails_app'
|
|
322
|
-
end
|
|
323
|
-
|
|
324
|
-
def auto_detect_adapters
|
|
325
|
-
adapters = []
|
|
326
|
-
|
|
327
|
-
# Always include stdout in development
|
|
328
|
-
adapters << :stdout if ::Rails.env.development?
|
|
329
|
-
|
|
330
|
-
# Auto-detect file logging
|
|
331
|
-
adapters << :file if ::Rails.root.join('log').directory?
|
|
332
|
-
|
|
333
|
-
# Auto-detect Sentry
|
|
334
|
-
adapters << :sentry if defined?(Sentry)
|
|
335
|
-
|
|
336
|
-
# Auto-detect Loki
|
|
337
|
-
adapters << :loki if ENV['LOKI_URL'].present?
|
|
338
|
-
|
|
339
|
-
adapters
|
|
340
|
-
end
|
|
341
|
-
end
|
|
342
|
-
end
|
|
343
|
-
end
|
|
344
|
-
```
|
|
193
|
+
Older drafts of this ADR showed extra initializers (e.g. a global `ActiveSupport::Notifications.subscribe(/.*/)`, unconditional Sidekiq registration, `lib/e11y/configuration/rails.rb`, `LoggerBridgeConfig` as a nested type). **Those are not in the codebase.**
|
|
194
|
+
|
|
195
|
+
### 3.2. Configuration
|
|
196
|
+
|
|
197
|
+
All tunables live on **`E11y::Configuration`** in `lib/e11y/configuration.rb`. There is no separate `E11y::Configuration::Rails` class.
|
|
345
198
|
|
|
346
199
|
---
|
|
347
200
|
|
|
@@ -400,7 +253,7 @@ module E11y
|
|
|
400
253
|
# ========================================
|
|
401
254
|
|
|
402
255
|
def self.setup!
|
|
403
|
-
return unless E11y.config.
|
|
256
|
+
return unless E11y.config.rails_instrumentation_enabled
|
|
404
257
|
|
|
405
258
|
# Subscribe to Rails events
|
|
406
259
|
event_mapping.each do |asn_pattern, e11y_event_class|
|
|
@@ -421,19 +274,20 @@ module E11y
|
|
|
421
274
|
|
|
422
275
|
# Built-in event mappings (can be overridden in config!)
|
|
423
276
|
DEFAULT_RAILS_EVENT_MAPPING = {
|
|
424
|
-
'sql.active_record' => Events::Rails::Database::Query,
|
|
425
|
-
'process_action.action_controller' => Events::Rails::Http::Request,
|
|
426
|
-
'render_template.action_view' => Events::Rails::View::Render,
|
|
427
|
-
'send_file.action_controller' => Events::Rails::Http::SendFile,
|
|
428
|
-
'redirect_to.action_controller' => Events::Rails::Http::Redirect,
|
|
429
|
-
'start_processing.action_controller' => Events::Rails::Http::StartProcessing,
|
|
430
|
-
'cache_read.active_support' => Events::Rails::Cache::Read,
|
|
431
|
-
'cache_write.active_support' => Events::Rails::Cache::Write,
|
|
432
|
-
'cache_delete.active_support' => Events::Rails::Cache::Delete,
|
|
433
|
-
'enqueue.active_job' => Events::Rails::Job::Enqueued,
|
|
434
|
-
'enqueue_at.active_job' => Events::Rails::Job::Scheduled,
|
|
435
|
-
'perform_start.active_job' => Events::Rails::Job::Started,
|
|
436
|
-
|
|
277
|
+
'sql.active_record' => E11y::Events::Rails::Database::Query,
|
|
278
|
+
'process_action.action_controller' => E11y::Events::Rails::Http::Request,
|
|
279
|
+
'render_template.action_view' => E11y::Events::Rails::View::Render,
|
|
280
|
+
'send_file.action_controller' => E11y::Events::Rails::Http::SendFile,
|
|
281
|
+
'redirect_to.action_controller' => E11y::Events::Rails::Http::Redirect,
|
|
282
|
+
'start_processing.action_controller' => E11y::Events::Rails::Http::StartProcessing,
|
|
283
|
+
'cache_read.active_support' => E11y::Events::Rails::Cache::Read,
|
|
284
|
+
'cache_write.active_support' => E11y::Events::Rails::Cache::Write,
|
|
285
|
+
'cache_delete.active_support' => E11y::Events::Rails::Cache::Delete,
|
|
286
|
+
'enqueue.active_job' => E11y::Events::Rails::Job::Enqueued,
|
|
287
|
+
'enqueue_at.active_job' => E11y::Events::Rails::Job::Scheduled,
|
|
288
|
+
'perform_start.active_job' => E11y::Events::Rails::Job::Started,
|
|
289
|
+
# perform.active_job: Completed on success; Failed when payload has exception
|
|
290
|
+
'perform.active_job' => E11y::Events::Rails::Job::Completed
|
|
437
291
|
}.freeze
|
|
438
292
|
|
|
439
293
|
# Get final event mapping (after config overrides)
|
|
@@ -442,7 +296,7 @@ module E11y
|
|
|
442
296
|
mapping = DEFAULT_RAILS_EVENT_MAPPING.dup
|
|
443
297
|
|
|
444
298
|
# Apply custom mappings from config (Devise-style overrides)
|
|
445
|
-
custom_mappings = E11y.config.
|
|
299
|
+
custom_mappings = E11y.config.rails_instrumentation_custom_mappings || {}
|
|
446
300
|
mapping.merge!(custom_mappings)
|
|
447
301
|
|
|
448
302
|
mapping
|
|
@@ -450,7 +304,7 @@ module E11y
|
|
|
450
304
|
end
|
|
451
305
|
|
|
452
306
|
def self.ignored?(pattern)
|
|
453
|
-
ignore_list = E11y.config.
|
|
307
|
+
ignore_list = E11y.config.rails_instrumentation_ignore_events || []
|
|
454
308
|
ignore_list.include?(pattern)
|
|
455
309
|
end
|
|
456
310
|
|
|
@@ -464,81 +318,32 @@ module E11y
|
|
|
464
318
|
end
|
|
465
319
|
```
|
|
466
320
|
|
|
467
|
-
### 4.2. Configuration
|
|
321
|
+
### 4.2. Configuration (Rails integration)
|
|
468
322
|
|
|
469
|
-
**
|
|
323
|
+
**As implemented:** flags and hashes on `E11y::Configuration` (`lib/e11y/configuration.rb`). There is **no** nested `config.rails_instrumentation do`, `config.sidekiq do`, or `config.active_job do` DSL—those snippets were design sketches and must not be copied into apps.
|
|
470
324
|
|
|
471
|
-
**
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
- ✅ **Opt-in**: Defaults work for 90% of cases, override only when needed
|
|
475
|
-
- ✅ **No monkey-patching**: Clean override mechanism
|
|
325
|
+
**Rails → E11y:** enable `rails_instrumentation_enabled`, optionally set `rails_instrumentation_custom_mappings` (String ASN pattern → event class) and `rails_instrumentation_ignore_events` (Array of patterns to skip). Instrumentation is wired in `E11y::Instruments::RailsInstrumentation` and invoked from `E11y::Railtie`.
|
|
326
|
+
|
|
327
|
+
**Jobs:** `sidekiq_enabled` and `active_job_enabled` are booleans. **Logger bridge:** `logger_bridge_enabled`, `logger_bridge_track_severities`, `logger_bridge_ignore_patterns`.
|
|
476
328
|
|
|
477
329
|
```ruby
|
|
478
330
|
# config/initializers/e11y.rb
|
|
479
331
|
E11y.configure do |config|
|
|
480
|
-
config.
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
# Located in Events::Rails namespace
|
|
490
|
-
use_built_in_events true # If false, no auto-mapping
|
|
491
|
-
|
|
492
|
-
# ========================================
|
|
493
|
-
# OVERRIDE EVENT CLASSES (Devise-style)
|
|
494
|
-
# ========================================
|
|
495
|
-
|
|
496
|
-
# Override default event class for specific ASN pattern
|
|
497
|
-
event_class_for 'sql.active_record', MyApp::Events::CustomDatabaseQuery
|
|
498
|
-
event_class_for 'process_action.action_controller', MyApp::Events::CustomHttpRequest
|
|
499
|
-
|
|
500
|
-
# Disable specific events (too noisy or not needed)
|
|
501
|
-
ignore_event 'cache_read.active_support'
|
|
502
|
-
ignore_event 'render_partial.action_view'
|
|
503
|
-
ignore_event 'SCHEMA' # Schema queries
|
|
504
|
-
|
|
505
|
-
# ========================================
|
|
506
|
-
# SELECTIVE INSTRUMENTATION
|
|
507
|
-
# ========================================
|
|
508
|
-
|
|
509
|
-
# Which Rails events to track (glob patterns)
|
|
510
|
-
track_patterns [
|
|
511
|
-
'sql.active_record',
|
|
512
|
-
'process_action.action_controller',
|
|
513
|
-
'render_template.action_view',
|
|
514
|
-
'cache_*.active_support'
|
|
515
|
-
]
|
|
516
|
-
|
|
517
|
-
# Sampling for high-volume events
|
|
518
|
-
sample_patterns do
|
|
519
|
-
pattern 'sql.active_record', sample_rate: 0.1 # 10% of SQL queries
|
|
520
|
-
pattern 'cache_read.active_support', sample_rate: 0.01 # 1% of cache reads
|
|
521
|
-
end
|
|
522
|
-
|
|
523
|
-
# Enrich with custom data
|
|
524
|
-
enrich do |asn_event|
|
|
525
|
-
{
|
|
526
|
-
controller: asn_event.payload[:controller],
|
|
527
|
-
action: asn_event.payload[:action],
|
|
528
|
-
format: asn_event.payload[:format],
|
|
529
|
-
user_id: Current.user&.id # Add context
|
|
530
|
-
}
|
|
531
|
-
end
|
|
532
|
-
end
|
|
533
|
-
|
|
534
|
-
# (Other integrations: Sidekiq, ActiveJob, etc.)
|
|
535
|
-
end
|
|
332
|
+
config.rails_instrumentation_enabled = true
|
|
333
|
+
|
|
334
|
+
config.rails_instrumentation_custom_mappings["sql.active_record"] =
|
|
335
|
+
MyApp::Events::CustomDatabaseQuery
|
|
336
|
+
config.rails_instrumentation_ignore_events << "cache_read.active_support"
|
|
337
|
+
|
|
338
|
+
config.sidekiq_enabled = true
|
|
339
|
+
config.active_job_enabled = true
|
|
340
|
+
config.ephemeral_buffer_enabled = true
|
|
536
341
|
end
|
|
537
342
|
```
|
|
538
343
|
|
|
539
344
|
### 4.2.1. Custom Event Class Example
|
|
540
345
|
|
|
541
|
-
**Use Case:** Override default `Events::Rails::Database::Query` with custom schema and PII rules.
|
|
346
|
+
**Use Case:** Override default `E11y::Events::Rails::Database::Query` with custom schema and PII rules.
|
|
542
347
|
|
|
543
348
|
```ruby
|
|
544
349
|
# app/events/custom_database_query.rb
|
|
@@ -580,153 +385,11 @@ end
|
|
|
580
385
|
|
|
581
386
|
# config/initializers/e11y.rb
|
|
582
387
|
E11y.configure do |config|
|
|
583
|
-
config.
|
|
584
|
-
|
|
585
|
-
event_class_for 'sql.active_record', MyApp::Events::CustomDatabaseQuery
|
|
586
|
-
end
|
|
388
|
+
config.rails_instrumentation_custom_mappings["sql.active_record"] =
|
|
389
|
+
MyApp::Events::CustomDatabaseQuery
|
|
587
390
|
end
|
|
588
391
|
|
|
589
|
-
# Now
|
|
590
|
-
```
|
|
591
|
-
|
|
592
|
-
### 4.2.2. Implementation (Configuration DSL)
|
|
593
|
-
|
|
594
|
-
```ruby
|
|
595
|
-
# lib/e11y/configuration/rails_instrumentation.rb
|
|
596
|
-
module E11y
|
|
597
|
-
module Configuration
|
|
598
|
-
class RailsInstrumentation
|
|
599
|
-
attr_accessor :enabled, :use_built_in_events
|
|
600
|
-
|
|
601
|
-
def initialize
|
|
602
|
-
@enabled = true
|
|
603
|
-
@use_built_in_events = true
|
|
604
|
-
@custom_event_classes = {}
|
|
605
|
-
@ignored_events = []
|
|
606
|
-
@track_patterns = []
|
|
607
|
-
@sample_patterns = {}
|
|
608
|
-
@enrich_block = nil
|
|
609
|
-
end
|
|
610
|
-
|
|
611
|
-
# Override event class (Devise-style)
|
|
612
|
-
def event_class_for(asn_pattern, custom_event_class)
|
|
613
|
-
unless custom_event_class < E11y::Event::Base
|
|
614
|
-
raise ArgumentError, "#{custom_event_class} must inherit from E11y::Event::Base"
|
|
615
|
-
end
|
|
616
|
-
|
|
617
|
-
@custom_event_classes[asn_pattern] = custom_event_class
|
|
618
|
-
end
|
|
619
|
-
|
|
620
|
-
# Disable specific event
|
|
621
|
-
def ignore_event(asn_pattern)
|
|
622
|
-
@ignored_events << asn_pattern
|
|
623
|
-
end
|
|
624
|
-
|
|
625
|
-
# Track only specific patterns
|
|
626
|
-
def track_patterns(*patterns)
|
|
627
|
-
@track_patterns = patterns.flatten
|
|
628
|
-
end
|
|
629
|
-
|
|
630
|
-
# Sampling configuration
|
|
631
|
-
def sample_patterns(&block)
|
|
632
|
-
@sample_patterns_builder ||= SamplePatternsBuilder.new
|
|
633
|
-
@sample_patterns_builder.instance_eval(&block) if block_given?
|
|
634
|
-
@sample_patterns_builder
|
|
635
|
-
end
|
|
636
|
-
|
|
637
|
-
# Enrich ASN events before conversion
|
|
638
|
-
def enrich(&block)
|
|
639
|
-
@enrich_block = block
|
|
640
|
-
end
|
|
641
|
-
|
|
642
|
-
# Get final event mapping (after overrides)
|
|
643
|
-
def event_mapping
|
|
644
|
-
mapping = E11y::Instruments::RailsInstrumentation::DEFAULT_RAILS_EVENT_MAPPING.dup
|
|
645
|
-
mapping.merge!(@custom_event_classes)
|
|
646
|
-
mapping.except(*@ignored_events)
|
|
647
|
-
end
|
|
648
|
-
|
|
649
|
-
# Check if event should be tracked
|
|
650
|
-
def track?(asn_pattern)
|
|
651
|
-
return false if @ignored_events.include?(asn_pattern)
|
|
652
|
-
return true if @track_patterns.empty?
|
|
653
|
-
|
|
654
|
-
@track_patterns.any? do |pattern|
|
|
655
|
-
File.fnmatch(pattern, asn_pattern)
|
|
656
|
-
end
|
|
657
|
-
end
|
|
658
|
-
end
|
|
659
|
-
end
|
|
660
|
-
end
|
|
661
|
-
```
|
|
662
|
-
|
|
663
|
-
# ========================================
|
|
664
|
-
# Sidekiq
|
|
665
|
-
# ========================================
|
|
666
|
-
sidekiq do
|
|
667
|
-
enabled true # Set to false to disable Sidekiq integration
|
|
668
|
-
|
|
669
|
-
# Server middleware (job execution)
|
|
670
|
-
server_middleware do
|
|
671
|
-
enabled true
|
|
672
|
-
track_start true
|
|
673
|
-
track_complete true
|
|
674
|
-
track_failure true
|
|
675
|
-
end
|
|
676
|
-
|
|
677
|
-
# Client middleware (job enqueuing)
|
|
678
|
-
client_middleware do
|
|
679
|
-
enabled true
|
|
680
|
-
track_enqueue true
|
|
681
|
-
end
|
|
682
|
-
|
|
683
|
-
# Trace propagation
|
|
684
|
-
propagate_trace_context true
|
|
685
|
-
trace_context_keys ['e11y_trace_id', 'e11y_span_id', 'e11y_sampled']
|
|
686
|
-
end
|
|
687
|
-
|
|
688
|
-
# ========================================
|
|
689
|
-
# ActiveJob
|
|
690
|
-
# ========================================
|
|
691
|
-
active_job do
|
|
692
|
-
enabled true # Set to false to disable ActiveJob integration
|
|
693
|
-
|
|
694
|
-
track_enqueue true
|
|
695
|
-
track_start true
|
|
696
|
-
track_complete true
|
|
697
|
-
track_failure true
|
|
698
|
-
|
|
699
|
-
# Trace propagation
|
|
700
|
-
propagate_trace_context true
|
|
701
|
-
|
|
702
|
-
# Job-scoped buffer (like request-scoped buffer for HTTP)
|
|
703
|
-
use_job_buffer true
|
|
704
|
-
|
|
705
|
-
job_buffer do
|
|
706
|
-
buffer_severities [:debug]
|
|
707
|
-
flush_on do
|
|
708
|
-
error true # Flush debug events if job fails
|
|
709
|
-
success false # Discard debug events if job succeeds
|
|
710
|
-
end
|
|
711
|
-
max_events 1000
|
|
712
|
-
end
|
|
713
|
-
end
|
|
714
|
-
|
|
715
|
-
# ========================================
|
|
716
|
-
# Rack Middleware
|
|
717
|
-
# ========================================
|
|
718
|
-
rack_middleware do
|
|
719
|
-
enabled true # Set to false to disable Rack middleware
|
|
720
|
-
|
|
721
|
-
track_request_start true
|
|
722
|
-
track_request_complete true
|
|
723
|
-
track_request_failure true
|
|
724
|
-
|
|
725
|
-
# Request-scoped buffer
|
|
726
|
-
use_request_buffer true
|
|
727
|
-
end
|
|
728
|
-
end
|
|
729
|
-
end
|
|
392
|
+
# Now sql.active_record uses CustomDatabaseQuery (see RailsInstrumentation.event_mapping).
|
|
730
393
|
```
|
|
731
394
|
|
|
732
395
|
**Example: Disable specific instruments:**
|
|
@@ -734,19 +397,16 @@ end
|
|
|
734
397
|
```ruby
|
|
735
398
|
# Disable ASN but keep Sidekiq
|
|
736
399
|
E11y.configure do |config|
|
|
737
|
-
config.
|
|
738
|
-
config.
|
|
739
|
-
config.
|
|
400
|
+
config.rails_instrumentation_enabled = false
|
|
401
|
+
config.sidekiq_enabled = true
|
|
402
|
+
config.active_job_enabled = true
|
|
740
403
|
end
|
|
741
404
|
|
|
742
405
|
# Minimal setup: only Sidekiq
|
|
743
406
|
E11y.configure do |config|
|
|
744
|
-
config.
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
active_job { enabled false }
|
|
748
|
-
rack_middleware { enabled false }
|
|
749
|
-
end
|
|
407
|
+
config.rails_instrumentation_enabled = false
|
|
408
|
+
config.sidekiq_enabled = true
|
|
409
|
+
config.active_job_enabled = false
|
|
750
410
|
end
|
|
751
411
|
```
|
|
752
412
|
|
|
@@ -756,7 +416,7 @@ end
|
|
|
756
416
|
|
|
757
417
|
**Design Decision:** E11y provides built-in event classes for standard Rails events.
|
|
758
418
|
|
|
759
|
-
**Location:** `Events::Rails` namespace (auto-loaded by gem)
|
|
419
|
+
**Location:** `E11y::Events::Rails` namespace (auto-loaded by gem)
|
|
760
420
|
|
|
761
421
|
```ruby
|
|
762
422
|
# app/events/rails/ (provided by E11y gem)
|
|
@@ -855,28 +515,14 @@ module Events
|
|
|
855
515
|
end
|
|
856
516
|
```
|
|
857
517
|
|
|
858
|
-
**User can override
|
|
859
|
-
|
|
860
|
-
```ruby
|
|
861
|
-
# config/initializers/e11y.rb
|
|
862
|
-
E11y.configure do |config|
|
|
863
|
-
config.instruments.active_support_notifications do
|
|
864
|
-
# Disable built-in events
|
|
865
|
-
use_built_in_events false
|
|
866
|
-
|
|
867
|
-
# Use custom events instead
|
|
868
|
-
custom_mappings do
|
|
869
|
-
map 'sql.active_record', to: MyApp::Events::DatabaseQuery
|
|
870
|
-
map 'process_action.action_controller', to: MyApp::Events::HttpRequest
|
|
871
|
-
end
|
|
872
|
-
end
|
|
873
|
-
end
|
|
874
|
-
```
|
|
518
|
+
**User can override** built-in ASN → event class mappings with `rails_instrumentation_custom_mappings` and skip patterns with `rails_instrumentation_ignore_events` (see §4.2).
|
|
875
519
|
|
|
876
520
|
---
|
|
877
521
|
|
|
878
522
|
## 5. Sidekiq Integration
|
|
879
523
|
|
|
524
|
+
**Implementation Note:** Sidekiq middleware emits `E11y::Events::Rails::Job::Enqueued`, `Started`, `Completed`, `Failed` for **raw Sidekiq jobs only** (`include Sidekiq::Worker`). When Sidekiq is the queue adapter for ActiveJob, jobs use `ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper`; we skip event emission in Sidekiq middleware to avoid double emission — ActiveJob events come from RailsInstrumentation (ASN).
|
|
525
|
+
|
|
880
526
|
### 5.1. Server Middleware (Job Execution)
|
|
881
527
|
|
|
882
528
|
```ruby
|
|
@@ -886,26 +532,29 @@ module E11y
|
|
|
886
532
|
module Sidekiq
|
|
887
533
|
class ServerMiddleware
|
|
888
534
|
def call(worker, job, queue)
|
|
889
|
-
#
|
|
890
|
-
|
|
535
|
+
# C17 Hybrid: Job gets NEW trace_id, parent_trace_id links to enqueuing request
|
|
536
|
+
parent_trace_id = job['e11y_parent_trace_id']
|
|
537
|
+
trace_id = E11y::TraceContext.generate_id # NEW trace per job
|
|
891
538
|
parent_span_id = job['e11y_span_id']
|
|
892
539
|
|
|
893
|
-
# Restore trace context
|
|
894
540
|
E11y::Current.set(
|
|
895
541
|
trace_id: trace_id,
|
|
542
|
+
parent_trace_id: parent_trace_id,
|
|
896
543
|
parent_span_id: parent_span_id,
|
|
897
544
|
job_id: job['jid'],
|
|
898
545
|
job_class: worker.class.name,
|
|
899
546
|
queue: queue
|
|
900
547
|
)
|
|
901
548
|
|
|
902
|
-
# Start
|
|
903
|
-
if E11y.config.
|
|
904
|
-
E11y
|
|
549
|
+
# Start request-scoped buffer (same as HTTP; config.ephemeral_buffer_enabled)
|
|
550
|
+
if E11y.config.ephemeral_buffer_enabled
|
|
551
|
+
limit = E11y.config.ephemeral_buffer_job_buffer_limit ||
|
|
552
|
+
E11y::Buffers::EphemeralBuffer::DEFAULT_BUFFER_LIMIT
|
|
553
|
+
E11y::Buffers::EphemeralBuffer.initialize!(buffer_limit: limit)
|
|
905
554
|
end
|
|
906
555
|
|
|
907
556
|
# Track job start
|
|
908
|
-
Events::Rails::Job::Started.track(
|
|
557
|
+
E11y::Events::Rails::Job::Started.track(
|
|
909
558
|
job_class: worker.class.name,
|
|
910
559
|
job_id: job['jid'],
|
|
911
560
|
queue: queue,
|
|
@@ -919,20 +568,20 @@ module E11y
|
|
|
919
568
|
result = yield
|
|
920
569
|
|
|
921
570
|
# Track job success
|
|
922
|
-
Events::Rails::Job::Completed.track(
|
|
571
|
+
E11y::Events::Rails::Job::Completed.track(
|
|
923
572
|
job_class: worker.class.name,
|
|
924
573
|
job_id: job['jid'],
|
|
925
574
|
duration: (Time.now - start_time) * 1000,
|
|
926
575
|
queue: queue
|
|
927
576
|
)
|
|
928
577
|
|
|
929
|
-
#
|
|
930
|
-
E11y::
|
|
578
|
+
# Discard buffer on success (same as HTTP)
|
|
579
|
+
E11y::Buffers::EphemeralBuffer.discard if E11y.config.ephemeral_buffer_enabled
|
|
931
580
|
|
|
932
581
|
result
|
|
933
582
|
rescue => error
|
|
934
583
|
# Track job failure
|
|
935
|
-
Events::Rails::Job::Failed.track(
|
|
584
|
+
E11y::Events::Rails::Job::Failed.track(
|
|
936
585
|
job_class: worker.class.name,
|
|
937
586
|
job_id: job['jid'],
|
|
938
587
|
duration: (Time.now - start_time) * 1000,
|
|
@@ -942,8 +591,8 @@ module E11y
|
|
|
942
591
|
backtrace: error.backtrace&.first(10)
|
|
943
592
|
)
|
|
944
593
|
|
|
945
|
-
# Flush
|
|
946
|
-
E11y::
|
|
594
|
+
# Flush buffer on error (includes debug events)
|
|
595
|
+
E11y::Buffers::EphemeralBuffer.flush_on_error if E11y.config.ephemeral_buffer_enabled
|
|
947
596
|
|
|
948
597
|
raise
|
|
949
598
|
ensure
|
|
@@ -976,13 +625,13 @@ module E11y
|
|
|
976
625
|
module Sidekiq
|
|
977
626
|
class ClientMiddleware
|
|
978
627
|
def call(worker_class, job, queue, redis_pool)
|
|
979
|
-
# Propagate trace
|
|
980
|
-
job['
|
|
628
|
+
# C17 Hybrid: Propagate parent trace (job will create NEW trace_id)
|
|
629
|
+
job['e11y_parent_trace_id'] = E11y::Current.trace_id if E11y::Current.trace_id
|
|
981
630
|
job['e11y_span_id'] = E11y::TraceContext.generate_span_id
|
|
982
631
|
job['e11y_sampled'] = E11y::Current.sampled # Trace-consistent sampling
|
|
983
632
|
|
|
984
633
|
# Track job enqueued
|
|
985
|
-
Events::Rails::Job::Enqueued.track(
|
|
634
|
+
E11y::Events::Rails::Job::Enqueued.track(
|
|
986
635
|
job_class: worker_class.to_s,
|
|
987
636
|
job_id: job['jid'],
|
|
988
637
|
queue: queue,
|
|
@@ -997,89 +646,22 @@ module E11y
|
|
|
997
646
|
end
|
|
998
647
|
```
|
|
999
648
|
|
|
1000
|
-
### 5.3.
|
|
649
|
+
### 5.3. Buffer for Jobs
|
|
1001
650
|
|
|
1002
|
-
**Design Decision:**
|
|
651
|
+
**Design Decision:** Jobs reuse the same `EphemeralBuffer` as HTTP requests. Same semantics: buffer debug events, flush on error, discard on success. Optional job-specific config allows tuning for longer-running jobs.
|
|
1003
652
|
|
|
1004
653
|
```ruby
|
|
1005
654
|
# config/initializers/e11y.rb
|
|
1006
655
|
E11y.configure do |config|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
use_job_buffer true
|
|
1010
|
-
|
|
1011
|
-
job_buffer do
|
|
1012
|
-
# Buffer debug events during job execution
|
|
1013
|
-
buffer_severities [:debug]
|
|
1014
|
-
|
|
1015
|
-
# Flush conditions
|
|
1016
|
-
flush_on do
|
|
1017
|
-
error true # Flush debug events if job fails
|
|
1018
|
-
success false # Discard debug events if job succeeds
|
|
1019
|
-
interval 5.seconds # Or flush every 5 seconds
|
|
1020
|
-
end
|
|
1021
|
-
|
|
1022
|
-
# Max buffer size per job
|
|
1023
|
-
max_events 1000
|
|
1024
|
-
end
|
|
1025
|
-
end
|
|
1026
|
-
end
|
|
1027
|
-
```
|
|
1028
|
-
|
|
1029
|
-
**How it works:**
|
|
1030
|
-
|
|
1031
|
-
```ruby
|
|
1032
|
-
class InvoiceGenerationWorker
|
|
1033
|
-
include Sidekiq::Worker
|
|
656
|
+
# Shared buffer for HTTP and jobs
|
|
657
|
+
config.ephemeral_buffer_enabled = true
|
|
1034
658
|
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
Events::Debug::FetchOrder.track(order_id: order_id)
|
|
1038
|
-
|
|
1039
|
-
order = Order.find(order_id)
|
|
1040
|
-
|
|
1041
|
-
Events::Debug::ValidateOrder.track(order_id: order_id, valid: order.valid?)
|
|
1042
|
-
|
|
1043
|
-
if order.invalid?
|
|
1044
|
-
# Job fails → debug events are flushed to adapters
|
|
1045
|
-
raise "Invalid order"
|
|
1046
|
-
end
|
|
1047
|
-
|
|
1048
|
-
# Job succeeds → debug events are discarded
|
|
1049
|
-
Events::InvoiceGenerated.track(order_id: order_id)
|
|
1050
|
-
end
|
|
659
|
+
# Optional: job-specific overrides (jobs can run longer → more debug events)
|
|
660
|
+
config.ephemeral_buffer_job_buffer_limit = 500 # nil = use default (100)
|
|
1051
661
|
end
|
|
1052
662
|
```
|
|
1053
663
|
|
|
1054
|
-
**
|
|
1055
|
-
|
|
1056
|
-
```mermaid
|
|
1057
|
-
sequenceDiagram
|
|
1058
|
-
participant Worker as Sidekiq Worker
|
|
1059
|
-
participant JobBuffer as Job Buffer
|
|
1060
|
-
participant MainBuffer as Main Buffer
|
|
1061
|
-
participant Adapters as Adapters
|
|
1062
|
-
|
|
1063
|
-
Worker->>JobBuffer: Start job buffer
|
|
1064
|
-
|
|
1065
|
-
Note over Worker: Job execution starts
|
|
1066
|
-
|
|
1067
|
-
Worker->>JobBuffer: Track debug event
|
|
1068
|
-
JobBuffer->>JobBuffer: Buffer (not flushed)
|
|
1069
|
-
|
|
1070
|
-
Worker->>MainBuffer: Track info event
|
|
1071
|
-
MainBuffer->>Adapters: Flush immediately (normal flow)
|
|
1072
|
-
|
|
1073
|
-
alt Job succeeds
|
|
1074
|
-
Worker->>JobBuffer: flush! (success)
|
|
1075
|
-
JobBuffer->>JobBuffer: Discard debug events
|
|
1076
|
-
Note over JobBuffer: Debug events never sent
|
|
1077
|
-
else Job fails
|
|
1078
|
-
Worker->>JobBuffer: flush_on_error!
|
|
1079
|
-
JobBuffer->>MainBuffer: Move debug events to main buffer
|
|
1080
|
-
MainBuffer->>Adapters: Flush all events (including debug)
|
|
1081
|
-
end
|
|
1082
|
-
```
|
|
664
|
+
**Rationale:** Single buffer implementation, single config. Jobs may need higher `job_buffer_limit` when they process many items and emit more debug events than a typical HTTP request.
|
|
1083
665
|
|
|
1084
666
|
---
|
|
1085
667
|
|
|
@@ -1100,21 +682,21 @@ sequenceDiagram
|
|
|
1100
682
|
ClientMW->>ClientMW: Extract trace_id from Current
|
|
1101
683
|
ClientMW->>Redis: Store job + trace metadata
|
|
1102
684
|
|
|
1103
|
-
Note over Redis: job['
|
|
685
|
+
Note over Redis: job['e11y_parent_trace_id'] = 'abc123'<br/>job['e11y_span_id'] = 'span002'<br/>job['e11y_sampled'] = true
|
|
1104
686
|
|
|
1105
687
|
ClientMW->>E11y: Track Enqueued event
|
|
1106
688
|
|
|
1107
689
|
Note over ServerMW: Later... job dequeued
|
|
1108
690
|
|
|
1109
691
|
Redis->>ServerMW: Fetch job
|
|
1110
|
-
ServerMW->>ServerMW:
|
|
692
|
+
ServerMW->>ServerMW: C17: new trace_id, parent_trace_id from job
|
|
1111
693
|
ServerMW->>E11y: Restore Current context
|
|
1112
694
|
|
|
1113
|
-
Note over E11y: Current.trace_id = 'abc123'<br/>Current.parent_span_id = 'span002'
|
|
695
|
+
Note over E11y: Current.trace_id = NEW<br/>Current.parent_trace_id = 'abc123'<br/>Current.parent_span_id = 'span002'
|
|
1114
696
|
|
|
1115
697
|
ServerMW->>E11y: Track Started event
|
|
1116
698
|
ServerMW->>Worker: perform
|
|
1117
|
-
Worker->>E11y: Track
|
|
699
|
+
Worker->>E11y: Track events (linked via parent_trace_id!)
|
|
1118
700
|
ServerMW->>E11y: Track Completed event
|
|
1119
701
|
```
|
|
1120
702
|
|
|
@@ -1122,6 +704,13 @@ sequenceDiagram
|
|
|
1122
704
|
|
|
1123
705
|
## 6. ActiveJob Integration
|
|
1124
706
|
|
|
707
|
+
**Implementation Note:** Job lifecycle events (`Enqueued`, `Started`, `Completed`, `Failed`) come from **RailsInstrumentation** (ASN), not from ActiveJob callbacks. ActiveJob callbacks handle trace context propagation, request-scoped buffer, and SLO tracking only. This design:
|
|
708
|
+
- Uses ASN as single source for all queue adapters (Sidekiq, Resque, Solid Queue, etc.)
|
|
709
|
+
- Avoids duplicate emission when ActiveJob uses Sidekiq as adapter
|
|
710
|
+
- Keeps callbacks focused on context/buffer/SLO
|
|
711
|
+
|
|
712
|
+
**perform.active_job routing:** When payload contains `exception`, RailsInstrumentation routes to `E11y::Events::Rails::Job::Failed` (with `error_class`, `error_message`); otherwise to `Completed`.
|
|
713
|
+
|
|
1125
714
|
### 6.1. Callbacks Integration
|
|
1126
715
|
|
|
1127
716
|
```ruby
|
|
@@ -1140,23 +729,27 @@ module E11y
|
|
|
1140
729
|
private
|
|
1141
730
|
|
|
1142
731
|
def e11y_track_job_execution
|
|
1143
|
-
#
|
|
1144
|
-
|
|
732
|
+
# C17 Hybrid: Job gets NEW trace_id, parent_trace_id links to enqueuer
|
|
733
|
+
parent_trace_id = job_metadata['e11y_parent_trace_id']
|
|
734
|
+
trace_id = E11y::TraceContext.generate_id
|
|
1145
735
|
parent_span_id = job_metadata['e11y_span_id']
|
|
1146
736
|
|
|
1147
737
|
E11y::Current.set(
|
|
1148
738
|
trace_id: trace_id,
|
|
739
|
+
parent_trace_id: parent_trace_id,
|
|
1149
740
|
parent_span_id: parent_span_id,
|
|
1150
741
|
job_id: job_id,
|
|
1151
742
|
job_class: self.class.name
|
|
1152
743
|
)
|
|
1153
744
|
|
|
1154
|
-
# Start
|
|
1155
|
-
if E11y.config.
|
|
1156
|
-
E11y
|
|
745
|
+
# Start request-scoped buffer (same as HTTP; config.ephemeral_buffer_enabled)
|
|
746
|
+
if E11y.config.ephemeral_buffer_enabled
|
|
747
|
+
limit = E11y.config.ephemeral_buffer_job_buffer_limit ||
|
|
748
|
+
E11y::Buffers::EphemeralBuffer::DEFAULT_BUFFER_LIMIT
|
|
749
|
+
E11y::Buffers::EphemeralBuffer.initialize!(buffer_limit: limit)
|
|
1157
750
|
end
|
|
1158
751
|
|
|
1159
|
-
Events::Rails::Job::Started.track(
|
|
752
|
+
E11y::Events::Rails::Job::Started.track(
|
|
1160
753
|
job_class: self.class.name,
|
|
1161
754
|
job_id: job_id,
|
|
1162
755
|
queue_name: queue_name,
|
|
@@ -1168,16 +761,16 @@ module E11y
|
|
|
1168
761
|
begin
|
|
1169
762
|
yield
|
|
1170
763
|
|
|
1171
|
-
Events::Rails::Job::Completed.track(
|
|
764
|
+
E11y::Events::Rails::Job::Completed.track(
|
|
1172
765
|
job_class: self.class.name,
|
|
1173
766
|
job_id: job_id,
|
|
1174
767
|
duration: (Time.now - start_time) * 1000
|
|
1175
768
|
)
|
|
1176
769
|
|
|
1177
|
-
#
|
|
1178
|
-
E11y::
|
|
770
|
+
# Discard buffer on success (same as HTTP)
|
|
771
|
+
E11y::Buffers::EphemeralBuffer.discard if E11y.config.ephemeral_buffer_enabled
|
|
1179
772
|
rescue => error
|
|
1180
|
-
Events::Rails::Job::Failed.track(
|
|
773
|
+
E11y::Events::Rails::Job::Failed.track(
|
|
1181
774
|
job_class: self.class.name,
|
|
1182
775
|
job_id: job_id,
|
|
1183
776
|
duration: (Time.now - start_time) * 1000,
|
|
@@ -1185,8 +778,8 @@ module E11y
|
|
|
1185
778
|
error_message: error.message
|
|
1186
779
|
)
|
|
1187
780
|
|
|
1188
|
-
# Flush
|
|
1189
|
-
E11y::
|
|
781
|
+
# Flush buffer on error (includes debug events)
|
|
782
|
+
E11y::Buffers::EphemeralBuffer.flush_on_error if E11y.config.ephemeral_buffer_enabled
|
|
1190
783
|
|
|
1191
784
|
raise
|
|
1192
785
|
ensure
|
|
@@ -1195,12 +788,12 @@ module E11y
|
|
|
1195
788
|
end
|
|
1196
789
|
|
|
1197
790
|
def e11y_track_job_enqueued
|
|
1198
|
-
# Store trace
|
|
1199
|
-
job_metadata['
|
|
791
|
+
# C17 Hybrid: Store parent trace (job will create NEW trace_id)
|
|
792
|
+
job_metadata['e11y_parent_trace_id'] = E11y::Current.trace_id if E11y::Current.trace_id
|
|
1200
793
|
job_metadata['e11y_span_id'] = E11y::TraceContext.generate_span_id
|
|
1201
794
|
job_metadata['e11y_sampled'] = E11y::Current.sampled
|
|
1202
795
|
|
|
1203
|
-
Events::Rails::Job::Enqueued.track(
|
|
796
|
+
E11y::Events::Rails::Job::Enqueued.track(
|
|
1204
797
|
job_class: self.class.name,
|
|
1205
798
|
job_id: job_id,
|
|
1206
799
|
queue_name: queue_name,
|
|
@@ -1235,7 +828,7 @@ module E11y
|
|
|
1235
828
|
module Logger
|
|
1236
829
|
class Bridge
|
|
1237
830
|
def self.setup!
|
|
1238
|
-
return unless E11y.config.
|
|
831
|
+
return unless E11y.config.logger_bridge_enabled
|
|
1239
832
|
|
|
1240
833
|
# Replace Rails.logger
|
|
1241
834
|
Rails.logger = Bridge.new(Rails.logger)
|
|
@@ -1304,17 +897,10 @@ module E11y
|
|
|
1304
897
|
# Extract message
|
|
1305
898
|
msg = message || (block_given? ? block.call : nil)
|
|
1306
899
|
|
|
1307
|
-
# Track via E11y
|
|
1308
|
-
|
|
1309
|
-
severity: severity,
|
|
1310
|
-
message: msg.to_s,
|
|
1311
|
-
caller_location: extract_caller_location
|
|
1312
|
-
)
|
|
900
|
+
# Track via E11y (filtered by track_severities, ignore_patterns)
|
|
901
|
+
event_class_for_severity(severity).track(message: msg.to_s, caller_location: extract_caller_location)
|
|
1313
902
|
|
|
1314
|
-
#
|
|
1315
|
-
if @original_logger && E11y.config.logger_bridge.dual_logging
|
|
1316
|
-
@original_logger.public_send(severity, msg)
|
|
1317
|
-
end
|
|
903
|
+
# Always delegate to original logger (SimpleDelegator super)
|
|
1318
904
|
end
|
|
1319
905
|
|
|
1320
906
|
def extract_caller_location
|
|
@@ -1330,50 +916,39 @@ module E11y
|
|
|
1330
916
|
end
|
|
1331
917
|
```
|
|
1332
918
|
|
|
1333
|
-
### 7.2.
|
|
919
|
+
### 7.2. Configuration
|
|
1334
920
|
|
|
1335
921
|
```ruby
|
|
1336
922
|
# config/initializers/e11y.rb
|
|
1337
923
|
E11y.configure do |config|
|
|
1338
|
-
config.
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
/Started GET/,
|
|
1350
|
-
/Completed \d+ OK/,
|
|
1351
|
-
/CACHE/
|
|
1352
|
-
]
|
|
1353
|
-
|
|
1354
|
-
# Sample high-volume logs
|
|
1355
|
-
sample_rate 0.1 # 10% of logs
|
|
1356
|
-
|
|
1357
|
-
# Enrich with Rails context
|
|
1358
|
-
enrich_with_context true
|
|
1359
|
-
context_fields [:controller, :action, :request_id]
|
|
1360
|
-
end
|
|
924
|
+
config.logger_bridge_enabled = true
|
|
925
|
+
|
|
926
|
+
# Which severities to track (nil = all)
|
|
927
|
+
config.logger_bridge_track_severities = [:info, :warn, :error, :fatal]
|
|
928
|
+
|
|
929
|
+
# Skip noisy log messages (regex or string)
|
|
930
|
+
config.logger_bridge_ignore_patterns = [
|
|
931
|
+
/Started GET/,
|
|
932
|
+
/Completed \d+ OK/,
|
|
933
|
+
/CACHE/
|
|
934
|
+
]
|
|
1361
935
|
end
|
|
1362
936
|
```
|
|
1363
937
|
|
|
938
|
+
**Context:** Events are enriched with `trace_id`, `request_id`, `span_id`, `user_id` from `E11y::Current` via `Event::Base.build_context` — no separate config needed.
|
|
939
|
+
|
|
1364
940
|
---
|
|
1365
941
|
|
|
1366
942
|
## 8. Middleware Integration
|
|
1367
943
|
|
|
1368
|
-
### 8.0.
|
|
944
|
+
### 8.0. Buffer Types (Summary)
|
|
1369
945
|
|
|
1370
|
-
**E11y
|
|
946
|
+
**E11y uses a single EphemeralBuffer for both HTTP and jobs:**
|
|
1371
947
|
|
|
1372
|
-
| Buffer
|
|
1373
|
-
|
|
1374
|
-
| **
|
|
1375
|
-
| **
|
|
1376
|
-
| **Job Buffer** | Debug events in background jobs | Per-job, flush on error | Background job debugging |
|
|
948
|
+
| Buffer | Purpose | Lifecycle | Config |
|
|
949
|
+
|--------|---------|-----------|--------|
|
|
950
|
+
| **Request Buffer** | Debug events (HTTP + jobs) | Per-request/job, flush on error, discard on success | `config.ephemeral_buffer_enabled`, `ephemeral_buffer_job_buffer_limit` |
|
|
951
|
+
| **Main Buffer** | All events (info+) | Global, flush every 200ms | — |
|
|
1377
952
|
|
|
1378
953
|
**Diagram:**
|
|
1379
954
|
|
|
@@ -1381,20 +956,20 @@ end
|
|
|
1381
956
|
graph TB
|
|
1382
957
|
subgraph "HTTP Request"
|
|
1383
958
|
HTTPEvent[Event tracked] --> Decision1{Severity?}
|
|
1384
|
-
Decision1 -->|:debug|
|
|
959
|
+
Decision1 -->|:debug| [Request Buffer]
|
|
1385
960
|
Decision1 -->|:info+| MainBuffer[Main Buffer]
|
|
1386
961
|
|
|
1387
|
-
|
|
962
|
+
--> OnError1{Request failed?}
|
|
1388
963
|
OnError1 -->|Yes| MainBuffer
|
|
1389
964
|
OnError1 -->|No| Discard1[Discard]
|
|
1390
965
|
end
|
|
1391
966
|
|
|
1392
967
|
subgraph "Background Job"
|
|
1393
968
|
JobEvent[Event tracked] --> Decision2{Severity?}
|
|
1394
|
-
Decision2 -->|:debug|
|
|
969
|
+
Decision2 -->|:debug|
|
|
1395
970
|
Decision2 -->|:info+| MainBuffer2[Main Buffer]
|
|
1396
971
|
|
|
1397
|
-
|
|
972
|
+
--> OnError2{Job failed?}
|
|
1398
973
|
OnError2 -->|Yes| MainBuffer2
|
|
1399
974
|
OnError2 -->|No| Discard2[Discard]
|
|
1400
975
|
end
|
|
@@ -1405,8 +980,7 @@ graph TB
|
|
|
1405
980
|
Interval --> Adapters[Flush to Adapters]
|
|
1406
981
|
end
|
|
1407
982
|
|
|
1408
|
-
style
|
|
1409
|
-
style JobBuffer fill:#d4edda
|
|
983
|
+
style fill:#fff3cd
|
|
1410
984
|
style MainBuffer fill:#d1ecf1
|
|
1411
985
|
style MainBuffer2 fill:#d1ecf1
|
|
1412
986
|
```
|
|
@@ -1415,134 +989,17 @@ graph TB
|
|
|
1415
989
|
|
|
1416
990
|
```ruby
|
|
1417
991
|
E11y.configure do |config|
|
|
1418
|
-
#
|
|
1419
|
-
config.
|
|
1420
|
-
config.
|
|
1421
|
-
|
|
1422
|
-
# Request-scoped buffer (HTTP only)
|
|
1423
|
-
config.instruments.rack_middleware.use_request_buffer = true
|
|
1424
|
-
|
|
1425
|
-
# Job-scoped buffer (Sidekiq + ActiveJob)
|
|
1426
|
-
config.instruments.sidekiq.use_job_buffer = true
|
|
1427
|
-
config.instruments.active_job.use_job_buffer = true
|
|
992
|
+
# Request-scoped buffer (shared for HTTP and jobs)
|
|
993
|
+
config.ephemeral_buffer_enabled = true
|
|
994
|
+
config.ephemeral_buffer_job_buffer_limit = 500 # Optional: higher limit for jobs (nil = default 100)
|
|
1428
995
|
end
|
|
1429
996
|
```
|
|
1430
997
|
|
|
1431
998
|
---
|
|
1432
999
|
|
|
1433
|
-
### 8.1. Request
|
|
1000
|
+
### 8.1. Request middleware
|
|
1434
1001
|
|
|
1435
|
-
|
|
1436
|
-
# lib/e11y/middleware/request.rb
|
|
1437
|
-
module E11y
|
|
1438
|
-
module Middleware
|
|
1439
|
-
class Request
|
|
1440
|
-
def initialize(app)
|
|
1441
|
-
@app = app
|
|
1442
|
-
end
|
|
1443
|
-
|
|
1444
|
-
def call(env)
|
|
1445
|
-
request = Rack::Request.new(env)
|
|
1446
|
-
|
|
1447
|
-
# Extract or generate trace_id
|
|
1448
|
-
trace_id = extract_trace_id(request) || TraceContext.generate_id
|
|
1449
|
-
span_id = TraceContext.generate_span_id
|
|
1450
|
-
|
|
1451
|
-
# Set context
|
|
1452
|
-
Current.set(
|
|
1453
|
-
trace_id: trace_id,
|
|
1454
|
-
span_id: span_id,
|
|
1455
|
-
request_id: request_id(env),
|
|
1456
|
-
user_id: extract_user_id(env),
|
|
1457
|
-
ip_address: request.ip,
|
|
1458
|
-
user_agent: request.user_agent
|
|
1459
|
-
)
|
|
1460
|
-
|
|
1461
|
-
# Start request-scoped buffer (for debug events)
|
|
1462
|
-
# Note: This is ONLY for HTTP requests, not for jobs
|
|
1463
|
-
# Jobs have their own JobBuffer (see Sidekiq/ActiveJob sections)
|
|
1464
|
-
if E11y.config.instruments.rack_middleware.use_request_buffer
|
|
1465
|
-
E11y::RequestBuffer.start!
|
|
1466
|
-
end
|
|
1467
|
-
|
|
1468
|
-
# Track request start
|
|
1469
|
-
start_time = Time.now
|
|
1470
|
-
|
|
1471
|
-
Events::Http::RequestStarted.track(
|
|
1472
|
-
method: request.request_method,
|
|
1473
|
-
path: request.path,
|
|
1474
|
-
query: request.query_string,
|
|
1475
|
-
format: request.format
|
|
1476
|
-
)
|
|
1477
|
-
|
|
1478
|
-
begin
|
|
1479
|
-
status, headers, body = @app.call(env)
|
|
1480
|
-
|
|
1481
|
-
# Track request completed
|
|
1482
|
-
Events::Http::RequestCompleted.track(
|
|
1483
|
-
method: request.request_method,
|
|
1484
|
-
path: request.path,
|
|
1485
|
-
status: status,
|
|
1486
|
-
duration: (Time.now - start_time) * 1000
|
|
1487
|
-
)
|
|
1488
|
-
|
|
1489
|
-
# Add trace headers to response
|
|
1490
|
-
headers['X-E11y-Trace-Id'] = trace_id
|
|
1491
|
-
headers['X-E11y-Span-Id'] = span_id
|
|
1492
|
-
|
|
1493
|
-
[status, headers, body]
|
|
1494
|
-
rescue => error
|
|
1495
|
-
# Track request failed
|
|
1496
|
-
Events::Http::RequestFailed.track(
|
|
1497
|
-
method: request.request_method,
|
|
1498
|
-
path: request.path,
|
|
1499
|
-
duration: (Time.now - start_time) * 1000,
|
|
1500
|
-
error_class: error.class.name,
|
|
1501
|
-
error_message: error.message
|
|
1502
|
-
)
|
|
1503
|
-
|
|
1504
|
-
# Flush request buffer (includes debug events on error)
|
|
1505
|
-
if E11y.config.instruments.rack_middleware.use_request_buffer
|
|
1506
|
-
E11y::RequestBuffer.flush_on_error!
|
|
1507
|
-
end
|
|
1508
|
-
|
|
1509
|
-
raise
|
|
1510
|
-
ensure
|
|
1511
|
-
# Flush request buffer (success case)
|
|
1512
|
-
if E11y.config.instruments.rack_middleware.use_request_buffer && !error
|
|
1513
|
-
E11y::RequestBuffer.flush!
|
|
1514
|
-
end
|
|
1515
|
-
|
|
1516
|
-
# Reset context
|
|
1517
|
-
Current.reset
|
|
1518
|
-
end
|
|
1519
|
-
end
|
|
1520
|
-
|
|
1521
|
-
private
|
|
1522
|
-
|
|
1523
|
-
def extract_trace_id(request)
|
|
1524
|
-
# W3C Trace Context
|
|
1525
|
-
request.get_header('HTTP_TRACEPARENT')&.split('-')&.[](1) ||
|
|
1526
|
-
# X-Request-ID
|
|
1527
|
-
request.get_header('HTTP_X_REQUEST_ID') ||
|
|
1528
|
-
# X-Trace-Id
|
|
1529
|
-
request.get_header('HTTP_X_TRACE_ID')
|
|
1530
|
-
end
|
|
1531
|
-
|
|
1532
|
-
def request_id(env)
|
|
1533
|
-
env['action_dispatch.request_id'] || SecureRandom.uuid
|
|
1534
|
-
end
|
|
1535
|
-
|
|
1536
|
-
def extract_user_id(env)
|
|
1537
|
-
# Try to extract from Warden (Devise)
|
|
1538
|
-
env['warden']&.user&.id ||
|
|
1539
|
-
# Try to extract from session
|
|
1540
|
-
env['rack.session']&.[]('user_id')
|
|
1541
|
-
end
|
|
1542
|
-
end
|
|
1543
|
-
end
|
|
1544
|
-
end
|
|
1545
|
-
```
|
|
1002
|
+
**Source of truth:** `lib/e11y/middleware/request.rb`. Earlier ADR excerpts used `Current.set`, non-existent `Events::Http::*` events, and a simplified error path—the real middleware wires W3C / fallback trace IDs, `E11y::Current`, optional `E11y::Buffers::EphemeralBuffer`, response status–based buffer flush, optional HTTP SLO tracking, and response headers.
|
|
1546
1003
|
|
|
1547
1004
|
---
|
|
1548
1005
|
|
|
@@ -1563,10 +1020,10 @@ module E11y
|
|
|
1563
1020
|
# E11y.stats
|
|
1564
1021
|
def E11y.stats
|
|
1565
1022
|
{
|
|
1566
|
-
events_tracked: Registry.
|
|
1023
|
+
events_tracked: Registry.event_classes.sum { |e| e.track_count },
|
|
1567
1024
|
events_in_buffer: Buffer.size,
|
|
1568
|
-
adapters:
|
|
1569
|
-
{ name:
|
|
1025
|
+
adapters: config.adapters.map { |name, a|
|
|
1026
|
+
{ name: name, healthy: a.respond_to?(:healthy?) ? a.healthy? : true }
|
|
1570
1027
|
},
|
|
1571
1028
|
rate_limiter: {
|
|
1572
1029
|
current_rate: RateLimiter.current_rate,
|
|
@@ -1588,17 +1045,17 @@ module E11y
|
|
|
1588
1045
|
|
|
1589
1046
|
# E11y.events
|
|
1590
1047
|
def E11y.events
|
|
1591
|
-
Registry.
|
|
1048
|
+
Registry.event_classes.map(&:name).sort
|
|
1592
1049
|
end
|
|
1593
1050
|
|
|
1594
1051
|
# E11y.adapters
|
|
1595
1052
|
def E11y.adapters
|
|
1596
|
-
|
|
1053
|
+
config.adapters.map do |name, adapter|
|
|
1597
1054
|
{
|
|
1598
|
-
name:
|
|
1055
|
+
name: name,
|
|
1599
1056
|
class: adapter.class.name,
|
|
1600
|
-
healthy: adapter.healthy
|
|
1601
|
-
capabilities: adapter.capabilities
|
|
1057
|
+
healthy: adapter.respond_to?(:healthy?) ? adapter.healthy? : true,
|
|
1058
|
+
capabilities: adapter.respond_to?(:capabilities) ? adapter.capabilities : {}
|
|
1602
1059
|
}
|
|
1603
1060
|
end
|
|
1604
1061
|
end
|
|
@@ -1606,7 +1063,7 @@ module E11y
|
|
|
1606
1063
|
# E11y.reset!
|
|
1607
1064
|
def E11y.reset!
|
|
1608
1065
|
Buffer.clear!
|
|
1609
|
-
|
|
1066
|
+
.clear!
|
|
1610
1067
|
puts "✅ Buffers cleared"
|
|
1611
1068
|
end
|
|
1612
1069
|
end
|
|
@@ -1621,7 +1078,7 @@ module E11y
|
|
|
1621
1078
|
)
|
|
1622
1079
|
|
|
1623
1080
|
# Disable rate limiting in console
|
|
1624
|
-
config.
|
|
1081
|
+
config.rate_limiting_enabled = false
|
|
1625
1082
|
|
|
1626
1083
|
# Show all severities
|
|
1627
1084
|
config.severity_threshold = :debug
|
|
@@ -1772,7 +1229,7 @@ RSpec.describe OrdersController, type: :controller do
|
|
|
1772
1229
|
}.to have_enqueued_job(SendOrderEmailJob)
|
|
1773
1230
|
|
|
1774
1231
|
job = ActiveJob::Base.queue_adapter.enqueued_jobs.last
|
|
1775
|
-
expect(job[:args].first['
|
|
1232
|
+
expect(job[:args].first['e11y_parent_trace_id']).to be_present
|
|
1776
1233
|
end
|
|
1777
1234
|
end
|
|
1778
1235
|
end
|
|
@@ -1819,90 +1276,72 @@ end
|
|
|
1819
1276
|
|
|
1820
1277
|
```ruby
|
|
1821
1278
|
E11y.configure do |config|
|
|
1822
|
-
config.
|
|
1823
|
-
config.
|
|
1824
|
-
config.
|
|
1825
|
-
config.
|
|
1279
|
+
config.rails_instrumentation_enabled = false # Disable ASN
|
|
1280
|
+
config.sidekiq_enabled = true # Keep Sidekiq
|
|
1281
|
+
config.active_job_enabled = true # Keep ActiveJob
|
|
1282
|
+
config.logger_bridge_enabled = true # Keep logger bridge
|
|
1826
1283
|
end
|
|
1827
1284
|
```
|
|
1828
1285
|
|
|
1829
1286
|
### Q2: Do you provide built-in event classes for Rails?
|
|
1830
1287
|
|
|
1831
|
-
**Yes!** E11y includes `Events::Rails` namespace with common Rails events:
|
|
1288
|
+
**Yes!** E11y includes the `E11y::Events::Rails` namespace with common Rails events:
|
|
1832
1289
|
|
|
1833
|
-
- `Events::Rails::Database::Query` (sql.active_record)
|
|
1834
|
-
- `Events::Rails::Http::Request` (process_action.action_controller)
|
|
1835
|
-
- `Events::Rails::Cache::Read/Write/Delete`
|
|
1836
|
-
- `Events::Rails::Job::Enqueued/Started/Completed/Failed`
|
|
1290
|
+
- `E11y::Events::Rails::Database::Query` (sql.active_record)
|
|
1291
|
+
- `E11y::Events::Rails::Http::Request` (process_action.action_controller)
|
|
1292
|
+
- `E11y::Events::Rails::Cache::Read/Write/Delete`
|
|
1293
|
+
- `E11y::Events::Rails::Job::Enqueued/Started/Completed/Failed`
|
|
1837
1294
|
|
|
1838
1295
|
**You can:**
|
|
1839
1296
|
- Use them as-is (default)
|
|
1840
|
-
- Override
|
|
1841
|
-
-
|
|
1297
|
+
- Override selected patterns via `config.rails_instrumentation_custom_mappings`
|
|
1298
|
+
- Skip patterns via `config.rails_instrumentation_ignore_events`
|
|
1299
|
+
- Turn the whole subscriber off with `config.rails_instrumentation_enabled = false`
|
|
1842
1300
|
|
|
1843
1301
|
### Q3: Is ActiveSupport::Notifications integration always on?
|
|
1844
1302
|
|
|
1845
|
-
**No
|
|
1303
|
+
**No.** Toggle `rails_instrumentation_enabled`. Ignore individual ASN names by appending to `rails_instrumentation_ignore_events` (exact string match to the subscription key, e.g. `"render_template.action_view"`).
|
|
1846
1304
|
|
|
1847
1305
|
```ruby
|
|
1848
|
-
|
|
1849
|
-
|
|
1306
|
+
E11y.configure do |config|
|
|
1307
|
+
config.rails_instrumentation_enabled = false
|
|
1850
1308
|
end
|
|
1851
|
-
```
|
|
1852
1309
|
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
```ruby
|
|
1856
|
-
config.instruments.active_support_notifications do
|
|
1857
|
-
track_patterns ['sql.active_record', 'process_action.*']
|
|
1858
|
-
ignore_patterns ['render_partial.*', 'SCHEMA']
|
|
1310
|
+
E11y.configure do |config|
|
|
1311
|
+
config.rails_instrumentation_ignore_events << "render_template.action_view"
|
|
1859
1312
|
end
|
|
1860
1313
|
```
|
|
1861
1314
|
|
|
1862
1315
|
### Q4: Does request-scoped buffer work for Sidekiq/ActiveJob?
|
|
1863
1316
|
|
|
1864
|
-
**
|
|
1317
|
+
**Yes. HTTP and jobs share the same EphemeralBuffer.**
|
|
1865
1318
|
|
|
1866
|
-
- **Request Buffer** → HTTP requests
|
|
1867
|
-
- **Job Buffer** → Sidekiq + ActiveJob (separate buffer per job)
|
|
1319
|
+
- **Request Buffer** → HTTP requests and jobs (same buffer, same semantics)
|
|
1868
1320
|
- **Main Buffer** → Global buffer for all info+ events
|
|
1869
1321
|
|
|
1870
|
-
|
|
1322
|
+
Config:
|
|
1871
1323
|
|
|
1872
1324
|
```ruby
|
|
1873
|
-
config.
|
|
1874
|
-
config.
|
|
1875
|
-
config.instruments.active_job.use_job_buffer = true # ActiveJob
|
|
1325
|
+
config.ephemeral_buffer_enabled = true
|
|
1326
|
+
config.ephemeral_buffer_job_buffer_limit = 500 # Optional: higher limit for jobs (nil = default 100)
|
|
1876
1327
|
```
|
|
1877
1328
|
|
|
1878
1329
|
### Q5: How do I customize built-in Rails events?
|
|
1879
1330
|
|
|
1880
|
-
**
|
|
1331
|
+
**Override one mapping:**
|
|
1881
1332
|
|
|
1882
1333
|
```ruby
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
end
|
|
1334
|
+
E11y.configure do |config|
|
|
1335
|
+
config.rails_instrumentation_custom_mappings["sql.active_record"] =
|
|
1336
|
+
MyApp::Events::CustomDatabaseQuery
|
|
1887
1337
|
end
|
|
1888
1338
|
```
|
|
1889
1339
|
|
|
1890
|
-
**
|
|
1891
|
-
|
|
1892
|
-
```ruby
|
|
1893
|
-
config.instruments.active_support_notifications do
|
|
1894
|
-
use_built_in_events false # No automatic mapping
|
|
1895
|
-
|
|
1896
|
-
# Manually handle ASN events
|
|
1897
|
-
enrich do |asn_event|
|
|
1898
|
-
MyCustomHandler.call(asn_event)
|
|
1899
|
-
end
|
|
1900
|
-
end
|
|
1901
|
-
```
|
|
1340
|
+
**Disable automatic ASN → E11y conversion:** set `rails_instrumentation_enabled = false` and subscribe to `ActiveSupport::Notifications` yourself if you need a custom pipeline.
|
|
1902
1341
|
|
|
1903
1342
|
### Q6: Can I use E11y without Rails?
|
|
1904
1343
|
|
|
1905
|
-
|
|
1344
|
+
The supported path is **Rails 7.0+** (see gemspec). Non-Rails usage is not a supported integration surface today.
|
|
1906
1345
|
|
|
1907
1346
|
---
|
|
1908
1347
|
|