Project

e11y

0.0
No release in over 3 years
E11y (Easy Telemetry) - Observability for Rails developers who hate noise. UNIQUE FEATURES: • Request-scoped debug buffering - buffers debug logs in memory, flushes ONLY on errors • Zero-config SLO tracking - automatic Service Level Objectives for HTTP endpoints and jobs • Schema-validated events - catch bugs before production with dry-schema DEVELOPER EXPERIENCE: • Minimal setup — one config block, works with stdout out of the box • Auto-metrics from events (no manual Yabeda.increment) • Rails-first design (follows Rails conventions) • Pluggable adapters (Loki, Sentry, OpenTelemetry, custom backends) COST SAVINGS: • Reduce log storage costs by 90% (request-scoped buffering) • Replace expensive APM SaaS ($500-5k/month → infra costs only) • Own your observability data (no vendor lock-in) PRODUCTION-READY: • Thread-safe for multi-threaded Rails + Sidekiq • Adaptive sampling (error-based, load-based, value-based) • PII filtering (GDPR-compliant masking/hashing) • Performance optimized (hash-based events, minimal allocations) Perfect for Rails 7.0+ teams who need observability without complexity or high costs.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

~> 2.7
~> 13.0
~> 3.12
~> 1.50
~> 0.22
~> 3.19
~> 0.9
>= 2.2.4

Runtime

 Project Readme

E11y - Easy Telemetry

Debug production issues in seconds. Zero setup overhead. Own your data.

Gem Version CI Code Coverage

Quick Start • How it works • Docs

v1.0.0 · Actively developed · Production feedback welcome → open an issue

Contents: Quick Look · Quick Start · Features · vs Alternatives · Docs


The Problem Every Rails Developer Knows

You enable debug logs in production to catch that one weird bug.
Result: 10,000 lines of noise for every 1 error.

# Production logs right now:
[DEBUG] Cache read: session:abc123         99% useless
[DEBUG] SQL: SELECT * FROM users WHERE...  99% useless  
[DEBUG] Rendered users/show.html.erb      99% useless
[INFO] User 123 logged in                 maybe useful
[DEBUG] Cache read: user:123               99% useless
[ERROR] Payment failed: Stripe timeout     THIS is what you need!

The dilemma:

  • Turn debug ON → drown in noise, pay $$$ for log storage
  • Turn debug OFF → fly blind when bugs happen

The E11y Solution

Request-scoped debug buffering - the only Rails observability gem that does this:

# E11y buffers debug logs in memory during request
# Flushes to storage ONLY if request fails

# Happy path (99% of requests):
[INFO] User 123 logged in 
# Debug logs discarded, zero noise

# Error path (1% of requests):  
[ERROR] Payment failed: Stripe timeout
[DEBUG] Cache read: session:abc123       NOW we see the context!
[DEBUG] SQL: SELECT * FROM users...       NOW we see what happened!
[DEBUG] Rendered users/show.html.erb     Complete error trail!

Result: Debug when you need it. Silence when you don't.


Quick Look (2 minutes)

# 1. Configure once
E11y.configure do |config|
  config.ephemeral_buffer_enabled = true
  config.adapters[:logs] = E11y::Adapters::Loki.new(url: ENV["LOKI_URL"])
end

# 2. Define a business event
class OrderPaidEvent < E11y::Event::Base
  schema do
    required(:order_id).filled(:string)
    required(:amount).filled(:float, gt?: 0)
    required(:currency).filled(:string, included_in?: %w[USD EUR GBP])
    optional(:user_email).maybe(:string)
  end

  validation_mode :sampled, sample_rate: 0.01   # 1% validation for hot path
  contains_pii true
  pii_filtering { hashes :user_email }
  sample_by_value :amount, greater_than: 1000  # Always sample large orders

  metrics do
    counter :orders_total, tags: [:currency]
    histogram :order_amount, value: :amount, tags: [:currency]
  end
end

# 3. Track it
OrderPaidEvent.track(order_id: "123", amount: 99.99, currency: "USD")

