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
data/lib/e11y/event/base.rb
CHANGED
|
@@ -81,22 +81,38 @@ module E11y
|
|
|
81
81
|
# - Auto-calculated retention_until from retention_period
|
|
82
82
|
#
|
|
83
83
|
# @param payload [Hash] Event data matching the schema
|
|
84
|
+
# @yield Optional block — measured for duration; adds :duration_ms to payload
|
|
84
85
|
# @return [Hash] Event hash (includes metadata)
|
|
85
86
|
#
|
|
86
|
-
# @example
|
|
87
|
+
# @example Without block
|
|
87
88
|
# UserSignupEvent.track(user_id: 123, email: "user@example.com")
|
|
88
89
|
# # => { event_name: "UserSignupEvent", payload: {...}, severity: :info, adapters: [:logs], ... }
|
|
89
90
|
#
|
|
91
|
+
# @example With block (duration measurement)
|
|
92
|
+
# Events::OrderPaid.track(order_id: '123') { ExternalPaymentService.charge! }
|
|
93
|
+
# # => payload includes duration_ms automatically
|
|
94
|
+
#
|
|
90
95
|
# @raise [E11y::ValidationError] if payload doesn't match schema (when validation runs)
|
|
91
|
-
def track(**payload)
|
|
96
|
+
def track(**payload, &block)
|
|
92
97
|
return unless E11y.config.enabled
|
|
93
98
|
|
|
99
|
+
# Block form: execute block, measure duration, capture return value
|
|
100
|
+
block_result = nil
|
|
101
|
+
if block
|
|
102
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
|
103
|
+
block_result = yield
|
|
104
|
+
payload = payload.merge(duration_ms: Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - start)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Severity: payload override (e.g. exception → :error) or class default
|
|
108
|
+
resolved_severity = payload[:severity] || payload["severity"] || severity
|
|
109
|
+
|
|
94
110
|
# Build event data hash for pipeline processing
|
|
95
111
|
event_data = {
|
|
96
112
|
event_class: self,
|
|
97
113
|
event_name: event_name,
|
|
98
114
|
payload: payload,
|
|
99
|
-
severity:
|
|
115
|
+
severity: resolved_severity,
|
|
100
116
|
version: version,
|
|
101
117
|
adapters: adapters,
|
|
102
118
|
timestamp: Time.now.utc,
|
|
@@ -109,8 +125,8 @@ module E11y
|
|
|
109
125
|
# Routing middleware is the LAST middleware and it writes to adapters directly
|
|
110
126
|
E11y.config.built_pipeline.call(event_data)
|
|
111
127
|
|
|
112
|
-
#
|
|
113
|
-
event_data
|
|
128
|
+
# With block: return block's result (caller cares about it); without: return event_data
|
|
129
|
+
block ? block_result : event_data
|
|
114
130
|
end
|
|
115
131
|
|
|
116
132
|
# Build event hash
|
|
@@ -240,9 +256,7 @@ module E11y
|
|
|
240
256
|
# end
|
|
241
257
|
def severity(value = nil)
|
|
242
258
|
if value
|
|
243
|
-
unless SEVERITIES.include?(value)
|
|
244
|
-
raise ArgumentError, "Invalid severity: #{value}. Must be one of: #{SEVERITIES.join(', ')}"
|
|
245
|
-
end
|
|
259
|
+
raise ArgumentError, "Invalid severity: #{value}. Must be one of: #{SEVERITIES.join(', ')}" unless SEVERITIES.include?(value)
|
|
246
260
|
|
|
247
261
|
@severity = value
|
|
248
262
|
end
|
|
@@ -263,13 +277,15 @@ module E11y
|
|
|
263
277
|
# class OrderPaidEventV2 < E11y::Event::Base
|
|
264
278
|
# version 2
|
|
265
279
|
# end
|
|
280
|
+
VERSION_REGEX = /V(\d+)$/
|
|
281
|
+
|
|
266
282
|
def version(value = nil)
|
|
267
283
|
@version = value if value
|
|
268
|
-
# Return explicitly set version OR inherit from parent (if set) OR default to 1
|
|
269
284
|
return @version if @version
|
|
270
|
-
return superclass.version if superclass != E11y::Event::Base && superclass.instance_variable_get(:@version)
|
|
271
285
|
|
|
272
|
-
|
|
286
|
+
# Auto-extract from class name (e.g. OrderPaidV2 → 2)
|
|
287
|
+
match = name&.match(VERSION_REGEX)
|
|
288
|
+
match ? match[1].to_i : 1
|
|
273
289
|
end
|
|
274
290
|
|
|
275
291
|
# Set or get retention period for this event
|
|
@@ -299,14 +315,15 @@ module E11y
|
|
|
299
315
|
@retention_period = value if value
|
|
300
316
|
# Return explicitly set retention_period OR inherit from parent (if set) OR config default OR final fallback
|
|
301
317
|
return @retention_period if @retention_period
|
|
302
|
-
if superclass != E11y::Event::Base && superclass.instance_variable_get(:@retention_period)
|
|
303
|
-
return superclass.retention_period
|
|
304
|
-
end
|
|
318
|
+
return superclass.retention_period if superclass != E11y::Event::Base && superclass.instance_variable_get(:@retention_period)
|
|
305
319
|
|
|
306
320
|
# Fallback to configuration or 30 days
|
|
307
321
|
E11y.configuration&.default_retention_period || 30.days
|
|
308
322
|
end
|
|
309
323
|
|
|
324
|
+
# Convenience alias — matches Quick Start documentation.
|
|
325
|
+
alias retention retention_period
|
|
326
|
+
|
|
310
327
|
# Set or get adapters for this event
|
|
311
328
|
#
|
|
312
329
|
# Adapters are referenced by NAME (e.g., :logs, :errors_tracker).
|
|
@@ -331,16 +348,42 @@ module E11y
|
|
|
331
348
|
return @adapters if @adapters
|
|
332
349
|
return superclass.adapters if superclass != E11y::Event::Base && superclass.instance_variable_get(:@adapters)
|
|
333
350
|
|
|
351
|
+
# No explicit adapters: inherit from parent or resolve from severity
|
|
352
|
+
# (audit events and regular events both use severity-based mapping)
|
|
334
353
|
resolved_adapters
|
|
335
354
|
end
|
|
336
355
|
|
|
337
|
-
# Get event name (normalized)
|
|
356
|
+
# Get or set event name (normalized)
|
|
338
357
|
#
|
|
339
|
-
#
|
|
358
|
+
# When called with a value, stores it and auto-registers the class in `E11y::Registry`.
|
|
359
|
+
# When called without a value, derives the name from the class name (stripping version suffix).
|
|
340
360
|
#
|
|
341
|
-
# @
|
|
361
|
+
# @param value [String, Symbol, nil] Explicit event name to set, or nil to read
|
|
362
|
+
# @return [String] Event name
|
|
363
|
+
#
|
|
364
|
+
# @example Explicit name
|
|
365
|
+
# class OrderPaidEvent < E11y::Event::Base
|
|
366
|
+
# event_name "order.paid"
|
|
367
|
+
# end
|
|
368
|
+
#
|
|
369
|
+
# @example Auto-derived name
|
|
342
370
|
# OrderPaidEventV2.event_name # => "OrderPaidEvent"
|
|
343
|
-
def event_name
|
|
371
|
+
def event_name(value = nil)
|
|
372
|
+
if value
|
|
373
|
+
@event_name = value.to_s
|
|
374
|
+
@event_name_explicit = true
|
|
375
|
+
# Auto-register in E11y::Registry when an explicit name is set.
|
|
376
|
+
# Guard with defined? so that loading order does not matter.
|
|
377
|
+
# NOTE: call register AFTER setting @event_name_explicit so that any
|
|
378
|
+
# re-entrant call to event_name (from Registry#register) returns the
|
|
379
|
+
# correct value instead of falling through to the auto-derive path.
|
|
380
|
+
E11y::Registry.register(self) if defined?(E11y::Registry)
|
|
381
|
+
return @event_name
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# Return explicitly-set name unconditionally (works for anonymous classes too)
|
|
385
|
+
return @event_name if @event_name_explicit
|
|
386
|
+
|
|
344
387
|
# Don't cache for anonymous classes (name returns nil)
|
|
345
388
|
return @event_name if @event_name && name
|
|
346
389
|
|
|
@@ -365,7 +408,6 @@ module E11y
|
|
|
365
408
|
# class CriticalEvent < E11y::Event::Base
|
|
366
409
|
# sample_rate 1.0 # 100% sampling
|
|
367
410
|
# end
|
|
368
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
|
369
411
|
def sample_rate(value = nil)
|
|
370
412
|
if value
|
|
371
413
|
unless value.is_a?(Numeric) && value >= 0.0 && value <= 1.0
|
|
@@ -377,13 +419,10 @@ module E11y
|
|
|
377
419
|
|
|
378
420
|
# Return explicitly set sample_rate OR inherit from parent (if set) OR nil (use resolve_sample_rate)
|
|
379
421
|
return @sample_rate if @sample_rate
|
|
380
|
-
if superclass != E11y::Event::Base && superclass.instance_variable_get(:@sample_rate)
|
|
381
|
-
return superclass.sample_rate
|
|
382
|
-
end
|
|
422
|
+
return superclass.sample_rate if superclass != E11y::Event::Base && superclass.instance_variable_get(:@sample_rate)
|
|
383
423
|
|
|
384
424
|
nil
|
|
385
425
|
end
|
|
386
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
|
387
426
|
|
|
388
427
|
# Configure value-based sampling (FEAT-4849)
|
|
389
428
|
#
|
|
@@ -460,9 +499,7 @@ module E11y
|
|
|
460
499
|
|
|
461
500
|
# Return explicitly set config OR inherit from parent (if set) OR nil
|
|
462
501
|
return @adaptive_sampling if @adaptive_sampling
|
|
463
|
-
if superclass != E11y::Event::Base && superclass.instance_variable_get(:@adaptive_sampling)
|
|
464
|
-
return superclass.adaptive_sampling
|
|
465
|
-
end
|
|
502
|
+
return superclass.adaptive_sampling if superclass != E11y::Event::Base && superclass.instance_variable_get(:@adaptive_sampling)
|
|
466
503
|
|
|
467
504
|
nil
|
|
468
505
|
end
|
|
@@ -476,12 +513,36 @@ module E11y
|
|
|
476
513
|
def resolve_rate_limit
|
|
477
514
|
case severity
|
|
478
515
|
when :error, :fatal
|
|
479
|
-
nil # Unlimited -
|
|
516
|
+
nil # Unlimited - never drop errors
|
|
480
517
|
else
|
|
481
518
|
1000 # 1000 events/sec
|
|
482
519
|
end
|
|
483
520
|
end
|
|
484
521
|
|
|
522
|
+
# Set a per-event-class rate limit for the RateLimiting middleware.
|
|
523
|
+
#
|
|
524
|
+
# Overrides the global rate limit for events of this class.
|
|
525
|
+
# error/fatal events are always exempt (never rate-limited).
|
|
526
|
+
#
|
|
527
|
+
# @param count [Integer] Max events allowed per window
|
|
528
|
+
# @param window [Numeric, ActiveSupport::Duration] Time window in seconds (default: 1.0)
|
|
529
|
+
#
|
|
530
|
+
# @example Strict limit for login failures (brute-force protection)
|
|
531
|
+
# class Events::UserLoginFailed < E11y::Event::Base
|
|
532
|
+
# rate_limit 100, window: 60
|
|
533
|
+
# end
|
|
534
|
+
def rate_limit(count, window: 1.0)
|
|
535
|
+
@rate_limit_count = count
|
|
536
|
+
@rate_limit_window = window.to_f
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
# Per-event rate limit configuration.
|
|
540
|
+
#
|
|
541
|
+
# @return [Hash] { count: Integer|nil, window: Float|nil }
|
|
542
|
+
def rate_limit_config
|
|
543
|
+
{ count: @rate_limit_count, window: @rate_limit_window }
|
|
544
|
+
end
|
|
545
|
+
|
|
485
546
|
private
|
|
486
547
|
|
|
487
548
|
# Determine if validation should run for this event
|
|
@@ -581,21 +642,21 @@ module E11y
|
|
|
581
642
|
# end
|
|
582
643
|
def contains_pii(value = nil)
|
|
583
644
|
if value.nil?
|
|
584
|
-
|
|
645
|
+
return superclass.contains_pii if !instance_variable_defined?(:@contains_pii) && superclass.respond_to?(:contains_pii)
|
|
646
|
+
|
|
585
647
|
@contains_pii
|
|
586
648
|
else
|
|
587
|
-
# Setter
|
|
588
649
|
@contains_pii = value
|
|
589
650
|
end
|
|
590
651
|
end
|
|
591
652
|
|
|
592
|
-
#
|
|
593
|
-
# @return [Symbol] :
|
|
594
|
-
def
|
|
653
|
+
# PII filtering mode for this event.
|
|
654
|
+
# @return [Symbol] :no_pii, :rails_filters, or :explicit_pii
|
|
655
|
+
def pii_filtering_mode
|
|
595
656
|
case contains_pii
|
|
596
|
-
when false then :
|
|
597
|
-
when true then :
|
|
598
|
-
else :
|
|
657
|
+
when false then :no_pii
|
|
658
|
+
when true then :explicit_pii
|
|
659
|
+
else :rails_filters # Default if not explicitly declared
|
|
599
660
|
end
|
|
600
661
|
end
|
|
601
662
|
|
|
@@ -610,15 +671,22 @@ module E11y
|
|
|
610
671
|
# allows :user_id, :amount
|
|
611
672
|
# end
|
|
612
673
|
def pii_filtering(&)
|
|
613
|
-
@pii_filtering_config
|
|
674
|
+
if @pii_filtering_config.nil?
|
|
675
|
+
parent_config = superclass.respond_to?(:pii_filtering_config) && superclass.pii_filtering_config
|
|
676
|
+
@pii_filtering_config = parent_config ? { fields: parent_config[:fields].dup } : { fields: {} }
|
|
677
|
+
end
|
|
614
678
|
builder = PIIFilteringBuilder.new(@pii_filtering_config)
|
|
615
679
|
builder.instance_eval(&)
|
|
616
680
|
end
|
|
617
681
|
|
|
618
|
-
# Get PII filtering configuration
|
|
682
|
+
# Get PII filtering configuration (inherits from superclass if not defined)
|
|
619
683
|
#
|
|
620
|
-
# @return [Hash] PII filtering config
|
|
621
|
-
|
|
684
|
+
# @return [Hash, nil] PII filtering config
|
|
685
|
+
def pii_filtering_config
|
|
686
|
+
return @pii_filtering_config if instance_variable_defined?(:@pii_filtering_config) && @pii_filtering_config
|
|
687
|
+
|
|
688
|
+
superclass.pii_filtering_config if superclass.respond_to?(:pii_filtering_config)
|
|
689
|
+
end
|
|
622
690
|
|
|
623
691
|
# PII Filtering DSL Builder
|
|
624
692
|
#
|
|
@@ -666,6 +734,30 @@ module E11y
|
|
|
666
734
|
def allows(*fields)
|
|
667
735
|
fields.each { |field| @config[:fields][field] = { strategy: :allow } }
|
|
668
736
|
end
|
|
737
|
+
|
|
738
|
+
# Per-field config with exclude_adapters (Tier 3 per-adapter filtering).
|
|
739
|
+
#
|
|
740
|
+
# @param field [Symbol] Field name
|
|
741
|
+
# @yield Block with strategy, exclude_adapters
|
|
742
|
+
# @example
|
|
743
|
+
# field :email do
|
|
744
|
+
# strategy :hash
|
|
745
|
+
# exclude_adapters [:file_audit] # Audit gets original (GDPR)
|
|
746
|
+
# end
|
|
747
|
+
def field(field_name, &)
|
|
748
|
+
return unless block_given?
|
|
749
|
+
|
|
750
|
+
opts = { strategy: :allow }
|
|
751
|
+
dsl = Class.new do
|
|
752
|
+
attr_reader :opts
|
|
753
|
+
|
|
754
|
+
def initialize(opts) = @opts = opts
|
|
755
|
+
def strategy(val) = @opts.[]=(:strategy, val)
|
|
756
|
+
def exclude_adapters(adapters) = @opts.[]=(:exclude_adapters, Array(adapters).map(&:to_sym))
|
|
757
|
+
end.new(opts)
|
|
758
|
+
dsl.instance_eval(&)
|
|
759
|
+
@config[:fields][field_name] = opts
|
|
760
|
+
end
|
|
669
761
|
end
|
|
670
762
|
|
|
671
763
|
# === Audit Event DSL (ADR-006, UC-012) ===
|
|
@@ -705,6 +797,33 @@ module E11y
|
|
|
705
797
|
@audit_event == true
|
|
706
798
|
end
|
|
707
799
|
|
|
800
|
+
# === DLQ Filter DSL (ADR-013, UC-021) ===
|
|
801
|
+
|
|
802
|
+
# Declare whether this event should be saved to DLQ on failure.
|
|
803
|
+
#
|
|
804
|
+
# @param value [Boolean, nil] true = save, false = discard, nil = use severity + default
|
|
805
|
+
# @example
|
|
806
|
+
# class Events::PaymentFailed < E11y::Event::Base
|
|
807
|
+
# use_dlq true
|
|
808
|
+
# end
|
|
809
|
+
#
|
|
810
|
+
# class Events::DebugTrace < E11y::Event::Base
|
|
811
|
+
# use_dlq false
|
|
812
|
+
# end
|
|
813
|
+
def use_dlq(value = nil)
|
|
814
|
+
if value.nil?
|
|
815
|
+
return superclass.use_dlq if !instance_variable_defined?(:@use_dlq) && superclass.respond_to?(:use_dlq)
|
|
816
|
+
|
|
817
|
+
@use_dlq
|
|
818
|
+
else
|
|
819
|
+
@use_dlq = value
|
|
820
|
+
end
|
|
821
|
+
end
|
|
822
|
+
|
|
823
|
+
def use_dlq?
|
|
824
|
+
use_dlq == true
|
|
825
|
+
end
|
|
826
|
+
|
|
708
827
|
# Configure cryptographic signing for audit event
|
|
709
828
|
#
|
|
710
829
|
# By default, all audit events are signed with HMAC-SHA256.
|
|
@@ -759,7 +878,7 @@ module E11y
|
|
|
759
878
|
audit_event? && signing_enabled?
|
|
760
879
|
end
|
|
761
880
|
|
|
762
|
-
# === Metrics DSL (ADR-002, UC-003) ===
|
|
881
|
+
# === Metrics DSL (ADR-002, UC-003 Event Metrics) ===
|
|
763
882
|
|
|
764
883
|
# Define metrics for this event
|
|
765
884
|
#
|
|
@@ -810,6 +929,23 @@ module E11y
|
|
|
810
929
|
register_metrics_in_registry!
|
|
811
930
|
end
|
|
812
931
|
|
|
932
|
+
# Single-call metric shorthand — equivalent to a one-metric `metrics` block.
|
|
933
|
+
#
|
|
934
|
+
# @param type [Symbol] :counter, :histogram, or :gauge
|
|
935
|
+
# @param name [Symbol] Metric name
|
|
936
|
+
# @param opts [Hash] Options: tags:, value: (histogram/gauge), buckets: (histogram)
|
|
937
|
+
#
|
|
938
|
+
# @example
|
|
939
|
+
# metric :counter, name: :orders_total, tags: [:currency]
|
|
940
|
+
# metric :histogram, name: :order_amount, value: :amount, tags: [:currency]
|
|
941
|
+
def metric(type, name:, **opts)
|
|
942
|
+
raise ArgumentError, "Unknown metric type: #{type}. Use :counter, :histogram, or :gauge" unless %i[counter histogram gauge].include?(type)
|
|
943
|
+
|
|
944
|
+
@metrics_config ||= []
|
|
945
|
+
@metrics_config << { type: type, name: name }.merge(opts).compact
|
|
946
|
+
register_metrics_in_registry!
|
|
947
|
+
end
|
|
948
|
+
|
|
813
949
|
# Get metrics configuration
|
|
814
950
|
#
|
|
815
951
|
# @return [Array<Hash>] Metrics configuration
|
|
@@ -911,48 +1047,6 @@ module E11y
|
|
|
911
1047
|
end
|
|
912
1048
|
end
|
|
913
1049
|
end
|
|
914
|
-
|
|
915
|
-
# Builder for PII filtering DSL
|
|
916
|
-
class PIIFilteringBuilder
|
|
917
|
-
def initialize(config)
|
|
918
|
-
@config = config
|
|
919
|
-
end
|
|
920
|
-
|
|
921
|
-
# Mask fields (strategy: :mask)
|
|
922
|
-
def masks(*fields)
|
|
923
|
-
fields.each do |field|
|
|
924
|
-
@config[:fields][field] = { strategy: :mask }
|
|
925
|
-
end
|
|
926
|
-
end
|
|
927
|
-
|
|
928
|
-
# Hash fields (strategy: :hash)
|
|
929
|
-
def hashes(*fields)
|
|
930
|
-
fields.each do |field|
|
|
931
|
-
@config[:fields][field] = { strategy: :hash }
|
|
932
|
-
end
|
|
933
|
-
end
|
|
934
|
-
|
|
935
|
-
# Allow fields (strategy: :allow)
|
|
936
|
-
def allows(*fields)
|
|
937
|
-
fields.each do |field|
|
|
938
|
-
@config[:fields][field] = { strategy: :allow }
|
|
939
|
-
end
|
|
940
|
-
end
|
|
941
|
-
|
|
942
|
-
# Partial mask fields (strategy: :partial)
|
|
943
|
-
def partials(*fields)
|
|
944
|
-
fields.each do |field|
|
|
945
|
-
@config[:fields][field] = { strategy: :partial }
|
|
946
|
-
end
|
|
947
|
-
end
|
|
948
|
-
|
|
949
|
-
# Redact fields (strategy: :redact)
|
|
950
|
-
def redacts(*fields)
|
|
951
|
-
fields.each do |field|
|
|
952
|
-
@config[:fields][field] = { strategy: :redact }
|
|
953
|
-
end
|
|
954
|
-
end
|
|
955
|
-
end
|
|
956
1050
|
end
|
|
957
1051
|
# rubocop:enable Metrics/ClassLength
|
|
958
1052
|
end
|
|
@@ -69,7 +69,6 @@ module E11y
|
|
|
69
69
|
|
|
70
70
|
private
|
|
71
71
|
|
|
72
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
|
73
72
|
# Validation requires checking multiple comparison types and threshold types
|
|
74
73
|
def validate_comparisons!
|
|
75
74
|
raise ArgumentError, "At least one comparison required" if comparisons.empty?
|
|
@@ -79,12 +78,9 @@ module E11y
|
|
|
79
78
|
|
|
80
79
|
raise ArgumentError, "in_range requires a Range" if type == :in_range && !threshold.is_a?(Range)
|
|
81
80
|
|
|
82
|
-
if NUMERIC_COMPARISON_TYPES.include?(type) && !threshold.is_a?(Numeric)
|
|
83
|
-
raise ArgumentError, "#{type} requires a Numeric threshold"
|
|
84
|
-
end
|
|
81
|
+
raise ArgumentError, "#{type} requires a Numeric threshold" if NUMERIC_COMPARISON_TYPES.include?(type) && !threshold.is_a?(Numeric)
|
|
85
82
|
end
|
|
86
83
|
end
|
|
87
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
|
88
84
|
end
|
|
89
85
|
end
|
|
90
86
|
end
|
|
@@ -16,10 +16,7 @@ module E11y
|
|
|
16
16
|
# @example Custom override
|
|
17
17
|
# # config/initializers/e11y.rb
|
|
18
18
|
# E11y.configure do |config|
|
|
19
|
-
# config.
|
|
20
|
-
# 'sql.active_record',
|
|
21
|
-
# MyApp::CustomDatabaseQuery
|
|
22
|
-
# )
|
|
19
|
+
# config.rails_instrumentation_custom_mappings['sql.active_record'] = MyApp::CustomDatabaseQuery
|
|
23
20
|
# end
|
|
24
21
|
#
|
|
25
22
|
# @see ADR-008 §4.3 (Built-in Event Classes)
|
|
@@ -29,13 +29,16 @@ module E11y
|
|
|
29
29
|
# Store current trace as parent (job will create NEW trace)
|
|
30
30
|
job.e11y_parent_trace_id = E11y::Current.trace_id if E11y::Current.trace_id
|
|
31
31
|
job.e11y_parent_span_id = E11y::Current.span_id if E11y::Current.span_id
|
|
32
|
+
job.e11y_sampled = E11y::Current.sampled if E11y::Current.respond_to?(:sampled) && !E11y::Current.sampled.nil?
|
|
33
|
+
baggage = E11y::Tracing::Propagator.baggage_for_propagation_from_current
|
|
34
|
+
job.e11y_baggage = baggage if baggage.any?
|
|
32
35
|
end
|
|
33
36
|
|
|
34
37
|
# Set up job-scoped context around job execution (C17 Hybrid Tracing + C18 Non-Failing)
|
|
35
38
|
around_perform do |job, block|
|
|
36
39
|
# C18: Disable fail_on_error for jobs (observability should not block business logic)
|
|
37
|
-
original_fail_on_error = E11y.config.
|
|
38
|
-
E11y.config.
|
|
40
|
+
original_fail_on_error = E11y.config.error_handling_fail_on_error
|
|
41
|
+
E11y.config.error_handling_fail_on_error = false
|
|
39
42
|
|
|
40
43
|
setup_job_context_active_job(job)
|
|
41
44
|
setup_job_buffer_active_job
|
|
@@ -59,7 +62,7 @@ module E11y
|
|
|
59
62
|
cleanup_job_context_active_job
|
|
60
63
|
|
|
61
64
|
# Restore original setting
|
|
62
|
-
E11y.config.
|
|
65
|
+
E11y.config.error_handling_fail_on_error = original_fail_on_error
|
|
63
66
|
end
|
|
64
67
|
end
|
|
65
68
|
|
|
@@ -79,13 +82,28 @@ module E11y
|
|
|
79
82
|
E11y::Current.span_id = span_id
|
|
80
83
|
E11y::Current.parent_trace_id = parent_trace_id
|
|
81
84
|
E11y::Current.request_id = job.job_id
|
|
85
|
+
E11y::Tracing::Propagator.hydrate_current_from_job_baggage!(job.e11y_baggage) if job.respond_to?(:e11y_baggage)
|
|
86
|
+
|
|
87
|
+
# Restore or compute sampling decision (ADR-005 §7)
|
|
88
|
+
if job.respond_to?(:e11y_sampled) && !job.e11y_sampled.nil?
|
|
89
|
+
E11y::Current.sampled = job.e11y_sampled
|
|
90
|
+
else
|
|
91
|
+
require "e11y/trace_context/sampler"
|
|
92
|
+
ctx = E11y::Current.to_context.merge(
|
|
93
|
+
job_class: job.class.name,
|
|
94
|
+
queue: job.queue_name
|
|
95
|
+
).compact
|
|
96
|
+
E11y::Current.sampled = E11y::TraceContext::Sampler.should_sample?(ctx)
|
|
97
|
+
end
|
|
82
98
|
end
|
|
83
99
|
|
|
84
100
|
# Setup job-scoped buffer
|
|
85
101
|
def setup_job_buffer_active_job
|
|
86
|
-
return unless E11y.config.
|
|
102
|
+
return unless E11y.config.ephemeral_buffer_enabled
|
|
87
103
|
|
|
88
|
-
E11y
|
|
104
|
+
limit = E11y.config.ephemeral_buffer_job_buffer_limit ||
|
|
105
|
+
E11y::Buffers::EphemeralBuffer::DEFAULT_BUFFER_LIMIT
|
|
106
|
+
E11y::Buffers::EphemeralBuffer.initialize!(buffer_limit: limit)
|
|
89
107
|
rescue StandardError => e
|
|
90
108
|
# C18: Don't fail job if buffer setup fails
|
|
91
109
|
warn "[E11y] Failed to start job buffer: #{e.message}"
|
|
@@ -93,9 +111,9 @@ module E11y
|
|
|
93
111
|
|
|
94
112
|
# Handle job error (C18: Non-Failing Event Tracking)
|
|
95
113
|
def handle_job_error_active_job(_error)
|
|
96
|
-
return unless E11y.config.
|
|
114
|
+
return unless E11y.config.ephemeral_buffer_enabled
|
|
97
115
|
|
|
98
|
-
E11y::Buffers::
|
|
116
|
+
E11y::Buffers::EphemeralBuffer.flush_on_error
|
|
99
117
|
rescue StandardError => e
|
|
100
118
|
# C18: Don't fail job if buffer flush fails
|
|
101
119
|
warn "[E11y] Failed to flush job buffer on error: #{e.message}"
|
|
@@ -104,9 +122,9 @@ module E11y
|
|
|
104
122
|
# Cleanup job-scoped context
|
|
105
123
|
def cleanup_job_context_active_job
|
|
106
124
|
# Flush buffer on success (not on error, already flushed in rescue)
|
|
107
|
-
if !$ERROR_INFO && E11y.config.
|
|
125
|
+
if !$ERROR_INFO && E11y.config.ephemeral_buffer_enabled
|
|
108
126
|
begin
|
|
109
|
-
E11y::Buffers::
|
|
127
|
+
E11y::Buffers::EphemeralBuffer.discard
|
|
110
128
|
rescue StandardError => e
|
|
111
129
|
# C18: Don't fail job if buffer flush fails
|
|
112
130
|
warn "[E11y] Failed to flush job buffer: #{e.message}"
|
|
@@ -139,10 +157,9 @@ module E11y
|
|
|
139
157
|
# @param start_time [Time] Job start time
|
|
140
158
|
# @return [void]
|
|
141
159
|
# @api private
|
|
142
|
-
# rubocop:disable Metrics/AbcSize
|
|
143
160
|
# SLO tracking requires config check, duration calculation, method call, and error handling
|
|
144
161
|
def track_job_slo_active_job(job, status, start_time)
|
|
145
|
-
return unless E11y.config.
|
|
162
|
+
return unless E11y.config.slo_tracking_enabled
|
|
146
163
|
|
|
147
164
|
duration_ms = ((Time.now - start_time) * 1000).round(2)
|
|
148
165
|
|
|
@@ -157,7 +174,6 @@ module E11y
|
|
|
157
174
|
# C18: Don't fail if SLO tracking fails
|
|
158
175
|
E11y.logger.warn("[E11y] SLO tracking error: #{e.message}", error: e.class.name)
|
|
159
176
|
end
|
|
160
|
-
# rubocop:enable Metrics/AbcSize
|
|
161
177
|
end
|
|
162
178
|
|
|
163
179
|
# Custom attribute accessors for trace context (C17 Hybrid Tracing)
|
|
@@ -195,6 +211,22 @@ module E11y
|
|
|
195
211
|
def e11y_span_id=(value)
|
|
196
212
|
@e11y_span_id = value
|
|
197
213
|
end
|
|
214
|
+
|
|
215
|
+
def e11y_sampled
|
|
216
|
+
@e11y_sampled
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def e11y_sampled=(value)
|
|
220
|
+
@e11y_sampled = value
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def e11y_baggage
|
|
224
|
+
@e11y_baggage
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def e11y_baggage=(value)
|
|
228
|
+
@e11y_baggage = value
|
|
229
|
+
end
|
|
198
230
|
end
|
|
199
231
|
end
|
|
200
232
|
end
|