
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Ruby applications often become opaque black boxes once they leave localhost, making debugging latency spikes or silent failures nearly impossible without proper tooling. Implementing observability for Ruby with OpenTelemetry solves this by standardizing how your application emits traces, metrics, and logs across any backend. This guide walks you through a production-grade setup that captures meaningful signal without degrading request performance or bloating your dependency tree.
opentelemetry-sdk and framework-specific instrumentation gems to your application, configuring an exporter (OTLP or console), and initializing the SDK at boot. This setup automatically captures requests, database queries, and background jobs while allowing manual spans for custom business logic.How do you configure observability for Ruby with OpenTelemetry in Rails?
The fastest path to observability for Ruby with OpenTelemetry in a Rails application relies on the official auto-instrumentation gems. These libraries hook into Rack, ActiveRecord, ActionMailer, and Sidekiq without requiring you to modify controller code. Before starting, ensure you understand the broader context by reading OpenTelemetry: the observability standard, which explains why vendor-neutral instrumentation matters for long-term maintainability.
Install the required gems
Add the core SDK and the specific instrumentations your stack requires to your Gemfile. Avoid installing every available instrumentation gem; each adds memory overhead and potential conflict surfaces.
# Gemfile
gem 'opentelemetry-sdk', '~> 1.5'
gem 'opentelemetry-exporter-otlp', '~> 0.29'
gem 'opentelemetry-instrumentation-rails', '~> 0.35'
gem 'opentelemetry-instrumentation-active_record', '~> 0.8'
gem 'opentelemetry-instrumentation-sidekiq', '~> 0.25' if defined?(Sidekiq) Initialize the SDK safely
Create a dedicated initializer rather than embedding configuration in application.rb. This keeps telemetry concerns isolated and makes it trivial to disable in test environments. The following configuration uses environment variables for all sensitive values, preventing hardcoded endpoints from leaking into version control.
# config/initializers/opentelemetry.rb
return if Rails.env.test?
require 'opentelemetry/sdk'
require 'opentelemetry/exporter/otlp'
require 'opentelemetry/instrumentation/all'
OpenTelemetry::SDK.configure do |c|
c.service_name = ENV.fetch('OTEL_SERVICE_NAME', 'my-rails-app')
c.service_version = ENV.fetch('APP_VERSION', 'unknown')
# Use batch processor in production to reduce network calls
c.use_all(
enabled_instrumentations: %w[
OpenTelemetry::Instrumentation::Rack
OpenTelemetry::Instrumentation::Rails
OpenTelemetry::Instrumentation::ActiveRecord
OpenTelemetry::Instrumentation::Sidekiq
]
)
# Explicit OTLP exporter with timeout safeguards
c.add_span_processor(
OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
OpenTelemetry::Exporter::OTLP::Exporter.new(
endpoint: ENV.fetch('OTEL_EXPORTER_OTLP_ENDPOINT', 'http://localhost:4318'),
headers: { 'Authorization' => ENV['OTEL_AUTH_HEADER'] }.compact
),
max_queue_size: 2048,
schedule_delay: 5,
export_timeout: 10
)
)
end A common mistake in 2026 is still using the synchronous SimpleSpanProcessor in production. This blocks the request thread on every span export and will destroy your p99 latency under load. Always use BatchSpanProcessor outside of local development.
When should you add manual instrumentation to Ruby traces?
Auto-instrumentation covers framework boundaries, but it cannot understand your business logic. You need manual spans when debugging requires visibility inside service objects, external API wrappers, or complex query builders. Refer to instrument an app with OpenTelemetry for language-agnostic principles before applying these Ruby-specific patterns.
Wrapping service objects and business logic
Use the tracer from the global provider to create child spans. Always set attributes that aid filtering later; a span named "process_payment" is useless without knowing the payment gateway or amount tier.
class PaymentService
TRACER = OpenTelemetry.tracer_provider.tracer('payment-service', '1.0.0')
def call(order)
TRACER.in_span('payment.process', attributes: {
'payment.gateway' => order.gateway,
'payment.amount_tier' => order.amount > 1000 ? 'high' : 'standard',
'order.id' => order.id
}) do |span|
result = gateway.charge(order)
span.set_attribute('payment.success', result.success?)
unless result.success?
span.status = OpenTelemetry::Trace::Status.error(result.error_message)
span.record_exception(result.exception)
end
result
end
end
end Propagating context across background jobs
If you use Sidekiq or Resque, verify that trace context propagates correctly. The Sidekiq instrumentation handles this automatically, but custom job wrappers or middleware can break the chain. Test this explicitly by triggering a job from a traced HTTP request and confirming the trace ID matches in your backend. Missing propagation is the single most frequent cause of "orphaned" spans in Ruby microservices.
Which OpenTelemetry exporter should Ruby applications use in production?
Choosing the right exporter determines whether your telemetry survives traffic spikes and network hiccups. While the console exporter is invaluable during development, production systems require a resilient transport layer.
| Exporter | Protocol | Best For | Production Safe? | Key Trade-off |
|---|---|---|---|---|
| OTLP/gRPC | Binary protobuf | High-throughput services, collector-direct | Yes | Requires gRPC-compatible LB; harder to debug raw payloads |
| OTLP/HTTP | JSON/protobuf over HTTPS | Serverless, edge, corporate proxies | Yes | Slightly higher payload size vs gRPC; universally supported |
| Console | STDOUT | Local dev, CI smoke tests | No | Blocks I/O; pollutes logs; never enable in prod |
| Jaeger Thrift | Thrift over UDP/HTTP | Legacy Jaeger-only shops | Deprecated | Migrate to OTLP; no new features post-2024 |
In practice, OTLP/HTTP is the safest default for Ruby applications in 2026. It traverses corporate firewalls and reverse proxies that strip gRPC headers, and the Ruby OTLP HTTP exporter has matured significantly. Only choose gRPC if you have verified your entire network path supports HTTP/2 and you are sending more than 10,000 spans per second.
How do you avoid performance pitfalls with Ruby OpenTelemetry?
Instrumentation is not free. I have seen Ruby applications lose 15% throughput after enabling OpenTelemetry due to misconfiguration. Follow these guardrails to keep overhead below 2%.
- Always sample in high-traffic services. Head-based sampling at 10–20% is sufficient for most web workloads. Use
OpenTelemetry::SDK::Trace::Samplers::TraceIdRatioBased.new(0.1)in your SDK configuration. Parent-based sampling ensures you either capture the full trace or none of it, avoiding broken partial traces. - Set explicit queue limits. The default batch processor queue can grow unbounded during backend outages. Cap
max_queue_sizeat 2048–4096 and accept dropped spans over OOM kills. Dropped spans are recoverable via metrics; crashed pods are not. - Redact sensitive attributes before export. Never rely on backend-side filtering for PII. Use attribute processors in the SDK or Collector to mask emails, tokens, and payment data at the source. This is non-negotiable for SOC 2 or ISO 27001 compliance.
- Disable unused instrumentations. Each loaded instrumentation hooks into Ruby's method dispatch. If you do not use Redis, Elasticsearch, or Faraday, do not load their gems. Audit your
enabled_instrumentationslist quarterly. - Monitor the SDK itself. Export
otel.sdk.exported.spansandotel.sdk.dropped.spansas Prometheus metrics. A rising drop rate signals queue saturation or backend issues before users complain. See Prometheus metrics monitoring fundamentals for setting up these alerts.
Start shipping reliable Ruby telemetry today
Implementing observability for Ruby with OpenTelemetry is a one-time investment that pays dividends across every future incident, capacity plan, and compliance audit. Start with auto-instrumentation and OTLP/HTTP, add manual spans only where business logic demands it, and enforce sampling and redaction from day one. Your future self debugging a 3 AM latency spike will thank you. If your team needs help designing a compliant, performant telemetry pipeline for Ruby or any other stack, reach out to discuss your observability architecture.