Public API: define events as subclasses of E11y::Event::Base and call .track(...) on the event class (for example OrderPaidEvent.track(...)). That is the only supported tracking entry point in application code.

→ Full Quick Start guide (5 min)


What Makes E11y Different?

1. Request-Scoped Debug Buffering (Unique to E11y)

No other Rails gem does this.

# Traditional approach:
Rails.logger.debug "query: SELECT..." # → Always written to disk
Rails.logger.debug "cache miss"       # → Always written to disk
Rails.logger.debug "rendering view"   # → Always written to disk
# Cost: $$$, Noise: 99%, Value: 1%

# E11y approach:
E11y.configure do |config|
  config.ephemeral_buffer_enabled = true
end

# Debug events buffered in memory during request
# Flushed to storage ONLY on 5xx server errors
# Cost: -90%, Noise: -99%, Value: 100%

Note: By default the buffer flushes only on 5xx server errors (flush_on_error = true). On 4xx responses the buffer is discarded. Two independent knobs control this:

# flush_on_error (default: true) — controls 5xx behaviour
config.ephemeral_buffer_flush_on_error = false  # disable 5xx flush

# flush_on_statuses (default: []) — extra statuses, independent of flush_on_error
config.ephemeral_buffer_flush_on_statuses = [403]       # also flush on 403 Forbidden
config.ephemeral_buffer_flush_on_statuses = [401, 403]  # multiple codes

Real-world impact:

  • Storage costs: Up to -90% log volume → proportional Loki storage savings
  • Log search time: 30 seconds → 3 seconds (90% less data)
  • Developer sanity: Infinite ✨

2. retention_until — Simple Archival

Events carry retention_until (ISO8601) in their payload. Archival happens later — a separate job (cron, Loki compaction) filters logs by this field. No custom logic: WHERE retention_until > ?. Cost savings (export to cheap cold storage) and simplicity (one field to filter).


3. Schema-Validated Business Events

Stop debugging nil values in production:

class OrderPaidEvent < E11y::Event::Base
  schema do
    required(:order_id).filled(:string)
    required(:amount).filled(:float, gt?: 0)
    required(:currency).filled(:string, included_in?: %w[USD EUR GBP])
  end
  
  metrics do
    counter :orders_total, tags: [:currency]
    histogram :order_amount, value: :amount, tags: [:currency]
  end
end

# Invalid data caught BEFORE production
OrderPaidEvent.track(order_id: "123", amount: -10, currency: "INVALID")
# => E11y::ValidationError: amount must be > 0

# Valid data: event + metrics in one call
OrderPaidEvent.track(order_id: "123", amount: 99.99, currency: "USD")
# ✅ Event sent to Loki/Sentry
# ✅ Prometheus metrics updated
# ✅ No manual Yabeda.increment

Developer experience:

  • Schema validation prevents bugs
  • Auto-metrics eliminate boilerplate
  • Type safety without TypeScript

4. Zero-Config SLO Tracking

Automatic Service Level Objectives for your endpoints and jobs:

# Enable Rails instrumentation
E11y.configure do |config|
  config.rails_instrumentation_enabled = true
end

# That's it! E11y now emits SLO metrics automatically:
# - HTTP endpoints: success rate, latency percentiles (p50, p95, p99)
# - Background jobs: success rate, execution time, retry rate
# - Database queries: slow query detection
# - Cache operations: hit/miss ratios
#
# SLO metrics are collected via Prometheus/Yabeda; calculate SLOs from those metrics.

vs. Traditional SLO Tracking:

  • ❌ Manual instrumentation of every endpoint
  • ❌ Complex SLO definitions and calculations
  • ❌ Separate tools for different SLOs
  • ✅ E11y: Zero config, automatic tracking, metrics for SLO calculation

5. Rails-First Design

Built for Rails developers, not platform engineers:

# Fast setup, not 2-week OpenTelemetry migration
gem "e11y"

