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.
Files changed (230) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +130 -10
  3. data/CHANGELOG.md +56 -1
  4. data/CLAUDE.md +168 -0
  5. data/CONTRIBUTING.md +640 -0
  6. data/README.md +134 -702
  7. data/RELEASE.md +18 -3
  8. data/Rakefile +108 -29
  9. data/config/README.md +1 -1
  10. data/config/loki-local-config.yaml +12 -0
  11. data/config/otel-collector-config.yaml +44 -0
  12. data/cucumber.yml +1 -0
  13. data/docker-compose.yml +18 -2
  14. data/docs/ADAPTERS.md +76 -0
  15. data/docs/ADAPTIVE_SAMPLING.md +59 -0
  16. data/docs/COMPARISON.md +104 -0
  17. data/docs/CONFIGURATION.md +52 -0
  18. data/docs/DISTRIBUTED_TRACING.md +44 -0
  19. data/docs/LIMITATIONS.md +13 -0
  20. data/docs/METRICS_DSL.md +84 -0
  21. data/docs/PERFORMANCE.md +60 -0
  22. data/docs/PII_FILTERING.md +40 -0
  23. data/docs/PRESETS.md +65 -0
  24. data/docs/QUICK-START.md +546 -587
  25. data/docs/RAILS_INTEGRATION.md +29 -0
  26. data/docs/SCHEMA_VALIDATION.md +63 -0
  27. data/docs/SLO-PROMQL-ALERTS.md +161 -0
  28. data/docs/TESTING.md +69 -0
  29. data/docs/{ADR-001-architecture.md → architecture/ADR-001-architecture.md} +35 -64
  30. data/docs/{ADR-002-metrics-yabeda.md → architecture/ADR-002-metrics-yabeda.md} +62 -236
  31. data/docs/{ADR-003-slo-observability.md → architecture/ADR-003-slo-observability.md} +27 -466
  32. data/docs/{ADR-004-adapter-architecture.md → architecture/ADR-004-adapter-architecture.md} +163 -146
  33. data/docs/{ADR-005-tracing-context.md → architecture/ADR-005-tracing-context.md} +10 -9
  34. data/docs/{ADR-006-security-compliance.md → architecture/ADR-006-security-compliance.md} +184 -191
  35. data/docs/{ADR-007-opentelemetry-integration.md → architecture/ADR-007-opentelemetry-integration.md} +3 -21
  36. data/docs/{ADR-008-rails-integration.md → architecture/ADR-008-rails-integration.md} +209 -339
  37. data/docs/{ADR-009-cost-optimization.md → architecture/ADR-009-cost-optimization.md} +45 -54
  38. data/docs/architecture/ADR-010-developer-experience.md +522 -0
  39. data/docs/{ADR-011-testing-strategy.md → architecture/ADR-011-testing-strategy.md} +41 -83
  40. data/docs/{ADR-013-reliability-error-handling.md → architecture/ADR-013-reliability-error-handling.md} +37 -12
  41. data/docs/{ADR-014-event-driven-slo.md → architecture/ADR-014-event-driven-slo.md} +12 -24
  42. data/docs/{ADR-015-middleware-order.md → architecture/ADR-015-middleware-order.md} +23 -41
  43. data/docs/{ADR-016-self-monitoring-slo.md → architecture/ADR-016-self-monitoring-slo.md} +52 -349
  44. data/docs/{ADR-017-multi-rails-compatibility.md → architecture/ADR-017-multi-rails-compatibility.md} +4 -11
  45. data/docs/architecture/ADR-018-memory-optimization.md +366 -0
  46. data/docs/{ADR-INDEX.md → architecture/ADR-INDEX.md} +11 -6
  47. data/docs/{00-ICP-AND-TIMELINE.md → prd/00-ICP-AND-TIMELINE.md} +6 -6
  48. data/docs/{01-SCALE-REQUIREMENTS.md → prd/01-SCALE-REQUIREMENTS.md} +6 -6
  49. data/docs/prd/01-overview-vision.md +19 -14
  50. data/docs/use_cases/README.md +22 -23
  51. data/docs/use_cases/UC-001-request-scoped-debug-buffering.md +50 -44
  52. data/docs/use_cases/UC-002-business-event-tracking.md +26 -95
  53. data/docs/use_cases/UC-003-event-metrics.md +66 -0
  54. data/docs/use_cases/UC-004-zero-config-slo-tracking.md +42 -101
  55. data/docs/use_cases/UC-005-sentry-integration.md +13 -15
  56. data/docs/use_cases/UC-006-trace-context-management.md +30 -28
  57. data/docs/use_cases/UC-007-pii-filtering.md +35 -87
  58. data/docs/use_cases/UC-008-opentelemetry-integration.md +51 -89
  59. data/docs/use_cases/UC-009-multi-service-tracing.md +4 -4
  60. data/docs/use_cases/UC-010-background-job-tracking.md +5 -5
  61. data/docs/use_cases/UC-011-rate-limiting.md +95 -168
  62. data/docs/use_cases/UC-012-audit-trail.md +21 -46
  63. data/docs/use_cases/UC-013-high-cardinality-protection.md +29 -167
  64. data/docs/use_cases/UC-014-adaptive-sampling.md +2 -2
  65. data/docs/use_cases/UC-015-cost-optimization.md +46 -99
  66. data/docs/use_cases/UC-016-rails-logger-migration.md +39 -213
  67. data/docs/use_cases/UC-017-local-development.md +203 -777
  68. data/docs/use_cases/UC-018-testing-events.md +3 -3
  69. data/docs/use_cases/UC-019-retention-based-routing.md +53 -106
  70. data/docs/use_cases/UC-020-event-versioning.md +8 -9
  71. data/docs/use_cases/UC-021-error-handling-retry-dlq.md +18 -22
  72. data/docs/use_cases/UC-022-event-registry.md +15 -21
  73. data/docs/use_cases/backlog.md +119 -87
  74. data/e11y.gemspec +2 -2
  75. data/gems/e11y-devtools/README.md +136 -0
  76. data/gems/e11y-devtools/config/routes.rb +8 -0
  77. data/gems/e11y-devtools/e11y-devtools.gemspec +25 -0
  78. data/gems/e11y-devtools/exe/e11y +34 -0
  79. data/gems/e11y-devtools/lib/e11y/devtools/mcp/server.rb +96 -0
  80. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tool_base.rb +25 -0
  81. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/clear.rb +31 -0
  82. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/errors.rb +35 -0
  83. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/event_detail.rb +33 -0
  84. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/events_by_trace.rb +33 -0
  85. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/interactions.rb +40 -0
  86. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/recent_events.rb +34 -0
  87. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/search.rb +34 -0
  88. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/stats.rb +30 -0
  89. data/gems/e11y-devtools/lib/e11y/devtools/overlay/assets/overlay.js +115 -0
  90. data/gems/e11y-devtools/lib/e11y/devtools/overlay/controller.rb +54 -0
  91. data/gems/e11y-devtools/lib/e11y/devtools/overlay/engine.rb +26 -0
  92. data/gems/e11y-devtools/lib/e11y/devtools/overlay/middleware.rb +80 -0
  93. data/gems/e11y-devtools/lib/e11y/devtools/overlay/rails_controller.rb +42 -0
  94. data/gems/e11y-devtools/lib/e11y/devtools/tui/app.rb +262 -0
  95. data/gems/e11y-devtools/lib/e11y/devtools/tui/grouping.rb +66 -0
  96. data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/event_detail.rb +62 -0
  97. data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/event_list.rb +70 -0
  98. data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/interaction_list.rb +47 -0
  99. data/gems/e11y-devtools/lib/e11y/devtools/version.rb +8 -0
  100. data/gems/e11y-devtools/lib/e11y/devtools.rb +13 -0
  101. data/gems/e11y-devtools/spec/e11y/devtools/mcp/tools_spec.rb +107 -0
  102. data/gems/e11y-devtools/spec/e11y/devtools/overlay/controller_spec.rb +58 -0
  103. data/gems/e11y-devtools/spec/e11y/devtools/overlay/middleware_spec.rb +46 -0
  104. data/gems/e11y-devtools/spec/e11y/devtools/tui/app_spec.rb +85 -0
  105. data/gems/e11y-devtools/spec/e11y/devtools/tui/grouping_spec.rb +64 -0
  106. data/gems/e11y-devtools/spec/spec_helper.rb +5 -0
  107. data/gems/e11y-devtools/spec/tui/widgets/event_list_spec.rb +44 -0
  108. data/gems/e11y-devtools/spec/tui/widgets/interaction_list_spec.rb +62 -0
  109. data/lib/e11y/adapters/audit_encrypted.rb +53 -11
  110. data/lib/e11y/adapters/base.rb +33 -34
  111. data/lib/e11y/adapters/dev_log/file_store.rb +143 -0
  112. data/lib/e11y/adapters/dev_log/query.rb +219 -0
  113. data/lib/e11y/adapters/dev_log.rb +118 -0
  114. data/lib/e11y/adapters/file.rb +3 -6
  115. data/lib/e11y/adapters/in_memory.rb +52 -5
  116. data/lib/e11y/adapters/in_memory_test.rb +29 -0
  117. data/lib/e11y/adapters/loki.rb +58 -23
  118. data/lib/e11y/adapters/null.rb +82 -0
  119. data/lib/e11y/adapters/opentelemetry_collector.rb +183 -0
  120. data/lib/e11y/adapters/otel_logs.rb +136 -23
  121. data/lib/e11y/adapters/sentry.rb +4 -7
  122. data/lib/e11y/adapters/stdout.rb +73 -7
  123. data/lib/e11y/adapters/yabeda.rb +153 -29
  124. data/lib/e11y/buffers/adaptive_buffer.rb +3 -17
  125. data/lib/e11y/buffers/{request_scoped_buffer.rb → ephemeral_buffer.rb} +72 -58
  126. data/lib/e11y/buffers/ring_buffer.rb +3 -16
  127. data/lib/e11y/configuration.rb +272 -0
  128. data/lib/e11y/console.rb +10 -17
  129. data/lib/e11y/current.rb +53 -1
  130. data/lib/e11y/debug/pipeline_inspector.rb +96 -0
  131. data/lib/e11y/documentation/generator.rb +48 -0
  132. data/lib/e11y/event/base.rb +176 -82
  133. data/lib/e11y/event/value_sampling_config.rb +1 -5
  134. data/lib/e11y/events/rails/database/query.rb +1 -4
  135. data/lib/e11y/events/rails/job/failed.rb +2 -0
  136. data/lib/e11y/instruments/active_job.rb +46 -12
  137. data/lib/e11y/instruments/rails_instrumentation.rb +49 -24
  138. data/lib/e11y/instruments/sidekiq.rb +137 -31
  139. data/lib/e11y/linters/base.rb +11 -0
  140. data/lib/e11y/linters/pii/pii_declaration_linter.rb +120 -0
  141. data/lib/e11y/linters/slo/config_consistency_linter.rb +76 -0
  142. data/lib/e11y/linters/slo/explicit_declaration_linter.rb +36 -0
  143. data/lib/e11y/linters/slo/slo_status_from_linter.rb +41 -0
  144. data/lib/e11y/logger/bridge.rb +26 -7
  145. data/lib/e11y/metrics/cardinality_protection.rb +10 -15
  146. data/lib/e11y/metrics/cardinality_tracker.rb +16 -6
  147. data/lib/e11y/metrics/registry.rb +3 -5
  148. data/lib/e11y/metrics/test_backend.rb +62 -0
  149. data/lib/e11y/metrics.rb +56 -10
  150. data/lib/e11y/middleware/adapter_resolver.rb +40 -0
  151. data/lib/e11y/middleware/audit_signing.rb +43 -6
  152. data/lib/e11y/middleware/baggage_protection.rb +75 -0
  153. data/lib/e11y/middleware/dev_log_source.rb +24 -0
  154. data/lib/e11y/middleware/event_slo.rb +23 -9
  155. data/lib/e11y/middleware/otel_span.rb +23 -0
  156. data/lib/e11y/middleware/pii_filter.rb +104 -75
  157. data/lib/e11y/middleware/rate_limiting.rb +54 -27
  158. data/lib/e11y/middleware/request.rb +70 -23
  159. data/lib/e11y/middleware/routing.rb +78 -21
  160. data/lib/e11y/middleware/sampling.rb +66 -17
  161. data/lib/e11y/middleware/self_monitoring_emit.rb +39 -0
  162. data/lib/e11y/middleware/trace_context.rb +45 -10
  163. data/lib/e11y/middleware/track_latency.rb +34 -0
  164. data/lib/e11y/middleware/validation.rb +7 -16
  165. data/lib/e11y/middleware/versioning.rb +26 -22
  166. data/lib/e11y/opentelemetry/semantic_conventions.rb +109 -0
  167. data/lib/e11y/opentelemetry/span_creator.rb +142 -0
  168. data/lib/e11y/pii/patterns.rb +12 -1
  169. data/lib/e11y/pipeline/builder.rb +1 -1
  170. data/lib/e11y/presets/audit_event.rb +13 -2
  171. data/lib/e11y/railtie.rb +52 -15
  172. data/lib/e11y/registry.rb +306 -0
  173. data/lib/e11y/reliability/circuit_breaker.rb +19 -21
  174. data/lib/e11y/reliability/dlq/base.rb +71 -0
  175. data/lib/e11y/reliability/dlq/file_adapter.rb +301 -0
  176. data/lib/e11y/reliability/dlq/file_storage.rb +63 -34
  177. data/lib/e11y/reliability/dlq/filter.rb +37 -54
  178. data/lib/e11y/reliability/retry_handler.rb +26 -29
  179. data/lib/e11y/reliability/retry_rate_limiter.rb +3 -11
  180. data/lib/e11y/sampling/error_spike_detector.rb +0 -2
  181. data/lib/e11y/sampling/load_monitor.rb +5 -9
  182. data/lib/e11y/sampling/stratified_tracker.rb +18 -0
  183. data/lib/e11y/self_monitoring/buffer_monitor.rb +2 -0
  184. data/lib/e11y/self_monitoring/performance_monitor.rb +19 -61
  185. data/lib/e11y/self_monitoring/reliability_monitor.rb +4 -74
  186. data/lib/e11y/slo/config_loader.rb +40 -0
  187. data/lib/e11y/slo/config_validator.rb +58 -0
  188. data/lib/e11y/slo/dashboard_generator.rb +122 -0
  189. data/lib/e11y/slo/event_driven.rb +8 -0
  190. data/lib/e11y/slo/tracker.rb +31 -4
  191. data/lib/e11y/testing/have_tracked_event_matcher.rb +190 -0
  192. data/lib/e11y/testing/rspec_matchers.rb +21 -0
  193. data/lib/e11y/testing/snapshot_matcher.rb +86 -0
  194. data/lib/e11y/trace_context/sampler.rb +35 -0
  195. data/lib/e11y/tracing/faraday_middleware.rb +31 -0
  196. data/lib/e11y/tracing/net_http_patch.rb +33 -0
  197. data/lib/e11y/tracing/propagator.rb +116 -0
  198. data/lib/e11y/tracing.rb +47 -0
  199. data/lib/e11y/version.rb +1 -1
  200. data/lib/e11y/versioning/version_extractor.rb +32 -0
  201. data/lib/e11y.rb +141 -265
  202. data/lib/generators/e11y/event/event_generator.rb +22 -0
  203. data/lib/generators/e11y/event/templates/event.rb.tt +16 -0
  204. data/lib/generators/e11y/grafana_dashboard/grafana_dashboard_generator.rb +30 -0
  205. data/lib/generators/e11y/grafana_dashboard/templates/e11y_dashboard.json +81 -0
  206. data/lib/generators/e11y/install/install_generator.rb +34 -0
  207. data/lib/generators/e11y/install/templates/e11y.rb +239 -0
  208. data/lib/generators/e11y/prometheus_alerts/prometheus_alerts_generator.rb +29 -0
  209. data/lib/generators/e11y/prometheus_alerts/templates/e11y_alerts.yml +28 -0
  210. data/lib/tasks/e11y_docs.rake +30 -0
  211. data/lib/tasks/e11y_events.rake +71 -0
  212. data/lib/tasks/e11y_lint.rake +91 -0
  213. data/lib/tasks/e11y_slo.rake +29 -0
  214. metadata +129 -39
  215. data/docs/ADR-010-developer-experience.md +0 -2166
  216. data/docs/API-REFERENCE-L28.md +0 -914
  217. data/docs/COMPREHENSIVE-CONFIGURATION.md +0 -2366
  218. data/docs/CONTRIBUTING.md +0 -312
  219. data/docs/IMPLEMENTATION_NOTES.md +0 -2804
  220. data/docs/IMPLEMENTATION_PLAN.md +0 -1971
  221. data/docs/IMPLEMENTATION_PLAN_ARCHITECTURE.md +0 -586
  222. data/docs/PLAN.md +0 -148
  223. data/docs/README.md +0 -296
  224. data/docs/design/00-memory-optimization.md +0 -593
  225. data/docs/guides/MIGRATION-L27-L28.md +0 -692
  226. data/docs/guides/PERFORMANCE-BENCHMARKS.md +0 -434
  227. data/docs/guides/README.md +0 -44
  228. data/docs/use_cases/UC-003-pattern-based-metrics.md +0 -1627
  229. data/lib/e11y/adapters/registry.rb +0 -141
  230. /data/docs/{ADR-012-event-evolution.md → architecture/ADR-012-event-evolution.md} +0 -0
