e11y 0.2.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +130 -10
- data/CHANGELOG.md +56 -1
- data/CLAUDE.md +168 -0
- data/CONTRIBUTING.md +640 -0
- data/README.md +134 -702
- data/RELEASE.md +18 -3
- data/Rakefile +108 -29
- data/config/README.md +1 -1
- data/config/loki-local-config.yaml +12 -0
- data/config/otel-collector-config.yaml +44 -0
- data/cucumber.yml +1 -0
- data/docker-compose.yml +18 -2
- data/docs/ADAPTERS.md +76 -0
- data/docs/ADAPTIVE_SAMPLING.md +59 -0
- data/docs/COMPARISON.md +104 -0
- data/docs/CONFIGURATION.md +52 -0
- data/docs/DISTRIBUTED_TRACING.md +44 -0
- data/docs/LIMITATIONS.md +13 -0
- data/docs/METRICS_DSL.md +84 -0
- data/docs/PERFORMANCE.md +60 -0
- data/docs/PII_FILTERING.md +40 -0
- data/docs/PRESETS.md +65 -0
- data/docs/QUICK-START.md +546 -587
- data/docs/RAILS_INTEGRATION.md +29 -0
- data/docs/SCHEMA_VALIDATION.md +63 -0
- data/docs/SLO-PROMQL-ALERTS.md +161 -0
- data/docs/TESTING.md +69 -0
- data/docs/{ADR-001-architecture.md → architecture/ADR-001-architecture.md} +35 -64
- data/docs/{ADR-002-metrics-yabeda.md → architecture/ADR-002-metrics-yabeda.md} +62 -236
- data/docs/{ADR-003-slo-observability.md → architecture/ADR-003-slo-observability.md} +27 -466
- data/docs/{ADR-004-adapter-architecture.md → architecture/ADR-004-adapter-architecture.md} +163 -146
- data/docs/{ADR-005-tracing-context.md → architecture/ADR-005-tracing-context.md} +10 -9
- data/docs/{ADR-006-security-compliance.md → architecture/ADR-006-security-compliance.md} +184 -191
- data/docs/{ADR-007-opentelemetry-integration.md → architecture/ADR-007-opentelemetry-integration.md} +3 -21
- data/docs/{ADR-008-rails-integration.md → architecture/ADR-008-rails-integration.md} +209 -339
- data/docs/{ADR-009-cost-optimization.md → architecture/ADR-009-cost-optimization.md} +45 -54
- data/docs/architecture/ADR-010-developer-experience.md +522 -0
- data/docs/{ADR-011-testing-strategy.md → architecture/ADR-011-testing-strategy.md} +41 -83
- data/docs/{ADR-013-reliability-error-handling.md → architecture/ADR-013-reliability-error-handling.md} +37 -12
- data/docs/{ADR-014-event-driven-slo.md → architecture/ADR-014-event-driven-slo.md} +12 -24
- data/docs/{ADR-015-middleware-order.md → architecture/ADR-015-middleware-order.md} +23 -41
- data/docs/{ADR-016-self-monitoring-slo.md → architecture/ADR-016-self-monitoring-slo.md} +52 -349
- data/docs/{ADR-017-multi-rails-compatibility.md → architecture/ADR-017-multi-rails-compatibility.md} +4 -11
- data/docs/architecture/ADR-018-memory-optimization.md +366 -0
- data/docs/{ADR-INDEX.md → architecture/ADR-INDEX.md} +11 -6
- data/docs/{00-ICP-AND-TIMELINE.md → prd/00-ICP-AND-TIMELINE.md} +6 -6
- data/docs/{01-SCALE-REQUIREMENTS.md → prd/01-SCALE-REQUIREMENTS.md} +6 -6
- data/docs/prd/01-overview-vision.md +19 -14
- data/docs/use_cases/README.md +22 -23
- data/docs/use_cases/UC-001-request-scoped-debug-buffering.md +50 -44
- data/docs/use_cases/UC-002-business-event-tracking.md +26 -95
- data/docs/use_cases/UC-003-event-metrics.md +66 -0
- data/docs/use_cases/UC-004-zero-config-slo-tracking.md +42 -101
- data/docs/use_cases/UC-005-sentry-integration.md +13 -15
- data/docs/use_cases/UC-006-trace-context-management.md +30 -28
- data/docs/use_cases/UC-007-pii-filtering.md +35 -87
- data/docs/use_cases/UC-008-opentelemetry-integration.md +51 -89
- data/docs/use_cases/UC-009-multi-service-tracing.md +4 -4
- data/docs/use_cases/UC-010-background-job-tracking.md +5 -5
- data/docs/use_cases/UC-011-rate-limiting.md +95 -168
- data/docs/use_cases/UC-012-audit-trail.md +21 -46
- data/docs/use_cases/UC-013-high-cardinality-protection.md +29 -167
- data/docs/use_cases/UC-014-adaptive-sampling.md +2 -2
- data/docs/use_cases/UC-015-cost-optimization.md +46 -99
- data/docs/use_cases/UC-016-rails-logger-migration.md +39 -213
- data/docs/use_cases/UC-017-local-development.md +203 -777
- data/docs/use_cases/UC-018-testing-events.md +3 -3
- data/docs/use_cases/UC-019-retention-based-routing.md +53 -106
- data/docs/use_cases/UC-020-event-versioning.md +8 -9
- data/docs/use_cases/UC-021-error-handling-retry-dlq.md +18 -22
- data/docs/use_cases/UC-022-event-registry.md +15 -21
- data/docs/use_cases/backlog.md +119 -87
- data/e11y.gemspec +2 -2
- data/gems/e11y-devtools/README.md +136 -0
- data/gems/e11y-devtools/config/routes.rb +8 -0
- data/gems/e11y-devtools/e11y-devtools.gemspec +25 -0
- data/gems/e11y-devtools/exe/e11y +34 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/server.rb +96 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tool_base.rb +25 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/clear.rb +31 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/errors.rb +35 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/event_detail.rb +33 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/events_by_trace.rb +33 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/interactions.rb +40 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/recent_events.rb +34 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/search.rb +34 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/stats.rb +30 -0
- data/gems/e11y-devtools/lib/e11y/devtools/overlay/assets/overlay.js +115 -0
- data/gems/e11y-devtools/lib/e11y/devtools/overlay/controller.rb +54 -0
- data/gems/e11y-devtools/lib/e11y/devtools/overlay/engine.rb +26 -0
- data/gems/e11y-devtools/lib/e11y/devtools/overlay/middleware.rb +80 -0
- data/gems/e11y-devtools/lib/e11y/devtools/overlay/rails_controller.rb +42 -0
- data/gems/e11y-devtools/lib/e11y/devtools/tui/app.rb +262 -0
- data/gems/e11y-devtools/lib/e11y/devtools/tui/grouping.rb +66 -0
- data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/event_detail.rb +62 -0
- data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/event_list.rb +70 -0
- data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/interaction_list.rb +47 -0
- data/gems/e11y-devtools/lib/e11y/devtools/version.rb +8 -0
- data/gems/e11y-devtools/lib/e11y/devtools.rb +13 -0
- data/gems/e11y-devtools/spec/e11y/devtools/mcp/tools_spec.rb +107 -0
- data/gems/e11y-devtools/spec/e11y/devtools/overlay/controller_spec.rb +58 -0
- data/gems/e11y-devtools/spec/e11y/devtools/overlay/middleware_spec.rb +46 -0
- data/gems/e11y-devtools/spec/e11y/devtools/tui/app_spec.rb +85 -0
- data/gems/e11y-devtools/spec/e11y/devtools/tui/grouping_spec.rb +64 -0
- data/gems/e11y-devtools/spec/spec_helper.rb +5 -0
- data/gems/e11y-devtools/spec/tui/widgets/event_list_spec.rb +44 -0
- data/gems/e11y-devtools/spec/tui/widgets/interaction_list_spec.rb +62 -0
- data/lib/e11y/adapters/audit_encrypted.rb +53 -11
- data/lib/e11y/adapters/base.rb +33 -34
- data/lib/e11y/adapters/dev_log/file_store.rb +143 -0
- data/lib/e11y/adapters/dev_log/query.rb +219 -0
- data/lib/e11y/adapters/dev_log.rb +118 -0
- data/lib/e11y/adapters/file.rb +3 -6
- data/lib/e11y/adapters/in_memory.rb +52 -5
- data/lib/e11y/adapters/in_memory_test.rb +29 -0
- data/lib/e11y/adapters/loki.rb +58 -23
- data/lib/e11y/adapters/null.rb +82 -0
- data/lib/e11y/adapters/opentelemetry_collector.rb +183 -0
- data/lib/e11y/adapters/otel_logs.rb +136 -23
- data/lib/e11y/adapters/sentry.rb +4 -7
- data/lib/e11y/adapters/stdout.rb +73 -7
- data/lib/e11y/adapters/yabeda.rb +153 -29
- data/lib/e11y/buffers/adaptive_buffer.rb +3 -17
- data/lib/e11y/buffers/{request_scoped_buffer.rb → ephemeral_buffer.rb} +72 -58
- data/lib/e11y/buffers/ring_buffer.rb +3 -16
- data/lib/e11y/configuration.rb +272 -0
- data/lib/e11y/console.rb +10 -17
- data/lib/e11y/current.rb +53 -1
- data/lib/e11y/debug/pipeline_inspector.rb +96 -0
- data/lib/e11y/documentation/generator.rb +48 -0
- data/lib/e11y/event/base.rb +176 -82
- data/lib/e11y/event/value_sampling_config.rb +1 -5
- data/lib/e11y/events/rails/database/query.rb +1 -4
- data/lib/e11y/events/rails/job/failed.rb +2 -0
- data/lib/e11y/instruments/active_job.rb +46 -12
- data/lib/e11y/instruments/rails_instrumentation.rb +49 -24
- data/lib/e11y/instruments/sidekiq.rb +137 -31
- data/lib/e11y/linters/base.rb +11 -0
- data/lib/e11y/linters/pii/pii_declaration_linter.rb +120 -0
- data/lib/e11y/linters/slo/config_consistency_linter.rb +76 -0
- data/lib/e11y/linters/slo/explicit_declaration_linter.rb +36 -0
- data/lib/e11y/linters/slo/slo_status_from_linter.rb +41 -0
- data/lib/e11y/logger/bridge.rb +26 -7
- data/lib/e11y/metrics/cardinality_protection.rb +10 -15
- data/lib/e11y/metrics/cardinality_tracker.rb +16 -6
- data/lib/e11y/metrics/registry.rb +3 -5
- data/lib/e11y/metrics/test_backend.rb +62 -0
- data/lib/e11y/metrics.rb +56 -10
- data/lib/e11y/middleware/adapter_resolver.rb +40 -0
- data/lib/e11y/middleware/audit_signing.rb +43 -6
- data/lib/e11y/middleware/baggage_protection.rb +75 -0
- data/lib/e11y/middleware/dev_log_source.rb +24 -0
- data/lib/e11y/middleware/event_slo.rb +23 -9
- data/lib/e11y/middleware/otel_span.rb +23 -0
- data/lib/e11y/middleware/pii_filter.rb +104 -75
- data/lib/e11y/middleware/rate_limiting.rb +54 -27
- data/lib/e11y/middleware/request.rb +70 -23
- data/lib/e11y/middleware/routing.rb +78 -21
- data/lib/e11y/middleware/sampling.rb +66 -17
- data/lib/e11y/middleware/self_monitoring_emit.rb +39 -0
- data/lib/e11y/middleware/trace_context.rb +45 -10
- data/lib/e11y/middleware/track_latency.rb +34 -0
- data/lib/e11y/middleware/validation.rb +7 -16
- data/lib/e11y/middleware/versioning.rb +26 -22
- data/lib/e11y/opentelemetry/semantic_conventions.rb +109 -0
- data/lib/e11y/opentelemetry/span_creator.rb +142 -0
- data/lib/e11y/pii/patterns.rb +12 -1
- data/lib/e11y/pipeline/builder.rb +1 -1
- data/lib/e11y/presets/audit_event.rb +13 -2
- data/lib/e11y/railtie.rb +52 -15
- data/lib/e11y/registry.rb +306 -0
- data/lib/e11y/reliability/circuit_breaker.rb +19 -21
- data/lib/e11y/reliability/dlq/base.rb +71 -0
- data/lib/e11y/reliability/dlq/file_adapter.rb +301 -0
- data/lib/e11y/reliability/dlq/file_storage.rb +63 -34
- data/lib/e11y/reliability/dlq/filter.rb +37 -54
- data/lib/e11y/reliability/retry_handler.rb +26 -29
- data/lib/e11y/reliability/retry_rate_limiter.rb +3 -11
- data/lib/e11y/sampling/error_spike_detector.rb +0 -2
- data/lib/e11y/sampling/load_monitor.rb +5 -9
- data/lib/e11y/sampling/stratified_tracker.rb +18 -0
- data/lib/e11y/self_monitoring/buffer_monitor.rb +2 -0
- data/lib/e11y/self_monitoring/performance_monitor.rb +19 -61
- data/lib/e11y/self_monitoring/reliability_monitor.rb +4 -74
- data/lib/e11y/slo/config_loader.rb +40 -0
- data/lib/e11y/slo/config_validator.rb +58 -0
- data/lib/e11y/slo/dashboard_generator.rb +122 -0
- data/lib/e11y/slo/event_driven.rb +8 -0
- data/lib/e11y/slo/tracker.rb +31 -4
- data/lib/e11y/testing/have_tracked_event_matcher.rb +190 -0
- data/lib/e11y/testing/rspec_matchers.rb +21 -0
- data/lib/e11y/testing/snapshot_matcher.rb +86 -0
- data/lib/e11y/trace_context/sampler.rb +35 -0
- data/lib/e11y/tracing/faraday_middleware.rb +31 -0
- data/lib/e11y/tracing/net_http_patch.rb +33 -0
- data/lib/e11y/tracing/propagator.rb +116 -0
- data/lib/e11y/tracing.rb +47 -0
- data/lib/e11y/version.rb +1 -1
- data/lib/e11y/versioning/version_extractor.rb +32 -0
- data/lib/e11y.rb +141 -265
- data/lib/generators/e11y/event/event_generator.rb +22 -0
- data/lib/generators/e11y/event/templates/event.rb.tt +16 -0
- data/lib/generators/e11y/grafana_dashboard/grafana_dashboard_generator.rb +30 -0
- data/lib/generators/e11y/grafana_dashboard/templates/e11y_dashboard.json +81 -0
- data/lib/generators/e11y/install/install_generator.rb +34 -0
- data/lib/generators/e11y/install/templates/e11y.rb +239 -0
- data/lib/generators/e11y/prometheus_alerts/prometheus_alerts_generator.rb +29 -0
- data/lib/generators/e11y/prometheus_alerts/templates/e11y_alerts.yml +28 -0
- data/lib/tasks/e11y_docs.rake +30 -0
- data/lib/tasks/e11y_events.rake +71 -0
- data/lib/tasks/e11y_lint.rake +91 -0
- data/lib/tasks/e11y_slo.rake +29 -0
- metadata +129 -39
- data/docs/ADR-010-developer-experience.md +0 -2166
- data/docs/API-REFERENCE-L28.md +0 -914
- data/docs/COMPREHENSIVE-CONFIGURATION.md +0 -2366
- data/docs/CONTRIBUTING.md +0 -312
- data/docs/IMPLEMENTATION_NOTES.md +0 -2804
- data/docs/IMPLEMENTATION_PLAN.md +0 -1971
- data/docs/IMPLEMENTATION_PLAN_ARCHITECTURE.md +0 -586
- data/docs/PLAN.md +0 -148
- data/docs/README.md +0 -296
- data/docs/design/00-memory-optimization.md +0 -593
- data/docs/guides/MIGRATION-L27-L28.md +0 -692
- data/docs/guides/PERFORMANCE-BENCHMARKS.md +0 -434
- data/docs/guides/README.md +0 -44
- data/docs/use_cases/UC-003-pattern-based-metrics.md +0 -1627
- data/lib/e11y/adapters/registry.rb +0 -141
- /data/docs/{ADR-012-event-evolution.md → architecture/ADR-012-event-evolution.md} +0 -0
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
- 4.1. [Global Rate Limiting](#41-global-rate-limiting)
|
|
22
22
|
- 4.2. [Per-Event Rate Limiting](#42-per-event-rate-limiting)
|
|
23
23
|
- 4.3. [Per-Context Rate Limiting](#43-per-context-rate-limiting)
|
|
24
|
-
- 4.4. [
|
|
24
|
+
- 4.4. [In-Memory Token Bucket Implementation](#44-in-memory-token-bucket-implementation)
|
|
25
25
|
5. [Audit Trail](#5-audit-trail)
|
|
26
26
|
- 5.1. [Immutable Events](#51-immutable-events)
|
|
27
27
|
- 5.2. [Cryptographic Signing](#52-cryptographic-signing)
|
|
@@ -121,12 +121,10 @@ C4Context
|
|
|
121
121
|
|
|
122
122
|
System(e11y, "E11y Gem", "Event tracking with security")
|
|
123
123
|
|
|
124
|
-
System_Ext(redis, "Redis", "Rate limiting state")
|
|
125
124
|
System_Ext(kms, "KMS", "Signing keys")
|
|
126
125
|
System_Ext(audit_store, "Audit Store", "Immutable audit logs")
|
|
127
126
|
|
|
128
127
|
Rel(dev, e11y, "Tracks events", "Events::OrderPaid.track(...)")
|
|
129
|
-
Rel(e11y, redis, "Check rate limits", "Redis commands")
|
|
130
128
|
Rel(e11y, kms, "Sign audit events", "HMAC-SHA256")
|
|
131
129
|
Rel(e11y, audit_store, "Store signed events", "Append-only")
|
|
132
130
|
|
|
@@ -151,14 +149,12 @@ graph TB
|
|
|
151
149
|
PII --> PerAdapter[Per-Adapter Rules]
|
|
152
150
|
end
|
|
153
151
|
|
|
154
|
-
subgraph "Rate Limiting"
|
|
155
|
-
Rate --> Global[Global
|
|
156
|
-
Rate --> PerEvent[Per-Event
|
|
157
|
-
Rate --> PerContext[Per-Context
|
|
152
|
+
subgraph "Rate Limiting (In-Memory)"
|
|
153
|
+
Rate --> Global[Global Token Bucket]
|
|
154
|
+
Rate --> PerEvent[Per-Event Token Buckets]
|
|
155
|
+
Rate --> PerContext[Per-Context Token Buckets]
|
|
158
156
|
|
|
159
|
-
|
|
160
|
-
PerEvent --> Redis2[Redis]
|
|
161
|
-
PerContext --> Redis3[Redis]
|
|
157
|
+
Note1[In-Memory<br/>No Redis Dependency]
|
|
162
158
|
end
|
|
163
159
|
|
|
164
160
|
subgraph "Audit Trail"
|
|
@@ -239,19 +235,19 @@ sequenceDiagram
|
|
|
239
235
|
|
|
240
236
|
#### 3.0.2. Three-Tier Filtering Strategy
|
|
241
237
|
|
|
242
|
-
E11y uses a **3-tier approach** to balance security and performance
|
|
238
|
+
E11y uses a **3-tier approach** to balance security and performance. Code uses `pii_filtering_mode` returning `:no_pii`, `:rails_filters`, or `:explicit_pii`:
|
|
243
239
|
|
|
244
|
-
|
|
|
240
|
+
| Mode | Strategy | Cost | Use Case | Events/sec |
|
|
245
241
|
|------|----------|------|----------|------------|
|
|
246
|
-
| **
|
|
247
|
-
| **
|
|
248
|
-
| **
|
|
242
|
+
| **:no_pii** | Skip filtering | 0ms | Health checks, metrics, internal events | 500 |
|
|
243
|
+
| **:rails_filters** | Rails filters only | ~0.05ms | Standard events (known PII keys) | 400 |
|
|
244
|
+
| **:explicit_pii** | Deep filtering | ~0.2ms | User data, payments, complex nested | 100 |
|
|
249
245
|
|
|
250
246
|
**Performance Budget:**
|
|
251
247
|
```
|
|
252
|
-
500 events/sec × 0ms = 0ms CPU/sec (
|
|
253
|
-
400 events/sec × 0.05ms = 20ms CPU/sec (
|
|
254
|
-
100 events/sec × 0.2ms = 20ms CPU/sec (
|
|
248
|
+
500 events/sec × 0ms = 0ms CPU/sec (:no_pii)
|
|
249
|
+
400 events/sec × 0.05ms = 20ms CPU/sec (:rails_filters)
|
|
250
|
+
100 events/sec × 0.2ms = 20ms CPU/sec (:explicit_pii)
|
|
255
251
|
---
|
|
256
252
|
Total: 40ms CPU/sec = 4% CPU on single core ✅
|
|
257
253
|
```
|
|
@@ -268,7 +264,7 @@ class Events::HealthCheck < E11y::Event::Base
|
|
|
268
264
|
end
|
|
269
265
|
|
|
270
266
|
# ✅ Explicit: This event contains NO PII
|
|
271
|
-
contains_pii false # Skip all PII filtering (
|
|
267
|
+
contains_pii false # Skip all PII filtering (:no_pii)
|
|
272
268
|
end
|
|
273
269
|
```
|
|
274
270
|
|
|
@@ -280,7 +276,7 @@ class Events::OrderCreated < E11y::Event::Base
|
|
|
280
276
|
required(:amount).filled(:float)
|
|
281
277
|
end
|
|
282
278
|
|
|
283
|
-
# No declaration → Rails filters applied (
|
|
279
|
+
# No declaration → Rails filters applied (:rails_filters, default)
|
|
284
280
|
# Uses: Rails.application.config.filter_parameters
|
|
285
281
|
end
|
|
286
282
|
```
|
|
@@ -296,7 +292,7 @@ class Events::UserRegistered < E11y::Event::Base
|
|
|
296
292
|
end
|
|
297
293
|
|
|
298
294
|
# ✅ Explicit: This event contains PII
|
|
299
|
-
contains_pii true #
|
|
295
|
+
contains_pii true # :explicit_pii: Deep filtering
|
|
300
296
|
|
|
301
297
|
pii_filtering do
|
|
302
298
|
# ✅ MANDATORY: Explicit declaration for EACH schema field
|
|
@@ -389,7 +385,7 @@ module E11y
|
|
|
389
385
|
module Linters
|
|
390
386
|
class PiiDeclarationLinter
|
|
391
387
|
def self.validate_all!
|
|
392
|
-
E11y::Registry.
|
|
388
|
+
E11y::Registry.event_classes.each do |event_class|
|
|
393
389
|
validate!(event_class)
|
|
394
390
|
end
|
|
395
391
|
end
|
|
@@ -480,7 +476,7 @@ namespace :e11y do
|
|
|
480
476
|
errors = []
|
|
481
477
|
warnings = []
|
|
482
478
|
|
|
483
|
-
E11y::Registry.
|
|
479
|
+
E11y::Registry.event_classes.each do |event_class|
|
|
484
480
|
begin
|
|
485
481
|
E11y::Linters::PiiDeclarationLinter.validate!(event_class)
|
|
486
482
|
|
|
@@ -567,7 +563,7 @@ end
|
|
|
567
563
|
#### 3.0.6. Default Behavior
|
|
568
564
|
|
|
569
565
|
**If `contains_pii` not specified:**
|
|
570
|
-
- ✅ Apply Rails filters only (
|
|
566
|
+
- ✅ Apply Rails filters only (:rails_filters)
|
|
571
567
|
- ✅ Fast (0.05ms overhead)
|
|
572
568
|
- ✅ Covers 90% of use cases (password, token, secret, api_key)
|
|
573
569
|
- ⚠️ No linter validation (assumes you know what you're doing)
|
|
@@ -639,7 +635,10 @@ end
|
|
|
639
635
|
└─→ PII Filtering (per-adapter, different rules per destination)
|
|
640
636
|
```
|
|
641
637
|
|
|
642
|
-
**Key Insight:** PII filtering
|
|
638
|
+
**Key Insight (Hybrid Implementation):** PII filtering uses a **hybrid model**:
|
|
639
|
+
- **:no_pii / :rails_filters:** Global middleware (PIIFilter) — skip or Rails filters only
|
|
640
|
+
- **:explicit_pii without exclude_adapters:** Global middleware — single filtered payload
|
|
641
|
+
- **:explicit_pii with exclude_adapters:** Per-adapter — PIIFilter produces `payload_rewrites`, Routing merges overrides per adapter (e.g. audit gets original for GDPR Art. 6(1)(c))
|
|
643
642
|
|
|
644
643
|
### 3.1. Rails Integration
|
|
645
644
|
|
|
@@ -1908,7 +1907,7 @@ RSpec.describe 'PII Filtering', type: :integration do
|
|
|
1908
1907
|
end
|
|
1909
1908
|
end
|
|
1910
1909
|
|
|
1911
|
-
describe '
|
|
1910
|
+
describe ':no_pii (Skip Filtering)' do
|
|
1912
1911
|
let(:event_class) { Events::HealthCheck }
|
|
1913
1912
|
let(:payload) { { status: 'ok' } }
|
|
1914
1913
|
|
|
@@ -1924,7 +1923,7 @@ RSpec.describe 'PII Filtering', type: :integration do
|
|
|
1924
1923
|
end
|
|
1925
1924
|
end
|
|
1926
1925
|
|
|
1927
|
-
describe '
|
|
1926
|
+
describe ':rails_filters (Rails Filters Only)' do
|
|
1928
1927
|
let(:event_class) { Events::OrderCreated }
|
|
1929
1928
|
let(:payload) do
|
|
1930
1929
|
{
|
|
@@ -1950,7 +1949,7 @@ RSpec.describe 'PII Filtering', type: :integration do
|
|
|
1950
1949
|
end
|
|
1951
1950
|
end
|
|
1952
1951
|
|
|
1953
|
-
describe '
|
|
1952
|
+
describe ':explicit_pii (Deep Filtering)' do
|
|
1954
1953
|
let(:event_class) { Events::UserRegistered }
|
|
1955
1954
|
let(:payload) do
|
|
1956
1955
|
{
|
|
@@ -1994,9 +1993,9 @@ RSpec.describe 'PII Filtering', type: :integration do
|
|
|
1994
1993
|
describe 'Performance Regression Test' do
|
|
1995
1994
|
it 'meets performance budget for 1000 events/sec' do
|
|
1996
1995
|
events = [
|
|
1997
|
-
{ class: Events::HealthCheck, payload: { status: 'ok' }, count: 500 }, #
|
|
1998
|
-
{ class: Events::OrderCreated, payload: { id: 'o1' }, count: 400 }, #
|
|
1999
|
-
{ class: Events::UserLogin, payload: { email: 'u@x.com' }, count: 100 } #
|
|
1996
|
+
{ class: Events::HealthCheck, payload: { status: 'ok' }, count: 500 }, # :no_pii
|
|
1997
|
+
{ class: Events::OrderCreated, payload: { id: 'o1' }, count: 400 }, # :rails_filters
|
|
1998
|
+
{ class: Events::UserLogin, payload: { email: 'u@x.com' }, count: 100 } # :explicit_pii
|
|
2000
1999
|
]
|
|
2001
2000
|
|
|
2002
2001
|
start = Time.now
|
|
@@ -2112,7 +2111,7 @@ end
|
|
|
2112
2111
|
bundle exec rspec spec/lib/e11y/security/pii_filtering_spec.rb
|
|
2113
2112
|
|
|
2114
2113
|
# Test specific tier
|
|
2115
|
-
bundle exec rspec spec/
|
|
2114
|
+
bundle exec rspec spec/e11y/middleware/pii_filtering_spec.rb -e "explicit_pii"
|
|
2116
2115
|
|
|
2117
2116
|
# Performance test
|
|
2118
2117
|
bundle exec rspec spec/lib/e11y/security/pii_filtering_spec.rb -e "Performance"
|
|
@@ -2183,31 +2182,65 @@ module E11y
|
|
|
2183
2182
|
def initialize(config)
|
|
2184
2183
|
@limit = config.limit # events per second
|
|
2185
2184
|
@window = config.window || 1.second
|
|
2186
|
-
@strategy = config.strategy || :sliding_window
|
|
2187
|
-
@redis = config.redis
|
|
2188
2185
|
|
|
2189
|
-
|
|
2186
|
+
# In-memory token bucket (no Redis dependency)
|
|
2187
|
+
@bucket = TokenBucket.new(
|
|
2188
|
+
capacity: @limit,
|
|
2189
|
+
refill_rate: @limit,
|
|
2190
|
+
window: @window.to_f
|
|
2191
|
+
)
|
|
2190
2192
|
end
|
|
2191
2193
|
|
|
2192
2194
|
def allow?(event_data = nil)
|
|
2193
|
-
@
|
|
2194
|
-
|
|
2195
|
-
current_rate = @counter.rate('global', window: @window)
|
|
2196
|
-
|
|
2197
|
-
current_rate <= @limit
|
|
2195
|
+
@bucket.allow?
|
|
2198
2196
|
end
|
|
2199
2197
|
|
|
2200
2198
|
def current_rate
|
|
2201
|
-
|
|
2199
|
+
# Calculate current rate based on tokens consumed
|
|
2200
|
+
@limit - @bucket.tokens
|
|
2202
2201
|
end
|
|
2203
2202
|
|
|
2204
2203
|
private
|
|
2205
2204
|
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2205
|
+
class TokenBucket
|
|
2206
|
+
def initialize(capacity:, refill_rate:, window:)
|
|
2207
|
+
@capacity = capacity
|
|
2208
|
+
@refill_rate = refill_rate
|
|
2209
|
+
@window = window
|
|
2210
|
+
@tokens = capacity.to_f
|
|
2211
|
+
@last_refill = Time.now
|
|
2212
|
+
@mutex = Mutex.new
|
|
2213
|
+
end
|
|
2214
|
+
|
|
2215
|
+
def allow?
|
|
2216
|
+
@mutex.synchronize do
|
|
2217
|
+
refill_tokens
|
|
2218
|
+
if @tokens >= 1.0
|
|
2219
|
+
@tokens -= 1.0
|
|
2220
|
+
true
|
|
2221
|
+
else
|
|
2222
|
+
false
|
|
2223
|
+
end
|
|
2224
|
+
end
|
|
2225
|
+
end
|
|
2226
|
+
|
|
2227
|
+
def tokens
|
|
2228
|
+
@mutex.synchronize do
|
|
2229
|
+
refill_tokens
|
|
2230
|
+
@tokens
|
|
2231
|
+
end
|
|
2232
|
+
end
|
|
2233
|
+
|
|
2234
|
+
private
|
|
2235
|
+
|
|
2236
|
+
def refill_tokens
|
|
2237
|
+
now = Time.now
|
|
2238
|
+
elapsed = now - @last_refill
|
|
2239
|
+
return if elapsed <= 0
|
|
2240
|
+
|
|
2241
|
+
tokens_to_add = elapsed * @refill_rate
|
|
2242
|
+
@tokens = [@tokens + tokens_to_add, @capacity].min
|
|
2243
|
+
@last_refill = now
|
|
2211
2244
|
end
|
|
2212
2245
|
end
|
|
2213
2246
|
end
|
|
@@ -2252,9 +2285,18 @@ module E11y
|
|
|
2252
2285
|
@limits = config.limits || {}
|
|
2253
2286
|
@default_limit = config.default_limit || Float::INFINITY
|
|
2254
2287
|
@window = config.window || 1.second
|
|
2255
|
-
@redis = config.redis
|
|
2256
2288
|
|
|
2257
|
-
|
|
2289
|
+
# In-memory token buckets per event type (lazy initialization)
|
|
2290
|
+
@buckets = Hash.new do |hash, event_name|
|
|
2291
|
+
limit = @limits[event_name] || @default_limit
|
|
2292
|
+
next nil if limit == Float::INFINITY
|
|
2293
|
+
hash[event_name] = TokenBucket.new(
|
|
2294
|
+
capacity: limit,
|
|
2295
|
+
refill_rate: limit,
|
|
2296
|
+
window: @window.to_f
|
|
2297
|
+
)
|
|
2298
|
+
end
|
|
2299
|
+
@mutex = Mutex.new
|
|
2258
2300
|
end
|
|
2259
2301
|
|
|
2260
2302
|
def allow?(event_data)
|
|
@@ -2263,15 +2305,15 @@ module E11y
|
|
|
2263
2305
|
|
|
2264
2306
|
return true if limit == Float::INFINITY
|
|
2265
2307
|
|
|
2266
|
-
@
|
|
2267
|
-
|
|
2268
|
-
current_rate = @counter.rate(event_name, window: @window)
|
|
2269
|
-
|
|
2270
|
-
current_rate <= limit
|
|
2308
|
+
bucket = @mutex.synchronize { @buckets[event_name] }
|
|
2309
|
+
bucket&.allow? || false
|
|
2271
2310
|
end
|
|
2272
2311
|
|
|
2273
2312
|
def current_rate(event_name)
|
|
2274
|
-
@
|
|
2313
|
+
bucket = @mutex.synchronize { @buckets[event_name] }
|
|
2314
|
+
return Float::INFINITY unless bucket
|
|
2315
|
+
limit = @limits[event_name] || @default_limit
|
|
2316
|
+
limit - bucket.tokens
|
|
2275
2317
|
end
|
|
2276
2318
|
end
|
|
2277
2319
|
end
|
|
@@ -2316,9 +2358,16 @@ module E11y
|
|
|
2316
2358
|
@limit = config.limit
|
|
2317
2359
|
@window = config.window || 1.minute
|
|
2318
2360
|
@context_keys = config.context_keys || [:user_id]
|
|
2319
|
-
@redis = config.redis
|
|
2320
2361
|
|
|
2321
|
-
|
|
2362
|
+
# In-memory token buckets per context value (lazy initialization)
|
|
2363
|
+
@buckets = Hash.new do |hash, context_value|
|
|
2364
|
+
hash[context_value] = TokenBucket.new(
|
|
2365
|
+
capacity: @limit,
|
|
2366
|
+
refill_rate: @limit,
|
|
2367
|
+
window: @window.to_f
|
|
2368
|
+
)
|
|
2369
|
+
end
|
|
2370
|
+
@mutex = Mutex.new
|
|
2322
2371
|
end
|
|
2323
2372
|
|
|
2324
2373
|
def allow?(event_data)
|
|
@@ -2326,12 +2375,8 @@ module E11y
|
|
|
2326
2375
|
|
|
2327
2376
|
return true unless context_value
|
|
2328
2377
|
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
current_rate = @counter.rate(key, window: @window)
|
|
2333
|
-
|
|
2334
|
-
current_rate <= @limit
|
|
2378
|
+
bucket = @mutex.synchronize { @buckets[context_value] }
|
|
2379
|
+
bucket.allow?
|
|
2335
2380
|
end
|
|
2336
2381
|
|
|
2337
2382
|
private
|
|
@@ -2376,88 +2421,58 @@ end
|
|
|
2376
2421
|
|
|
2377
2422
|
---
|
|
2378
2423
|
|
|
2379
|
-
### 4.4.
|
|
2424
|
+
### 4.4. In-Memory Token Bucket Implementation
|
|
2425
|
+
|
|
2426
|
+
**Design Decision:** In-memory token bucket algorithm (no Redis dependency).
|
|
2380
2427
|
|
|
2381
|
-
**
|
|
2428
|
+
**Rationale:**
|
|
2429
|
+
- ✅ **Fast:** No network latency (O(1) operations, ~0.003ms per event)
|
|
2430
|
+
- ✅ **Simple:** No external dependencies (Redis not required)
|
|
2431
|
+
- ✅ **Thread-safe:** Mutex-protected token buckets
|
|
2432
|
+
- ✅ **Smooth rate limiting:** Token bucket avoids bursty behavior
|
|
2433
|
+
- ⚠️ **Trade-off:** Per-process limits (not shared across instances)
|
|
2382
2434
|
|
|
2435
|
+
**Current Implementation:**
|
|
2383
2436
|
```ruby
|
|
2384
2437
|
module E11y
|
|
2385
|
-
module
|
|
2386
|
-
class
|
|
2387
|
-
def initialize(
|
|
2388
|
-
|
|
2389
|
-
@
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
when :sliding_window
|
|
2406
|
-
count_sliding_window(key, window)
|
|
2407
|
-
when :fixed_window
|
|
2408
|
-
count_fixed_window(key, window)
|
|
2409
|
-
when :token_bucket
|
|
2410
|
-
tokens_remaining(key)
|
|
2411
|
-
end
|
|
2412
|
-
end
|
|
2413
|
-
|
|
2414
|
-
private
|
|
2415
|
-
|
|
2416
|
-
# Sliding Window (most accurate)
|
|
2417
|
-
def increment_sliding_window(key)
|
|
2418
|
-
now = Time.now.to_f
|
|
2419
|
-
redis_key = "e11y:rate:#{key}"
|
|
2420
|
-
|
|
2421
|
-
@redis.multi do |multi|
|
|
2422
|
-
multi.zadd(redis_key, now, "#{now}:#{SecureRandom.uuid}")
|
|
2423
|
-
multi.expire(redis_key, 60)
|
|
2424
|
-
end
|
|
2425
|
-
end
|
|
2426
|
-
|
|
2427
|
-
def count_sliding_window(key, window)
|
|
2428
|
-
now = Time.now.to_f
|
|
2429
|
-
cutoff = now - window.to_f
|
|
2430
|
-
redis_key = "e11y:rate:#{key}"
|
|
2431
|
-
|
|
2432
|
-
# Remove old entries
|
|
2433
|
-
@redis.zremrangebyscore(redis_key, '-inf', cutoff)
|
|
2434
|
-
|
|
2435
|
-
# Count remaining
|
|
2436
|
-
@redis.zcard(redis_key)
|
|
2437
|
-
end
|
|
2438
|
-
|
|
2439
|
-
# Fixed Window (simpler, less accurate)
|
|
2440
|
-
def increment_fixed_window(key)
|
|
2441
|
-
window_key = current_window_key(key)
|
|
2442
|
-
|
|
2443
|
-
@redis.multi do |multi|
|
|
2444
|
-
multi.incr(window_key)
|
|
2445
|
-
multi.expire(window_key, 60)
|
|
2438
|
+
module Middleware
|
|
2439
|
+
class RateLimiting < Base
|
|
2440
|
+
def initialize(app, global_limit: 10_000, per_event_limit: 1_000, window: 1.0)
|
|
2441
|
+
super(app)
|
|
2442
|
+
@global_limit = global_limit
|
|
2443
|
+
@per_event_limit = per_event_limit
|
|
2444
|
+
@window = window
|
|
2445
|
+
|
|
2446
|
+
# Token buckets for rate limiting (in-memory)
|
|
2447
|
+
@global_bucket = TokenBucket.new(
|
|
2448
|
+
capacity: @global_limit,
|
|
2449
|
+
refill_rate: @global_limit,
|
|
2450
|
+
window: @window
|
|
2451
|
+
)
|
|
2452
|
+
@per_event_buckets = Hash.new do |hash, event_name|
|
|
2453
|
+
hash[event_name] = TokenBucket.new(
|
|
2454
|
+
capacity: @per_event_limit,
|
|
2455
|
+
refill_rate: @per_event_limit,
|
|
2456
|
+
window: @window
|
|
2457
|
+
)
|
|
2446
2458
|
end
|
|
2459
|
+
|
|
2460
|
+
@mutex = Mutex.new
|
|
2447
2461
|
end
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2462
|
+
end
|
|
2463
|
+
end
|
|
2464
|
+
end
|
|
2465
|
+
```
|
|
2466
|
+
|
|
2467
|
+
**Why Not Redis?**
|
|
2468
|
+
- ❌ **Complexity:** Adds external dependency and infrastructure overhead
|
|
2469
|
+
- ❌ **Latency:** Network round-trip adds ~0.25ms per event (83x slower)
|
|
2470
|
+
- ❌ **Not needed:** Per-process rate limiting is sufficient for event tracking
|
|
2471
|
+
- ✅ **User feedback:** "outdated solution"
|
|
2472
|
+
|
|
2473
|
+
**Note:** In-memory rate limiting is sufficient for most use cases. Each application process maintains its own rate limits, which is appropriate for event tracking workloads. If distributed rate limiting is needed in the future, it can be added as an optional feature.
|
|
2474
|
+
|
|
2475
|
+
---
|
|
2461
2476
|
redis_key = "e11y:rate:bucket:#{key}"
|
|
2462
2477
|
|
|
2463
2478
|
# Lua script for atomic token consumption
|
|
@@ -3213,44 +3228,24 @@ end
|
|
|
3213
3228
|
```ruby
|
|
3214
3229
|
# config/initializers/e11y.rb
|
|
3215
3230
|
E11y.configure do |config|
|
|
3216
|
-
config.
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
block_mode :silent # Options: :silent, :warn, :raise
|
|
3235
|
-
|
|
3236
|
-
# Monitoring
|
|
3237
|
-
on_blocked_key do |key, value, caller_location|
|
|
3238
|
-
# Track violations for security audit
|
|
3239
|
-
Yabeda.e11y_baggage_pii_blocked.increment(
|
|
3240
|
-
key: key,
|
|
3241
|
-
service: ENV['SERVICE_NAME']
|
|
3242
|
-
)
|
|
3243
|
-
|
|
3244
|
-
# Alert on critical violations
|
|
3245
|
-
if key.match?(/email|password|ssn|credit_card/)
|
|
3246
|
-
Sentry.capture_message(
|
|
3247
|
-
"Critical PII blocked from baggage: #{key}",
|
|
3248
|
-
level: :warning,
|
|
3249
|
-
extra: { caller: caller_location }
|
|
3250
|
-
)
|
|
3251
|
-
end
|
|
3252
|
-
end
|
|
3253
|
-
end
|
|
3231
|
+
config.security_baggage_protection_enabled = true # ✅ CRITICAL: Always enable in production
|
|
3232
|
+
|
|
3233
|
+
# Allowlist: Only these keys allowed in baggage
|
|
3234
|
+
config.security_baggage_protection_allowed_keys = [
|
|
3235
|
+
'trace_id',
|
|
3236
|
+
'span_id',
|
|
3237
|
+
'environment',
|
|
3238
|
+
'version',
|
|
3239
|
+
'service_name',
|
|
3240
|
+
'deployment_id',
|
|
3241
|
+
'request_id',
|
|
3242
|
+
# Custom safe keys (optional):
|
|
3243
|
+
'feature_flag_id', # Feature flag identifier (not PII)
|
|
3244
|
+
'ab_test_variant' # A/B test variant (not PII)
|
|
3245
|
+
]
|
|
3246
|
+
|
|
3247
|
+
# Block mode (how to handle violations)
|
|
3248
|
+
config.security_baggage_protection_block_mode = :silent # Options: :silent, :warn, :raise
|
|
3254
3249
|
end
|
|
3255
3250
|
```
|
|
3256
3251
|
|
|
@@ -3314,14 +3309,12 @@ OpenTelemetry::Baggage.set_value('request_id', SecureRandom.uuid)
|
|
|
3314
3309
|
```ruby
|
|
3315
3310
|
# config/environments/development.rb
|
|
3316
3311
|
E11y.configure do |config|
|
|
3317
|
-
config.
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
allowed_keys E11y::Middleware::BaggageProtection::ALLOWED_KEYS
|
|
3324
|
-
end
|
|
3312
|
+
config.security_baggage_protection_enabled = true
|
|
3313
|
+
|
|
3314
|
+
# RAISE exception on blocked keys (fail fast)
|
|
3315
|
+
config.security_baggage_protection_block_mode = :raise # ← Developer sees error immediately
|
|
3316
|
+
|
|
3317
|
+
config.security_baggage_protection_allowed_keys = E11y::BAGGAGE_PROTECTION_DEFAULT_ALLOWED_KEYS
|
|
3325
3318
|
end
|
|
3326
3319
|
|
|
3327
3320
|
# Developer tries to set PII in baggage:
|
|
@@ -3426,11 +3419,11 @@ Events::UserLogin.track(
|
|
|
3426
3419
|
|
|
3427
3420
|
**Metadata Flags:**
|
|
3428
3421
|
- `:pii_filtered` - Event already went through PII filtering (set by PII filter middleware)
|
|
3429
|
-
- `:
|
|
3422
|
+
- `:dlq_replayed` - Event is being replayed from DLQ (set by DLQ replay service)
|
|
3430
3423
|
|
|
3431
3424
|
**When to Skip PII Filtering:**
|
|
3432
3425
|
1. Event has `:pii_filtered => true` (already processed)
|
|
3433
|
-
2. Event has `:
|
|
3426
|
+
2. Event has `:dlq_replayed => true` (replay scenario)
|
|
3434
3427
|
3. Both flags present → skip PII filtering entirely
|
|
3435
3428
|
|
|
3436
3429
|
### 5.6.3. PiiFilter Middleware with Replay Detection
|
|
@@ -3457,7 +3450,7 @@ module E11y
|
|
|
3457
3450
|
|
|
3458
3451
|
# Check if event contains PII
|
|
3459
3452
|
unless event_class.contains_pii?
|
|
3460
|
-
# No PII declared, skip filtering (
|
|
3453
|
+
# No PII declared, skip filtering (:no_pii)
|
|
3461
3454
|
return event_data
|
|
3462
3455
|
end
|
|
3463
3456
|
|
|
@@ -3477,7 +3470,7 @@ module E11y
|
|
|
3477
3470
|
metadata = event_data[:metadata] || {}
|
|
3478
3471
|
|
|
3479
3472
|
# Check for replay flag
|
|
3480
|
-
return true if metadata[:
|
|
3473
|
+
return true if metadata[:dlq_replayed]
|
|
3481
3474
|
|
|
3482
3475
|
# Check for already-filtered flag
|
|
3483
3476
|
return true if metadata[:pii_filtered]
|
|
@@ -3507,7 +3500,7 @@ module E11y
|
|
|
3507
3500
|
|
|
3508
3501
|
# ✅ CRITICAL: Mark as replayed (skip transformations)
|
|
3509
3502
|
event_data[:metadata] ||= {}
|
|
3510
|
-
event_data[:metadata][:
|
|
3503
|
+
event_data[:metadata][:dlq_replayed] = true
|
|
3511
3504
|
event_data[:metadata][:pii_filtered] = true # Already filtered!
|
|
3512
3505
|
event_data[:metadata][:replayed_at] = Time.now.utc.iso8601
|
|
3513
3506
|
event_data[:metadata][:original_event_id] = event_data[:event_id]
|
|
@@ -3560,7 +3553,7 @@ E11y.configure do |config|
|
|
|
3560
3553
|
config.dlq.replay do
|
|
3561
3554
|
# Metadata flags for replayed events
|
|
3562
3555
|
set_metadata_flags do
|
|
3563
|
-
|
|
3556
|
+
dlq_replayed true
|
|
3564
3557
|
pii_filtered true
|
|
3565
3558
|
replay_timestamp true
|
|
3566
3559
|
original_event_id true
|
|
@@ -4022,7 +4015,7 @@ RSpec.describe E11y::RateLimiting do
|
|
|
4022
4015
|
end
|
|
4023
4016
|
|
|
4024
4017
|
# Exceed limit
|
|
4025
|
-
E11y.config.
|
|
4018
|
+
E11y.config.rate_limiting_global_limit = 100
|
|
4026
4019
|
|
|
4027
4020
|
expect(Events::Test.track(message: 'over limit')).to be_falsey
|
|
4028
4021
|
end
|
data/docs/{ADR-007-opentelemetry-integration.md → architecture/ADR-007-opentelemetry-integration.md}
RENAMED
|
@@ -127,25 +127,7 @@ C4Context
|
|
|
127
127
|
|
|
128
128
|
**Configuration:**
|
|
129
129
|
|
|
130
|
-
|
|
131
|
-
# config/initializers/e11y.rb
|
|
132
|
-
E11y.configure do |config|
|
|
133
|
-
# Option 1: Yabeda only (DEFAULT, recommended for Rails)
|
|
134
|
-
config.metrics do
|
|
135
|
-
backend :yabeda # Prometheus via Yabeda
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
# Option 2: OpenTelemetry only (for OTLP backends)
|
|
139
|
-
# config.metrics do
|
|
140
|
-
# backend :opentelemetry # OTLP via OTel SDK
|
|
141
|
-
# end
|
|
142
|
-
|
|
143
|
-
# Option 3: Both (for migration period ONLY)
|
|
144
|
-
# config.metrics do
|
|
145
|
-
# backend [:yabeda, :opentelemetry] # ⚠️ DOUBLE OVERHEAD!
|
|
146
|
-
# end
|
|
147
|
-
end
|
|
148
|
-
```
|
|
130
|
+
Metrics are emitted via Yabeda adapter when events with `metrics do` are tracked. Register Yabeda adapter in `config.adapters`.
|
|
149
131
|
|
|
150
132
|
**Metrics Adapter Pattern:**
|
|
151
133
|
|
|
@@ -756,7 +738,7 @@ module E11y
|
|
|
756
738
|
return true if event[:severity].in?([:error, :fatal])
|
|
757
739
|
|
|
758
740
|
# Check if event matches span creation patterns
|
|
759
|
-
E11y.config.
|
|
741
|
+
E11y.config.opentelemetry_span_creation_patterns.any? do |pattern|
|
|
760
742
|
File.fnmatch(pattern, event[:event_name])
|
|
761
743
|
end
|
|
762
744
|
end
|
|
@@ -1068,7 +1050,7 @@ module E11y
|
|
|
1068
1050
|
return true if event[:severity].in?([:error, :fatal])
|
|
1069
1051
|
|
|
1070
1052
|
# Check configured patterns
|
|
1071
|
-
patterns = E11y.config.
|
|
1053
|
+
patterns = E11y.config.opentelemetry_span_creation_patterns || []
|
|
1072
1054
|
patterns.any? { |pattern| File.fnmatch(pattern, event[:event_name]) }
|
|
1073
1055
|
end
|
|
1074
1056
|
|