E11y.configure do |config|
  config.adapters[:logs] = E11y::Adapters::Loki.new(url: ENV["LOKI_URL"])
  config.adapters[:errors_tracker] = E11y::Adapters::Sentry.new(dsn: ENV["SENTRY_DSN"])

  # ActiveSupport::Notifications → E11y (HTTP, DB, cache, job lifecycle, etc.)
  config.rails_instrumentation_enabled = true

  # Optional: Sidekiq client/server middleware (buffer + job events) — enable if you use Sidekiq
  config.sidekiq_enabled = true

  # Optional: ActiveJob callbacks (buffer + context) — enable if you use Active Job
  config.active_job_enabled = true

  # Optional: wrap Rails.logger and emit E11y::Events::Rails::Log::* — opt-in
  # config.logger_bridge_enabled = true
end

vs. Traditional Observability:

  • ❌ OpenTelemetry: 5 docs pages, complex setup
  • ❌ Datadog: $10k+/year, vendor lock-in
  • ❌ ELK Stack: DevOps team needed
  • ✅ E11y: One gem, Rails conventions, owned data

6. Built-in PII Filtering

Built-in PII filtering — mask, hash, or redact sensitive fields per event class. No other Ruby observability gem provides this out of the box.

class Events::UserSignedIn < E11y::Event::Base
  contains_pii :email, strategy: :hash
  contains_pii :ip_address, strategy: :mask
end

Who Should Use E11y?

✅ Perfect For

Rails developers who:

  • Hate searching through 100k debug logs for 1 error
  • Pay too much for Datadog/New Relic ($500-5k/month)
  • Need observability but don't have a DevOps team
  • Want type-safe events without migrating to TypeScript

Teams that:

  • Run Rails 7.0+ in production (Sidekiq, PostgreSQL, Redis)
  • Use Loki/Grafana or Sentry for monitoring
  • Care about developer experience and code quality
  • Prefer open-source over SaaS vendor lock-in

⚠️ Not For (Yet)

  • Non-Rails Ruby - Focused on Rails conventions first
  • Microservices polyglot - OpenTelemetry better for multi-language
  • Enterprise compliance requirements — audit trails and compliance reports are not yet available
  • Auto-instrumentation only - E11y requires event definitions (by design)

Core Features

Feature Status Description
Request-Scoped Buffering ✅ Implemented Buffer debug logs, flush only on errors (-90% noise)
Zero-Config SLO Tracking ✅ Implemented Automatic Service Level Objectives for endpoints/jobs
Schema Validation ✅ Implemented dry-schema validation before sending events
Metrics DSL ✅ Implemented Define Prometheus metrics alongside events
Adapters ✅ 7 adapters Loki, Sentry, OpenTelemetry, Yabeda, File, Stdout, InMemory
PII Filtering ✅ Implemented Configurable field masking/hashing/redaction (event-level DSL)
Adaptive Sampling ✅ Implemented Error-based, load-based, value-based strategies
Rate Limiting ✅ Implemented Opt-in — requires config.pipeline.use E11y::Middleware::RateLimiting
Rails Integration ✅ Implemented Auto-instrument HTTP, ActiveRecord, ActiveJob, Cache
Production Testing 🚧 In Progress Validating with real workloads

Quick Start

1. Install

# Gemfile
gem "e11y"

bundle install

2. Configure (One-Time Setup)

# config/initializers/e11y.rb
E11y.configure do |config|
  # Enable request-scoped debug buffering (THE killer feature)
  config.ephemeral_buffer_enabled        = true
  # config.ephemeral_buffer_flush_on_error    = true   # default: flush on 5xx
  # config.ephemeral_buffer_flush_on_statuses = [403]  # also flush on 403
  
  # Configure where events go
  config.adapters[:logs] = E11y::Adapters::Loki.new(
    url: ENV["LOKI_URL"],
    batch_size: 100
  )
  
  config.adapters[:errors_tracker] = E11y::Adapters::Sentry.new(
    dsn: ENV["SENTRY_DSN"]
  )
  
  # ActiveSupport::Notifications → E11y (see docs/RAILS_INTEGRATION.md)
  config.rails_instrumentation_enabled = true

  # Optional: Sidekiq / ActiveJob (ephemeral buffer + instrumentation at job boundaries)
  config.sidekiq_enabled = true    # set if you use Sidekiq
  config.active_job_enabled = true # set if you use Active Job (in addition or instead)

  # Optional: send Rails.logger lines into E11y as structured events
  # config.logger_bridge_enabled = true
  # config.logger_bridge_track_severities = [:warn, :error, :fatal] # nil = all severities
  # config.logger_bridge_ignore_patterns = [%r{\A\[ActiveJob\]}]