data/RELEASE.md CHANGED
@@ -20,14 +20,29 @@ For more control, see [Step-by-Step Release](#step-by-step-release) below.
20
20
 
21
21
  ## Pre-Release Checklist
22
22
 
23
+ ### Core
23
24
  - [ ] All changes documented in CHANGELOG.md under `[Unreleased]`
24
25
  - [ ] Version bumped: `rake release:bump`
25
- - [ ] All tests passing: `rake spec`
26
+ - [ ] All tests passing: `rake spec:all` (unit + integration)
27
+ - [ ] RuboCop clean: `bundle exec rubocop`
26
28
  - [ ] Changes committed
27
29
  - [ ] Git tag created
28
30
  - [ ] Published to RubyGems.org
29
31
  - [ ] GitHub release created
30
32
 
33
+ ### Documentation & Links
34
+ - [ ] All doc links valid (ADRs in `docs/architecture/`, use cases in `docs/use_cases/`)
35
+ - [ ] No broken references to deleted files (e.g. `docs/analysis/`, `docs/IMPLEMENTATION_PLAN.md`)
36
+ - [ ] README, CONTRIBUTING, CLAUDE.md reference correct paths
37
+ - [ ] All user-facing text in English (no Russian in docs/code comments)
38
+
39
+ ### Production Readiness
40
+ - [ ] SECURITY.md present (if handling sensitive data)
41
+ - [ ] LICENSE file present and correct
42
+ - [ ] No TODO/FIXME in critical paths
43
+ - [ ] Deprecation warnings documented (if any)
44
+ - [ ] Breaking changes clearly called out in CHANGELOG
45
+
31
46
  ## Step-by-Step Release
32
47
 
33
48
  ### Step 0: Bump Version
@@ -181,7 +196,7 @@ bundle install
181
196
  ## 📚 Documentation
182
197
 
183
198
  - **Quick Start**: [README.md](https://github.com/arturseletskiy/e11y#quick-start)
184
- - **Architecture**: [docs/ADR-INDEX.md](https://github.com/arturseletskiy/e11y/blob/main/docs/ADR-INDEX.md)
199
+ - **Architecture**: [docs/architecture/ADR-INDEX.md](https://github.com/arturseletskiy/e11y/blob/main/docs/architecture/ADR-INDEX.md)
185
200
  - **Benchmarks**: [benchmarks/README.md](https://github.com/arturseletskiy/e11y/blob/main/benchmarks/README.md)
186
201
 
187
202
  ## 🔥 What's New
@@ -192,7 +207,7 @@ See [CHANGELOG.md](https://github.com/arturseletskiy/e11y/blob/main/CHANGELOG.md
192
207
  - Event System with dry-schema validation
193
208
  - Pipeline Architecture (middleware-based)
194
209
  - 7 Adapters: Stdout, File, InMemory, Loki, Sentry, OpenTelemetry, Yabeda
195
- - 3 Buffer Types: RingBuffer, RequestScopedBuffer, AdaptiveBuffer
210
+ - 3 Buffer Types: RingBuffer, EphemeralBuffer, AdaptiveBuffer
196
211
  - Reliability: Retry, Circuit Breaker, Dead Letter Queue
197
212
 
198
213
  ### SLO Tracking (Phase 3)
data/Rakefile CHANGED
@@ -20,6 +20,12 @@ require "bundler/gem_tasks"
20
20
  require "rspec/core/rake_task"
21
21
  require "rubocop/rake_task"
22
22
 
23
+ def e11y_devtools_specs_available?
24
+ # Devtools specs live in monorepo; run them when the directory exists
25
+ # (no need for gem in bundle — spec_helper loads lib via path)
26
+ File.directory?(File.join(__dir__, "gems/e11y-devtools/spec"))
27
+ end
28
+
23
29
  RSpec::Core::RakeTask.new(:spec)
24
30
 
25
31
  RuboCop::RakeTask.new
@@ -36,7 +42,6 @@ namespace :spec do
36
42
  desc "Run integration tests (requires Rails, bundle install --with integration)"
37
43
  task :integration do
38
44
  # Run integration tests with explicit file patterns to avoid loading all specs
39
- # This prevents test pollution from unit test files
40
45
  sh "INTEGRATION=true bundle exec rspec " \
41
46
  "spec/integration/*.rb " \
42
47
  "spec/e11y/adapters/*_spec.rb " \
@@ -49,13 +54,27 @@ namespace :spec do
49
54
  sh "bundle exec rspec spec/e11y/railtie_integration_spec.rb --tag railtie_integration"
50
55
  end
51
56
 
52
- desc "Run all tests (unit + integration + railtie, ~1729 examples)"
57
+ desc "Run all tests (unit + memory + integration + railtie + cucumber)"
53
58
  task :all do
54
59
  puts "\n#{'=' * 80}"
55
60
  puts "Running UNIT tests (spec/e11y + top-level specs)..."
56
61
  puts "#{'=' * 80}\n"
57
62
  Rake::Task["spec:unit"].invoke
58
63
 
64
+ if e11y_devtools_specs_available?
65
+ puts "\n#{'=' * 80}"
66
+ puts "Running E11Y-DEVTOOLS unit tests (gems/e11y-devtools/spec/)..."
67
+ puts "#{'=' * 80}\n"
68
+ Rake::Task["spec:devtools"].invoke
69
+ else
70
+ puts "\n⏭️ Skipping e11y-devtools specs (gems/e11y-devtools/spec/ not found)"
71
+ end
72
+
73
+ puts "\n#{'=' * 80}"
74
+ puts "Running MEMORY tests (allocations, leaks, consumption)..."
75
+ puts "#{'=' * 80}\n"
76
+ Rake::Task["spec:memory"].invoke
77
+
59
78
  puts "\n#{'=' * 80}"
60
79
  puts "Running INTEGRATION tests (spec/integration)..."
61
80
  puts "#{'=' * 80}\n"
@@ -66,6 +85,15 @@ namespace :spec do
66
85
  puts "#{'=' * 80}\n"
67
86
  Rake::Task["spec:railtie"].invoke
68
87
 
88
+ if Rake::Task.task_defined?("cucumber:passing")
89
+ puts "\n#{'=' * 80}"
90
+ puts "Running CUCUMBER tests (features/, exclude @wip)..."
91
+ puts "#{'=' * 80}\n"
92
+ Rake::Task["cucumber:passing"].invoke
93
+ else
94
+ puts "\n⚠️ Skipping Cucumber (bundle install --with development)"
95
+ end
96
+
69
97
  puts "\n#{'=' * 80}"
70
98
  puts "✅ All test suites completed!"
71
99
  puts "#{'=' * 80}\n"
@@ -86,15 +114,31 @@ namespace :spec do
86
114
  sh "bundle exec rspec spec/e11y --tag benchmark"
87
115
  end
88
116
 
89
- desc "Run ALL tests including benchmarks (very slow)"
117
+ desc "Run memory profiling specs (allocations, leaks, consumption)"
118
+ task :memory do
119
+ sh "bundle exec rspec " \
120
+ "spec/e11y/memory_spec.rb " \
121
+ "spec/e11y/event/base_benchmark_spec.rb " \
122
+ "--tag memory --format documentation"
123
+ end
124
+
125
+ desc "Run e11y-devtools unit tests (gems/e11y-devtools/spec/)"
126
+ task :devtools do
127
+ sh "bundle exec rspec gems/e11y-devtools/spec/ --tag ~integration --format progress"
128
+ end
129
+
130
+ desc "Run ALL tests including benchmarks and cucumber (very slow)"
90
131
  task :everything do
91
132
  puts "\n#{'=' * 80}"
92
- puts "Running ALL tests (unit + integration + railtie + benchmarks)"
133
+ puts "Running ALL tests (unit + integration + railtie + cucumber + benchmarks)"
93
134
  puts "#{'=' * 80}\n"
94
135
  Rake::Task["spec:unit"].invoke
136
+ Rake::Task["spec:devtools"].invoke if e11y_devtools_specs_available?
95
137
  Rake::Task["spec:integration"].invoke
96
138
  Rake::Task["spec:railtie"].invoke
139
+ Rake::Task["cucumber:passing"].invoke if Rake::Task.task_defined?("cucumber:passing")
97
140
  Rake::Task["spec:benchmark"].invoke
141
+ Rake::Task["spec:memory"].invoke
98
142
 
99
143
  puts "\n#{'=' * 80}"
100
144
  puts "✅ All test suites including benchmarks completed!"
@@ -136,32 +180,32 @@ namespace :release do
136
180
  task :bump do
137
181
  require_relative "lib/e11y/version"
138
182
  current_version = E11y::VERSION
139
-
183
+
140
184
  puts "\n#{'=' * 80}"
141
185
  puts "📝 Version Bump"
142
186
  puts "#{'=' * 80}\n"
143
187
  puts "Current version: #{current_version}"
144
188
  puts "\nEnter new version (e.g., 0.2.0, 1.0.0):"
145
-
189
+
146
190
  new_version = $stdin.gets.chomp.strip
147
-
191
+
148
192
  if new_version.empty?
149
193
  puts "❌ Error: Version cannot be empty"
150
194
  exit 1
151
195
  end
152
-
196
+
153
197
  unless new_version.match?(/^\d+\.\d+\.\d+$/)
154
198
  puts "❌ Error: Invalid version format. Use semantic versioning (e.g., 0.2.0)"
155
199
  exit 1
156
200
  end
157
-
201
+
158
202
  if new_version == current_version
159
203
  puts "⚠️ Warning: New version is the same as current version"
160
204
  puts "Continue anyway? (y/N)"
161
205
  response = $stdin.gets.chomp.downcase
162
- exit 0 unless response == "y" || response == "yes"
206
+ exit 0 unless %w[y yes].include?(response)
163
207
  end
164
-
208
+
165
209
  puts "\n[1/3] Updating lib/e11y/version.rb..."
166
210
  version_file = "lib/e11y/version.rb"
167
211
  version_content = File.read(version_file)
@@ -171,58 +215,60 @@ namespace :release do
171
215
  )
172
216
  File.write(version_file, updated_version_content)
173
217
  puts "✅ Updated: #{current_version} → #{new_version}"
174
-
218
+
175
219
  puts "\n[2/3] Updating CHANGELOG.md..."
176
220
  changelog_file = "CHANGELOG.md"
177
221
  changelog_content = File.read(changelog_file)
178
-
222
+
179
223
  # Check if there's an [Unreleased] section
224
+ today = Time.now.strftime("%Y-%m-%d")
180
225
  if changelog_content.include?("## [Unreleased]")
181
226
  # Replace [Unreleased] with version and date
182
- today = Time.now.strftime("%Y-%m-%d")
183
227
  updated_changelog = changelog_content.sub(
184
- /## \[Unreleased\]/,
228
+ "## [Unreleased]",
185
229
  "## [#{new_version}] - #{today}"
186
230
  )
187
-
231
+
188
232
  # Add new [Unreleased] section at the top
233
+ unreleased = "## [Unreleased]\n\n### Added\n\n### Changed\n\n### Fixed\n\n"
234
+ unreleased += "### Deprecated\n\n### Removed\n\n### Security\n\n\\1"
189
235
  updated_changelog = updated_changelog.sub(
190
236
  /(## \[#{Regexp.escape(new_version)}\] - #{today})/,
191
- "## [Unreleased]\n\n### Added\n\n### Changed\n\n### Fixed\n\n### Deprecated\n\n### Removed\n\n### Security\n\n\\1"
237
+ unreleased
192
238
  )
193
-
239
+
194
240
  File.write(changelog_file, updated_changelog)
195
241
  puts "✅ Updated CHANGELOG.md:"
196
242
  puts " - [Unreleased] → [#{new_version}] - #{today}"
197
243
  puts " - Added new [Unreleased] section"
198
244
  else
199
245
  # No [Unreleased] section, just add version entry
200
- today = Time.now.strftime("%Y-%m-%d")
201
-
246
+
202
247
  # Find where to insert (after the header, before first version)
203
- if changelog_content =~ /(## \[\d+\.\d+\.\d+\])/
204
- updated_changelog = changelog_content.sub(
205
- /(## \[\d+\.\d+\.\d+\])/,
206
- "## [Unreleased]\n\n### Added\n\n### Changed\n\n### Fixed\n\n### Deprecated\n\n### Removed\n\n### Security\n\n## [#{new_version}] - #{today}\n\n### Added\n- Version bump\n\n\\1"
207
- )
248
+
249
+ if /(## \[\d+\.\d+\.\d+\])/.match?(changelog_content)
250
+ new_section = "## [Unreleased]\n\n### Added\n\n### Changed\n\n### Fixed\n\n"
251
+ new_section += "### Deprecated\n\n### Removed\n\n### Security\n\n"
252
+ new_section += "## [#{new_version}] - #{today}\n\n### Added\n- Version bump\n\n\\1"
253
+ updated_changelog = changelog_content.sub(/(## \[\d+\.\d+\.\d+\])/, new_section)
208
254
  else
209
255
  # No previous versions, add after header
210
256
  header_end = changelog_content.index("\n\n") || 0
211
257
  header = changelog_content[0..header_end]
212
- rest = changelog_content[header_end + 1..-1] || ""
258
+ rest = changelog_content[(header_end + 1)..] || ""
213
259
  updated_changelog = "#{header}\n## [#{new_version}] - #{today}\n\n### Added\n- Initial release\n\n#{rest}"
214
260
  end
215
-
261
+
216
262
  File.write(changelog_file, updated_changelog)
217
263
  puts "✅ Added version [#{new_version}] - #{today} to CHANGELOG.md"
218
264
  end
219
-
265
+
220
266
  puts "\n[3/3] Summary"
221
267
  puts "✅ Version bumped: #{current_version} → #{new_version}"
222
268
  puts "✅ Files updated:"
223
269
  puts " - lib/e11y/version.rb"
224
270
  puts " - CHANGELOG.md"
225
-
271
+
226
272
  puts "\n#{'=' * 80}"
227
273
  puts "Next steps:"
228
274
  puts " 1. Review changes: git diff"
@@ -412,3 +458,36 @@ namespace :release do
412
458
  puts "✅ Clean complete"
413
459
  end
414
460
  end
461
+
462
+ # ---------------------------------------------------------------------------
463
+ # Cucumber acceptance tests
464
+ # ---------------------------------------------------------------------------
465
+ begin
466
+ require "cucumber/rake/task"
467
+
468
+ namespace :cucumber do
469
+ desc "Run all Cucumber acceptance tests"
470
+ Cucumber::Rake::Task.new(:all) do |t|
471
+ t.cucumber_opts = ["--format", "progress", "features/"]
472
+ end
473
+
474
+ desc "Run only @wip (known-bug) Cucumber scenarios"
475
+ Cucumber::Rake::Task.new(:wip) do |t|
476
+ t.cucumber_opts = ["--tags", "@wip", "--format", "progress", "features/"]
477
+ end
478
+
479
+ desc "Run passing Cucumber scenarios (exclude @wip)"
480
+ Cucumber::Rake::Task.new(:passing) do |t|
481
+ # Quote tag expression so shell keeps "not @wip" as one arg (Cucumber::Rake::Task uses cmd.join(' '))
482
+ t.cucumber_opts = ["--tags", '"not @wip"', "--format", "progress", "features/"]
483
+ end
484
+ end
485
+
486
+ desc "Run all Cucumber acceptance tests (alias for cucumber:all)"
487
+ task cucumber: "cucumber:all"
488
+ rescue LoadError
489
+ desc "Cucumber not available — install with: bundle install --with development"
490
+ task :cucumber do
491
+ warn "Cucumber gem is not available. Run: bundle install --with development"
492
+ end
493
+ end
data/config/README.md CHANGED
@@ -80,4 +80,4 @@ Edit `docker-compose.yml` and change port mappings.
80
80
 
81
81
  ⚠️ **This is for TESTING ONLY!** Do not use these configurations in production.
82
82
 
83
- For production setup, see `docs/guides/production-deployment.md` (Phase 5).
83
+ For production setup, see [QUICK-START](../docs/QUICK-START.md) and [CONFIGURATION](../docs/CONFIGURATION.md).
@@ -26,6 +26,18 @@ schema_config:
26
26
  prefix: index_
27
27
  period: 24h
28
28
 
29
+ # Fast flush for integration tests
30
+ ingester:
31
+ chunk_idle_period: 1s # Flush chunks after 1 second of inactivity
32
+ chunk_retain_period: 1s # Keep chunks in memory for 1 second before flush
33
+ max_chunk_age: 2s # Force flush after 2 seconds
34
+ chunk_target_size: 0 # Disable size-based flush delay
35
+ chunk_encoding: snappy
36
+ wal:
37
+ enabled: true
38
+ dir: /tmp/loki/wal
39
+ replay_memory_ceiling: 100MB
40
+
29
41
  ruler:
30
42
  alertmanager_url: http://localhost:9093
31
43
 
@@ -0,0 +1,44 @@
1
+ # OpenTelemetry Collector config for E11y integration tests
2
+ # Receives OTLP (logs, traces, metrics) and exports to debug + Loki
3
+ #
4
+ # Usage: docker compose up -d otel-collector
5
+ # App sends to: http://localhost:4318 (HTTP) or localhost:4317 (gRPC)
6
+
7
+ receivers:
8
+ otlp:
9
+ protocols:
10
+ grpc:
11
+ endpoint: 0.0.0.0:4317
12
+ http:
13
+ endpoint: 0.0.0.0:4318
14
+
15
+ processors:
16
+ batch:
17
+ timeout: 5s
18
+ send_batch_size: 1024
19
+
20
+ exporters:
21
+ debug:
22
+ verbosity: detailed
23
+ sampling_initial: 5
24
+ sampling_thereafter: 200
25
+
26
+ # Forward logs to Loki (same backend as direct Loki adapter)
27
+ # Requires contrib image: otel/opentelemetry-collector-contrib
28
+ loki:
29
+ endpoint: http://loki:3100/loki/api/v1/push
30
+
31
+ service:
32
+ pipelines:
33
+ logs:
34
+ receivers: [otlp]
35
+ processors: [batch]
36
+ exporters: [debug, loki]
37
+ traces:
38
+ receivers: [otlp]
39
+ processors: [batch]
40
+ exporters: [debug]
41
+ metrics:
42
+ receivers: [otlp]
43
+ processors: [batch]
44
+ exporters: [debug]
data/cucumber.yml ADDED
@@ -0,0 +1 @@
1
+ default: --publish-quiet
data/docker-compose.yml CHANGED
@@ -1,5 +1,3 @@
1
- version: '3.8'
2
-
3
1
  services:
4
2
  loki:
5
3
  image: grafana/loki:2.9.0
@@ -69,6 +67,24 @@ services:
69
67
  networks:
70
68
  - e11y_network
71
69
 
70
+ # OpenTelemetry Collector - receives OTLP, exports to debug + Loki
71
+ # HTTP: localhost:4318, gRPC: localhost:4317
72
+ # Usage: docker compose up -d loki otel-collector
73
+ otel-collector:
74
+ image: otel/opentelemetry-collector-contrib:0.104.0
75
+ container_name: e11y_otel_collector
76
+ ports:
77
+ - "4317:4317" # OTLP gRPC
78
+ - "4318:4318" # OTLP HTTP
79
+ volumes:
80
+ - ./config/otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml
81
+ command: ["--config=/etc/otelcol-contrib/config.yaml"]
82
+ depends_on:
83
+ loki:
84
+ condition: service_healthy
85
+ networks:
86
+ - e11y_network
87
+
72
88
  networks:
73
89
  e11y_network:
74
90
  driver: bridge
data/docs/ADAPTERS.md ADDED
@@ -0,0 +1,76 @@
1
+ # Adapters
2
+
3
+ > Back to [README](../README.md#documentation)
4
+
5
+ E11y supports multiple adapters for different backends.
6
+
7
+ | Adapter | Purpose | Batching | Use Case |
8
+ |---------|---------|----------|----------|
9
+ | **Loki** | Log aggregation (Grafana) | Yes | Production logs |
10
+ | **Sentry** | Error tracking | Via SDK | Error monitoring |
11
+ | **OpenTelemetry** | OTLP export (OTelLogs, OpenTelemetryCollector) | Varies | Distributed tracing, logs |
12
+ | **Yabeda** | Prometheus metrics | N/A | Metrics export |
13
+ | **File** | Local logs | Yes | Development, CI |
14
+ | **Stdout** | Console output | No | Development |
15
+ | **InMemory** | Test buffer | No | Testing |
16
+
17
+ ## Configuration
18
+
19
+ ```ruby
20
+ # config/initializers/e11y.rb
21
+ E11y.configure do |config|
22
+ # Configure adapters
23
+ config.adapters[:logs] = E11y::Adapters::Loki.new(
24
+ url: ENV["LOKI_URL"],
25
+ batch_size: 100,
26
+ batch_timeout: 5,
27
+ compress: true
28
+ )
29
+
30
+ config.adapters[:errors_tracker] = E11y::Adapters::Sentry.new(
31
+ dsn: ENV["SENTRY_DSN"]
32
+ )
33
+
34
+ config.adapters[:stdout] = E11y::Adapters::Stdout.new(
35
+ format: :pretty
36
+ )
37
+
38
+ # OpenTelemetry Collector (compress: true default, requires Faraday)
39
+ # config.adapters[:otel] = E11y::Adapters::OpenTelemetryCollector.new(
40
+ # endpoint: ENV["OTEL_EXPORTER_OTLP_ENDPOINT"],
41
+ # service_name: "my-app"
42
+ # )
43
+ end
44
+ ```
45
+
46
+ ## Adapter Routing by Severity
47
+
48
+ Events are routed to adapters based on severity. The default mapping:
49
+
50
+ - `error`, `fatal` → `[:logs, :errors_tracker]`
51
+ - Other severities → `[:logs]`
52
+
53
+ Override routing explicitly:
54
+
55
+ ```ruby
56
+ class CustomEvent < E11y::Event::Base
57
+ adapters :logs, :stdout # Explicit routing
58
+ end
59
+ ```
60
+
61
+ ## Custom Adapters
62
+
63
+ Implement the `write` method:
64
+
65
+ ```ruby
66
+ class MyBackendAdapter < E11y::Adapters::Base
67
+ def write(event_data)
68
+ # event_data contains event_name, payload, severity, timestamp, etc.
69
+ MyBackend.send_event(event_data)
70
+ end
71
+ end
72
+
73
+ E11y.configure do |config|
74
+ config.adapters[:my_backend] = MyBackendAdapter.new
75
+ end
76
+ ```
@@ -0,0 +1,59 @@
1
+ # Adaptive Sampling
2
+
3
+ > Back to [README](../README.md#documentation)
4
+
5
+ E11y supports adaptive sampling to reduce event volume during high load.
6
+
7
+ Sampling strategies:
8
+
9
+ 1. **Error-based** - Increase sampling during error spikes
10
+ 2. **Load-based** - Reduce sampling under high throughput
11
+ 3. **Value-based** - Always sample high-value events
12
+
13
+ > **Note:** Rate limiting (`E11y::Middleware::RateLimiting`) is **not included in the default
14
+ > pipeline**. To enable it, add it manually:
15
+ >
16
+ > ```ruby
17
+ > config.pipeline.use E11y::Middleware::RateLimiting
18
+ > ```
19
+ > Enabling `config.rate_limiting_enabled = true` alone has no effect without this step.
20
+
21
+ ## Configuration
22
+
23
+ ```ruby
24
+ E11y.configure do |config|
25
+ config.pipeline.use E11y::Middleware::Sampling,
26
+ default_sample_rate: 0.1,
27
+
28
+ # Error-based sampling
29
+ error_based_adaptive: true,
30
+ error_spike_config: {
31
+ window: 60,
32
+ absolute_threshold: 100,
33
+ relative_threshold: 3.0,
34
+ spike_duration: 300
35
+ },
36
+
37
+ # Load-based sampling
38
+ load_based_adaptive: true,
39
+ load_monitor_config: {
40
+ window: 60,
41
+ thresholds: {
42
+ normal: 1_000,
43
+ high: 10_000,
44
+ very_high: 50_000,
45
+ overload: 100_000
46
+ }
47
+ }
48
+ end
49
+ ```
50
+
51
+ ## Value-Based Sampling
52
+
53
+ Sample events based on payload values:
54
+
55
+ ```ruby
56
+ class PaymentEvent < E11y::Event::Base
57
+ sample_by_value :amount, greater_than: 1000 # Always sample large payments
58
+ end
59
+ ```
@@ -0,0 +1,104 @@
1
+ # E11y vs. Alternatives — Detailed Comparison
2
+
3
+ > For a quick overview, see the [comparison table in README](../README.md#what-makes-e11y-different).
4
+
5
+ ### Detailed Comparisons
6
+
7
+ #### vs. SaaS APM (Datadog, New Relic, Dynatrace)
8
+
9
+ **Datadog / New Relic:**
10
+ - ✅ **Pros:** Full-stack visibility, mature dashboards, auto-instrumentation
11
+ - ❌ **Cons:** $500-5k/month, vendor lock-in, no debug buffering, no schema validation
12
+ - **E11y advantage:** 10x cheaper, request-scoped buffering (unique), type-safe events, own your data
13
+
14
+ **When to use Datadog/New Relic instead:**
15
+ - You need frontend RUM (Real User Monitoring)
16
+ - You have polyglot microservices (not just Rails)
17
+ - Budget is unlimited, prefer turnkey solution
18
+
19
+ ---
20
+
21
+ #### vs. Open-Source Logging (Semantic Logger, Lograge)
22
+
23
+ **Semantic Logger:**
24
+ - ✅ **Pros:** Structured logs (JSON), async writes, Rails integration
25
+ - ❌ **Cons:** No debug buffering, no schema validation, no auto-metrics, logs-only
26
+ - **E11y advantage:** Request-scoped buffering (unique), schema validation, auto-metrics, unified events
27
+
28
+ **Lograge:**
29
+ - ✅ **Pros:** Reduces Rails log noise (single-line requests)
30
+ - ❌ **Cons:** Filtering only, no buffering, no validation, no metrics
31
+ - **E11y advantage:** Request-scoped buffering (selective, not filtering), schema validation, auto-metrics
32
+
33
+ **When to use Semantic Logger instead:**
34
+ - You only need structured JSON logs (no events/metrics)
35
+ - You don't need debug buffering or schema validation
36
+
37
+ ---
38
+
39
+ #### vs. OpenTelemetry
40
+
41
+ **OpenTelemetry:**
42
+ - ✅ **Pros:** Industry standard, polyglot, vendor-neutral, mature ecosystem
43
+ - ❌ **Cons:** Complex setup (1-2 weeks), no debug buffering, no schema validation, overkill for Rails monolith
44
+ - **E11y advantage:** Fast setup, Rails-first, request-scoped buffering, schema validation
45
+
46
+ **When to use OpenTelemetry instead:**
47
+ - You have microservices in multiple languages (Go, Java, Python, etc.)
48
+ - You need distributed tracing across services
49
+ - You have a platform team to manage complexity
50
+
51
+ **Use both:** E11y events can be sent to OpenTelemetry via `E11y::Adapters::OtelLogs`
52
+
53
+ ---
54
+
55
+ #### vs. Grafana + Loki + Prometheus
56
+
57
+ **Grafana Stack:**
58
+ - ✅ **Pros:** Open-source, powerful visualizations, mature, self-hosted
59
+ - ❌ **Cons:** Complex setup (2-3 days), requires DevOps, no Rails integration, no schema validation
60
+ - **E11y advantage:** Fast setup, Rails-native, schema validation, no DevOps required
61
+
62
+ **When to use Grafana Stack instead:**
63
+ - You already have Grafana/Loki infrastructure
64
+ - You have a dedicated DevOps team
65
+ - You need custom dashboards across multiple systems
66
+
67
+ **Use both:** E11y can send events to Loki via `E11y::Adapters::Loki`
68
+
69
+ ---
70
+
71
+ #### vs. Error Tracking (Sentry, Honeybadger, Rollbar)
72
+
73
+ **Sentry:**
74
+ - ✅ **Pros:** Excellent error tracking, stack traces, breadcrumbs, release tracking
75
+ - ❌ **Cons:** Errors-only, no debug buffering, no schema validation, $26-80/mo
76
+ - **E11y advantage:** Events + errors + metrics unified, request-scoped buffering, schema validation
77
+
78
+ **When to use Sentry instead:**
79
+ - You only need error tracking (not general observability)
80
+ - You need frontend JavaScript error tracking
81
+
82
+ **Use both:** E11y can send error events to Sentry via `E11y::Adapters::Sentry`
83
+
84
+ ---
85
+
86
+ #### vs. Rails-First APM (AppSignal, Skylight)
87
+
88
+ **AppSignal:**
89
+ - ✅ **Pros:** Rails-native, beautiful UI, performance monitoring, $23/mo entry
90
+ - ❌ **Cons:** SaaS lock-in, no debug buffering, no schema validation, limited to supported languages
91
+ - **E11y advantage:** Request-scoped buffering (unique), schema validation, own your data
92
+
93
+ **Skylight:**
94
+ - ✅ **Pros:** Rails performance profiling, SQL query analysis
95
+ - ❌ **Cons:** Performance-only (no logs/events), SaaS lock-in, $20+/mo
96
+ - **E11y advantage:** Unified events/logs/metrics, request-scoped buffering, own your data
97
+
98
+ **When to use AppSignal/Skylight instead:**
99
+ - You want zero-config turnkey solution
100
+ - You prefer paying for hosted service over self-hosting
101
+
102
+ **Use both:** E11y for events/logs/metrics, AppSignal for performance profiling
103
+
104
+ ---