OpenTelemetry in Rails 8: Productie-Observability Opzetten in 30 Minuten
Voeg de opentelemetry-sdk gem toe aan je Rails 8 app, configureer auto-instrumentatie, en binnen 30 minuten stromen je distributed traces naar je backend. Zo werkt het.
Waarom OpenTelemetry in Plaats van Vendor Lock-in
Traditionele APM-tools (New Relic, Datadog, Scout) vereisen vendor-specifieke agents. Wil je wisselen van provider — of telemetry naar meerdere backends sturen — dan moet je alle instrumentatiecode herschrijven. OpenTelemetry (OTel) is de CNCF-standaard die instrumentatie loskoppelt van export. Je instrumenteert één keer en routeert signalen waarheen je wilt.
Ik heb vorige maand een productie Rails 8.0.1 app gemigreerd van Scout APM naar OpenTelemetry. De migratie duurde een middag, en de flexibiliteit was direct merkbaar: traces gaan naar Jaeger voor development en Grafana Tempo in productie, met dezelfde instrumentatiecode.
Stap 1: Gems Toevoegen
In je Gemfile:
# OpenTelemetry core
gem "opentelemetry-sdk", "~> 1.4"
gem "opentelemetry-exporter-otlp", "~> 0.29"
# Auto-instrumentatie (pikt Rails, ActiveRecord, etc. op)
gem "opentelemetry-instrumentation-all", "~> 0.68"
Draai bundle install. De opentelemetry-instrumentation-all meta-gem haalt instrumentors binnen voor Rails, ActiveRecord, Action Pack, Action View, Net::HTTP, Faraday, Redis, Sidekiq en zo’n 30 andere libraries. Wil je meer controle, kies dan individuele gems zoals opentelemetry-instrumentation-rails en opentelemetry-instrumentation-active_record.
Stap 2: SDK Configureren
Maak config/initializers/opentelemetry.rb:
require "opentelemetry/sdk"
require "opentelemetry/exporter/otlp"
require "opentelemetry/instrumentation/all"
OpenTelemetry::SDK.configure do |c|
c.service_name = "my-rails-app"
c.service_version = ENV.fetch("GIT_SHA", "unknown")
c.use_all(
"OpenTelemetry::Instrumentation::Rack" => {
untraced_endpoints: ["/up", "/healthz"]
},
"OpenTelemetry::Instrumentation::ActiveRecord" => {
db_statement: :obfuscate
}
)
end
Een paar punten:
db_statement: :obfuscate vervangt query-parameterwaarden door ? in trace spans. Zonder dit bevatten je traces rauwe SQL met gebruikersdata — een beveiligings- en complianceprobleem. Sla dit niet over.
untraced_endpoints filtert health check-ruis. Kubernetes probes die elke 10 seconden /up raken genereren duizenden nutteloze spans per dag.
service_version gekoppeld aan GIT_SHA laat je performance-regressies correleren aan specifieke deploys. Als je deployt met Kamal 2, geef de SHA mee als omgevingsvariabele in je deploy-configuratie.
Stap 3: Omgevingsvariabelen Instellen
De OTLP exporter leest configuratie standaard uit omgevingsvariabelen:
# Waar telemetry naartoe gaat
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
# Resource attributes (verschijnt in je backend UI)
OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,host.name=web-1
Voor een lokale collector tijdens development:
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
OTEL_TRACES_EXPORTER=otlp
OTEL_LOG_LEVEL=debug
Het debug log level print elke geëxporteerde span naar stdout — handig om te verifiëren dat instrumentatie werkt, maar je wilt het snel weer uitzetten.
Stap 4: Custom Spans Toevoegen Waar Het Ertoe Doet
Auto-instrumentatie dekt HTTP-requests, database-queries, cache-operaties en template rendering. Maar je interessantste performancedata zit in applicatie-specifieke code: betalingsverwerking, PDF-generatie, externe API-calls naar services zonder HTTP-instrumentatie.
class InvoiceGenerator
def generate(order)
tracer = OpenTelemetry.tracer_provider.tracer("invoice-generator")
tracer.in_span("generate_invoice", attributes: {
"order.id" => order.id,
"order.line_items" => order.line_items.count
}) do |span|
pdf = render_pdf(order)
span.set_attribute("invoice.pages", pdf.page_count)
span.set_attribute("invoice.size_bytes", pdf.bytesize)
upload_to_s3(pdf)
end
end
end
Houd custom span-namen kort en consistent. Gebruik dot-notatie voor attributen (order.id, niet orderId). De OpenTelemetry semantic conventions definiëren standaard attribuutnamen voor veelvoorkomende operaties.
Stap 5: Lokale Collector voor Development
Richt je development Rails server niet direct op een productie Grafana-instantie. Draai een lokale OpenTelemetry Collector die exporteert naar Jaeger:
# docker-compose.otel.yml
services:
otel-collector:
image: otel/opentelemetry-collector-contrib:0.96.0
ports:
- "4318:4318" # OTLP HTTP receiver
- "4317:4317" # OTLP gRPC receiver
volumes:
- ./otel-collector-config.yml:/etc/otelcol-contrib/config.yaml
jaeger:
image: jaegertracing/all-in-one:1.54
ports:
- "16686:16686" # Jaeger UI
- "4317" # Ontvangt van collector
Collector configuratie:
# otel-collector-config.yml
receivers:
otlp:
protocols:
http:
endpoint: 0.0.0.0:4318
grpc:
endpoint: 0.0.0.0:4317
processors:
batch:
timeout: 5s
send_batch_size: 1024
exporters:
otlp/jaeger:
endpoint: jaeger:4317
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlp/jaeger]
Draai docker compose -f docker-compose.otel.yml up, start je Rails server, doe wat requests, en open Jaeger op http://localhost:16686. Je ziet traces met spans voor elke controller action, database query en view render.
Stap 6: Productie Collector Architectuur
In productie zit de collector tussen je app en je observability backend. Dit geeft je:
- Buffering — de collector batcht en herprobeert, zodat je app nooit blokkeert op telemetry-export
- Sampling — verwijder low-value traces voordat ze je betaalde backend bereiken
- Routing — stuur traces naar Tempo, metrics naar Prometheus, logs naar Loki, allemaal vanuit één pipeline
Voor een Rails app die 500 requests/seconde verwerkt, gebruik ik tail-based sampling in de collector om 100% van error traces en trage requests (>2s) te behouden, terwijl ik 10% van succesvolle snelle requests sample. Dit bespaart ruwweg 80% op opslagkosten zonder de data te verliezen die er echt toe doet.
processors:
tail_sampling:
decision_wait: 10s
policies:
- name: errors
type: status_code
status_code: {status_codes: [ERROR]}
- name: slow-requests
type: latency
latency: {threshold_ms: 2000}
- name: baseline
type: probabilistic
probabilistic: {sampling_percentage: 10}
Aansluiten op Je Bestaande Stack
Als je al GitHub Actions CI/CD hebt opgezet voor je Rails app, kun je een stap toevoegen om OpenTelemetry-initialisatie te verifiëren in je testsuite:
# test/test_helper.rb
require "opentelemetry/sdk"
OpenTelemetry::SDK.configure do |c|
c.service_name = "my-rails-app-test"
c.add_span_processor(
OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(
OpenTelemetry::SDK::Trace::Export::InMemorySpanExporter.new
)
)
end
Voor background job monitoring maken de Sidekiq en Solid Queue auto-instrumentors automatisch parent spans aan voor elke job-uitvoering, gelinkt aan het web request dat de job in de wachtrij plaatste.
Performance Impact
Op de productie-app die ik eerder noemde (Rails 8.0.1, Ruby 3.3.6, ~500 req/s): OpenTelemetry auto-instrumentatie voegt ruwweg 1-2ms overhead toe per request. Het geheugengebruik steeg met ongeveer 15MB per Puma worker. De OTLP exporter batcht spans en verstuurt ze asynchroon, dus exportlatentie beïnvloedt de responstijden niet.
Als je GC-getuned Puma workers draait, houd dan rekening met de extra objectallocaties door span-creatie. In mijn benchmarks voegde OTel ~200 allocaties per request toe met auto-instrumentatie ingeschakeld. Niet nul, maar ruim acceptabel voor de zichtbaarheid die je krijgt.
FAQ
Werkt OpenTelemetry met Solid Queue in Rails 8?
Ja. De opentelemetry-instrumentation-active_job gem instrumenteert elke Active Job backend, inclusief Solid Queue. Elke job-uitvoering krijgt zijn eigen trace span die gelinkt is aan het bovenliggende request. Installeer het via de opentelemetry-instrumentation-all meta-gem of voeg het individueel toe.
Kan ik OpenTelemetry naast een bestaande APM-agent gebruiken?
Dat kan, maar ik zou het niet aanraden voor de lange termijn. Beide draaien betekent dubbele instrumentatie-overhead en mogelijk conflicterende monkey-patches. Een betere aanpak: migreer naar OpenTelemetry en gebruik de OTLP exporter om data naar je bestaande APM-vendor te sturen (de meeste accepteren nu OTLP — Datadog, New Relic, Honeycomb en Grafana Cloud allemaal).
Hoeveel vertraagt OpenTelemetry mijn Rails app?
In mijn productiemetingen: 1-2ms per request met volledige auto-instrumentatie en ongeveer 15MB extra geheugen per Puma worker. De async batch exporter zorgt ervoor dat telemetry-export de requestverwerking niet blokkeert.
Moet ik de OTLP HTTP of gRPC exporter gebruiken?
Gebruik HTTP (opentelemetry-exporter-otlp gem, poort 4318) tenzij je een specifieke reden hebt voor gRPC. HTTP is eenvoudiger te debuggen, werkt door meer proxies en load balancers heen, en het prestatieverschil is verwaarloosbaar voor de meeste Rails-applicaties.
About the Author
Roger Heykoop is een senior Ruby on Rails ontwikkelaar met 19+ jaar Rails ervaring en 35+ jaar ervaring in softwareontwikkeling. Hij is gespecialiseerd in Rails modernisering, performance optimalisatie, en AI-ondersteunde ontwikkeling.
Get in TouchRelated Articles
Need Expert Rails Development?
Let's discuss how we can help you build or modernize your Rails application with 19+ years of expertise
Schedule a Free Consultation