end

3. Define Business Events

# app/events/order_paid_event.rb
class OrderPaidEvent < E11y::Event::Base
  # Schema validation (catch bugs before production)
  schema do
    required(:order_id).filled(:string)
    required(:amount).filled(:float, gt?: 0)
    required(:currency).filled(:string, included_in?: %w[USD EUR GBP])
  end
  
  # Auto-metrics (no manual Yabeda.increment!)
  metrics do
    counter :orders_total, tags: [:currency]
    histogram :order_amount, value: :amount, tags: [:currency]
  end
end

4. Track Events

# In controllers/services
class OrdersController < ApplicationController
  def create
    order = Order.create!(order_params)
    
    # One method call = validation + event + metrics
    OrderPaidEvent.track(
      order_id: order.id,
      amount: order.total,
      currency: "USD"
    )
    
    # If amount is negative → E11y::ValidationError (caught before production!)
    # If valid → event sent to Loki + orders_total metric incremented
  end
end

That's it! Now you have:

  • ✅ Debug logs buffered in memory (flushed only on errors)
  • ✅ Schema-validated business events
  • ✅ Auto-generated Prometheus metrics
  • ✅ Zero-config SLO tracking (success rates, latency percentiles)
  • ✅ Events sent to Loki, Sentry, or custom adapters

No more:

  • ❌ Searching through 100k debug logs
  • ❌ nil values in production
  • ❌ Manual Yabeda.increment everywhere
  • ❌ Manual SLO definitions and calculations
  • ❌ High log storage bills from storing everything

Before and After E11y

Before: Traditional Rails Logging

# Development: Debug enabled
Rails.logger.debug "Cache read: session:abc"
Rails.logger.debug "SQL: SELECT * FROM users..."
Rails.logger.debug "Rendered users/show"
# Result: Helpful for debugging ✅

# Production: Debug disabled (too noisy)
Rails.logger.info "User logged in"
# Bug happens...
Rails.logger.error "Payment failed!"
# Result: No context, blind debugging ❌

# Production: Debug enabled (to catch bug)
# 99 successful requests:
#   [DEBUG] Cache read... (297 lines)
#   [INFO] User logged in (99 lines)
# 1 failed request:
#   [DEBUG] Cache read... (3 lines)
#   [ERROR] Payment failed (1 line)
# Total: 400 lines, 74% noise ❌
# Cost: High (all logs stored) ❌
# Search time: 30 seconds ❌

After: E11y Request-Scoped Buffering

# Production with E11y:
E11y.configure { |c| c.ephemeral_buffer_enabled = true }

# 99 successful requests:
#   [INFO] User logged in (99 lines)
# Debug logs buffered in memory, discarded ✅

# 1 failed request:
#   [ERROR] Payment failed (1 line)
#   [DEBUG] Cache read... (3 lines) ← Flushed!
#   [DEBUG] SQL: SELECT... (context!) ← Flushed!
#   [DEBUG] Rendered view... (trail!) ← Flushed!
# Total: 103 lines, 0% noise ✅
# Cost: Low (-90% log volume) ✅
# Search time: 3 seconds ✅ (-90%)

Impact:

  • Developer productivity: 10x faster debugging (context when you need it)
  • Infrastructure cost: -90% log storage (only relevant logs stored)
  • Signal-to-noise: 100% vs 1% (every log line matters)

E11y vs Alternatives

Comparison Matrix

Solution Setup Time Monthly Cost Request-Scoped Buffering SLO Tracking Schema Validation Auto-Metrics Built-in PII Filtering Data Ownership Ecosystem / Managed Infra
E11y 5–30 min* Infra costs ✅ Unique ✅ Zero-config ✅ Field masking, hashing, redaction ✅ Full ⚠️ Ruby/Rails only
Datadog APM 2-4 hours $500-5,000 ✅ Manual ⚠️ Via agent config (limited) ❌ SaaS lock-in ✅ Extensive + fully managed
New Relic 2-4 hours $99-658/user ✅ Manual ⚠️ Via obfuscation rules ❌ SaaS lock-in ✅ Extensive + fully managed
Sentry 1 hour $26-80/mo Partial ⚠️ Data scrubbing rules ❌ SaaS lock-in ✅ Managed (error-focused)
Semantic Logger 30 minutes Infra costs ❌ None ✅ Full ⚠️ Ruby only, self-hosted
OpenTelemetry 1-2 weeks Infra costs Manual setup ❌ Manual implementation required ✅ Full ✅ Polyglot, vendor-neutral
Grafana + Loki 2-3 days Infra costs Manual setup Manual ❌ None ✅ Full ✅ Mature, DevOps-friendly
AppSignal 1 hour $23-499/mo ✅ Built-in ⚠️ Parameter filtering only ❌ SaaS lock-in ✅ Managed (Rails-friendly)

Legend:

  • Setup Time: From zero to first meaningful data
  • Monthly Cost: For 10-person team, medium Rails app (estimated)
  • Request-Scoped Buffering: Buffer debug logs, flush only on errors
  • SLO Tracking: Automatic Service Level Objectives monitoring
  • Schema Validation: Type-safe event schemas
  • Auto-Metrics: Metrics generated from events automatically
  • Built-in PII Filtering: Automatic masking/hashing of sensitive fields (emails, IPs, credit cards, etc.) — no other Ruby observability gem provides this out of the box
  • Data Ownership: Can you host it yourself?
  • Ecosystem / Managed Infra: Integration breadth and whether infrastructure is managed for you

** 5 min for gem + stdout; 30 min if adding self-hosted Loki/Grafana stack.

Cost estimates assume migration from verbose SaaS logging (Datadog/CloudWatch) to self-hosted Loki. Actual savings depend on your current setup.


Detailed Comparisons

See docs/COMPARISON.md for detailed per-tool comparisons.


Decision Matrix

Choose E11y if:

  • ✅ You're tired of noisy debug logs
  • ✅ You want type-safe events (catch bugs before production)
  • ✅ You prefer Rails conventions over platform engineering
  • ✅ You want to own your observability data
  • ✅ You want to reduce log storage costs by 90%

Choose SaaS APM (Datadog, New Relic) if:

  • ✅ You need frontend RUM + backend APM
  • ✅ You have polyglot microservices
  • ✅ Budget unlimited, prefer turnkey solution
  • ✅ You don't want to manage infrastructure

Choose OpenTelemetry if:

  • ✅ You have microservices in multiple languages
  • ✅ You have a platform team to manage complexity
  • ✅ You need vendor-neutral distributed tracing

Choose Grafana Stack if:

  • ✅ You already have Grafana infrastructure
  • ✅ You have a DevOps team
  • ✅ You need custom dashboards across systems

Choose Semantic Logger/Lograge if:

  • ✅ You only need structured JSON logs (nothing else)
  • ✅ You don't need debug buffering or schema validation

The E11y Sweet Spot

E11y is optimized for:

Team size: 5-100 engineers
Stack: Rails 7.0+ monolith or modular monolith
Infra: PostgreSQL, Redis, Sidekiq, standard Rails stack
Budget: Prefer infrastructure costs over $500-5k/month SaaS
Philosophy: Developer experience > platform complexity

Not optimized for:

  • Polyglot microservices (use OpenTelemetry)
  • Frontend-heavy SPAs (use Datadog/Sentry for RUM)
  • Enterprise compliance requirements (not yet available)

Performance

p99 latency <70µs (:always), <10µs (:sampled), <50µs (:never). Full benchmarks → docs/PERFORMANCE.md


Upgrading

Breaking renames and migration steps are listed in CHANGELOG.md. Common ones:

  • RequestScopedBufferE11y::Buffers::EphemeralBuffer — use initialize!, flush_on_error, and discard at request/job boundaries (middleware and instruments already do this).
  • Sidekiq / Active Job — enable with config.sidekiq_enabled and config.active_job_enabled; see docs/RAILS_INTEGRATION.md.
  • Logger bridgeconfig.logger_bridge_enabled plus optional logger_bridge_track_severities and logger_bridge_ignore_patterns (same file).

Known tradeoffs and unfinished pieces: docs/LIMITATIONS.md.


Documentation

Topic Doc
Schema Validation dry-schema validation, modes, error handling
Metrics DSL Counters, histograms, gauges, Yabeda integration
Adapters Loki, Sentry, OTel, Yabeda, File, Stdout, InMemory
PII Filtering Mask, hash, redact sensitive fields
Adaptive Sampling Error-based, load-based, value-based
Presets HighValueEvent, AuditEvent, DebugEvent
Distributed Tracing W3C Trace Context, manual propagation
Rails Integration Auto-instrumentation, Sidekiq
Testing InMemoryTest adapter, RSpec setup
Configuration Basic config, middleware pipeline
SLO PromQL & Alerts PromQL queries, Prometheus alert rules
Performance Benchmarks, validation modes, cardinality
Limitations Rails only, Ruby 3.2+, tradeoffs
Comparison vs Datadog, OTel, Sentry, AppSignal, etc.

Also: ADRs, Use Cases, QUICK-START


Development

Running Tests

E11y has three test suites with different requirements:

Quick Commands (recommended)

# Using rake tasks
rake spec:unit            # Unit tests (~1672 examples, includes all e11y tests)
rake spec:integration     # Integration tests (~36 examples, requires Rails)
rake spec:railtie        # Railtie tests (~21 examples, Rails initialization)
rake spec:all            # All tests (~1729 examples, unit + integration + railtie)
rake spec:benchmark      # Benchmark tests (~44 examples, slow)
rake spec:coverage       # With coverage

Manual Commands

# Unit tests (fast, no Rails required)
bundle exec rspec --exclude-pattern 'spec/{integration,e11y/railtie_integration_spec.rb}/**/*_spec.rb'

# Integration tests (requires: bundle install --with integration)
INTEGRATION=true bundle exec rspec spec/integration/

# Railtie integration tests
bundle exec rspec spec/e11y/railtie_integration_spec.rb --tag railtie_integration

# All tests
bundle exec rspec

# Benchmarks (optional)
bundle exec rspec --tag benchmark

Test Suite Overview

  • Unit tests (~1672 examples, ~30s): Core logic, all e11y/* tests
  • Integration tests (~36 examples, ~5s): Rails, ActiveJob, Sidekiq integration
  • Railtie tests (~21 examples, ~2s): Rails initialization and configuration
  • Benchmark tests (~44 examples, ~30s): Performance tests (run with rake spec:benchmark)

Other Development Commands

# Linting
bundle exec rubocop

# Auto-fix linting issues
bundle exec rubocop -a

# Interactive console
rake e11y:console

# Generate documentation
rake e11y:docs

# Security audit
rake e11y:audit

# Run benchmarks
rake e11y:benchmark

Contributing

Bug reports and pull requests are welcome at https://github.com/arturseletskiy/e11y.

Contributing workflow:

  1. Fork the repository
  2. Create a feature branch
  3. Run tests: rake spec:all
  4. Run linter: bundle exec rubocop
  5. Submit a pull request

Note: Performance benchmarks are excluded from default test runs due to CI environment variability. Run them explicitly with --tag benchmark when needed.


License

MIT License. See LICENSE.txt for details.