Ruby
Instrumentação Ruby on Rails com Elven Observability
Sumário
- Visão geral
- Pré-requisitos
- Exemplo de referência
- Configuração de variáveis de ambiente
- Bootstrap OpenTelemetry
- Instrumentações automáticas
- Span manual no controller
- Spans manuais por etapa no service object
- Chamada HTTP de saída
- Captura de erros em spans
- Estrutura de código relevante
- Como rodar localmente
- Como validar no collector da Elven
- Convenções recomendadas para spans manuais
- Como adaptar para a sua aplicação
- Testes
- Troubleshooting
- Checklist de deploy
Visão geral
A instrumentação Ruby on Rails da Elven usa o SDK oficial do OpenTelemetry para Ruby com exportação OTLP HTTP. Ela cobre instrumentação automática de HTTP server (Rails), banco de dados (ActiveRecord) e HTTP client (Net::HTTP), além de instrumentação manual de spans por etapa de negócio.
O Collector OTLP fica sempre no ambiente do cliente. A aplicação envia traces para esse Collector local/do cliente, e o Collector encaminha os dados para Tempo conforme a arquitetura contratada.
- Grafana Tempo para traces
- Grafana para consulta, correlação, painéis e alertas
Esta integração cobre somente traces. Métricas e logs ficam fora do escopo desta instrumentação.
| Componente | O que faz |
|---|---|
opentelemetry-api |
API pública do OpenTelemetry para Ruby. |
opentelemetry-sdk |
SDK completo com TracerProvider e BatchSpanProcessor. |
opentelemetry-exporter-otlp |
Exporter OTLP HTTP para envio de traces ao collector. |
opentelemetry-instrumentation-rails |
Instrumentação automática do Rails (HTTP server, ActiveRecord). |
opentelemetry-instrumentation-net_http |
Instrumentação automática de chamadas HTTP de saída via Net::HTTP. |
Pré-requisitos
- Ruby 3.3.x instalado
- Rails 8.1.2
- Docker Desktop ou Docker Engine funcionando
- Acesso de rede ao collector da Elven
- Collector OTLP HTTP aceitando
POST /v1/traces
Baseline técnico
| Item | Versão |
|---|---|
| Ruby | 3.3.x |
| Rails | 8.1.2 |
pg |
1.6.3 |
opentelemetry-api |
1.7.0 |
opentelemetry-sdk |
1.10.0 |
opentelemetry-exporter-otlp |
0.31.1 |
opentelemetry-instrumentation-rails |
0.39.1 |
opentelemetry-instrumentation-net_http |
0.27.0 |
Checklist do collector
Antes de subir a aplicação, confirme:
- O endpoint de traces está correto
- O header de autenticação está definido (se necessário)
- O collector aceita
http/protobuf
Atenção: se o collector exigir token, o valor em
OTEL_EXPORTER_OTLP_HEADERSdeve estar URL-encoded. Exemplo:
OTEL_EXPORTER_OTLP_HEADERS=authorization=Bearer%20SEU_TOKEN
Exemplo de referência
A Elven disponibiliza uma aplicação demo completa em Ruby on Rails que demonstra todos os conceitos desta documentação:
👉 elven-observability/ruby-otel-app
Configuração de variáveis de ambiente
Copie o arquivo de exemplo e preencha com os dados do seu collector:
cp .env.example .env
Configuração mínima funcional:
OTEL_SERVICE_NAME=ruby-rails-traces-demo
OTEL_RESOURCE_ATTRIBUTES=deployment.environment=local,service.version=1.0.0
OTEL_TRACES_EXPORTER=otlp
OTEL_METRICS_EXPORTER=none
OTEL_LOGS_EXPORTER=none
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://SEU-COLLECTOR:4318/v1/traces
OTEL_EXPORTER_OTLP_HEADERS=authorization=Bearer%20SEU_TOKEN
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
DATABASE_URL=postgres://postgres:postgres@localhost:5432/ruby_otel_app_development
DATABASE_TEST_URL=postgres://postgres:postgres@localhost:5432/ruby_otel_app_test
Variáveis de identidade do serviço
| Variável | Descrição | Default |
|---|---|---|
OTEL_SERVICE_NAME |
Nome do serviço. Deve ser estável e único por aplicação. | ruby-rails-traces-demo |
OTEL_RESOURCE_ATTRIBUTES |
Atributos extras no formato key=value,key2=value2. |
— |
OTEL_DEPLOYMENT_ENVIRONMENT |
Ambiente: local, staging, production. Lido diretamente no initializer. |
Rails.env |
Variáveis de export OTLP
| Variável | Descrição | Default |
|---|---|---|
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT |
Endpoint específico de traces. | http://localhost:4318/v1/traces |
OTEL_EXPORTER_OTLP_HEADERS |
Headers em key=value. Para tokens, use URL-encoding. |
— |
Variáveis de sinais
| Variável | Descrição | Default |
|---|---|---|
OTEL_TRACES_EXPORTER |
Exporter de traces: otlp ou none. |
otlp |
OTEL_METRICS_EXPORTER |
Desabilitado nesta integração. | none |
OTEL_LOGS_EXPORTER |
Desabilitado nesta integração. | none |
Variáveis de banco
| Variável | Descrição |
|---|---|
POSTGRES_HOST |
Host do PostgreSQL. |
POSTGRES_PORT |
Porta do PostgreSQL. Default: 5432. |
POSTGRES_USER |
Usuário do banco. |
POSTGRES_PASSWORD |
Senha do banco. |
DATABASE_URL |
URL completa de conexão para desenvolvimento. |
DATABASE_TEST_URL |
URL completa de conexão para testes. |
Bootstrap OpenTelemetry
O initializer do OpenTelemetry deve ser criado em config/initializers/opentelemetry.rb. Ele é responsável por configurar o SDK, o exporter OTLP, o BatchSpanProcessor e ativar as instrumentações automáticas.
# config/initializers/opentelemetry.rb
require "opentelemetry/sdk"
require "opentelemetry/exporter/otlp"
require "opentelemetry/instrumentation/rails"
require "opentelemetry/instrumentation/net/http"
otel_headers = ENV["OTEL_EXPORTER_OTLP_HEADERS"]
otel_headers = {} if otel_headers.nil? || otel_headers.strip.empty?
exporter = OpenTelemetry::Exporter::OTLP::Exporter.new(
endpoint: ENV.fetch("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", "http://localhost:4318/v1/traces"),
headers: otel_headers
)
resource = OpenTelemetry::SDK::Resources::Resource.create(
"deployment.environment" => ENV.fetch("OTEL_DEPLOYMENT_ENVIRONMENT", Rails.env.to_s)
)
OpenTelemetry::SDK.configure do |c|
c.service_name = ENV.fetch("OTEL_SERVICE_NAME", "ruby-rails-traces-demo")
c.resource = resource
c.add_span_processor(OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(exporter))
c.use "OpenTelemetry::Instrumentation::Rails"
c.use "OpenTelemetry::Instrumentation::Net::HTTP"
end
Com esse setup:
- O SDK inicializa antes do primeiro request Rails
- O
BatchSpanProcessorenvia spans em lote ao collector - As instrumentações automáticas de Rails e Net::HTTP são ativadas explicitamente
Instrumentações automáticas
Com o initializer configurado, as seguintes instrumentações passam a funcionar sem nenhuma alteração no código da aplicação:
| Instrumentação | O que gera |
|---|---|
OpenTelemetry::Instrumentation::Rails |
Span inbound HTTP server para cada request Rails. Span de banco (ActiveRecord) para cada query. |
OpenTelemetry::Instrumentation::Net::HTTP |
Span outbound HTTP client para cada chamada via Net::HTTP. |
Para adicionar outras instrumentações disponíveis no opentelemetry-ruby-contrib, basta incluir a gem correspondente e ativar com c.use no initializer. Exemplos:
c.use "OpenTelemetry::Instrumentation::Faraday"
c.use "OpenTelemetry::Instrumentation::Sidekiq"
c.use "OpenTelemetry::Instrumentation::Redis"
Span manual no controller
Defina um tracer por arquivo e envolva o bloco de negócio principal com in_span. Sempre capture o trace_id para retornar na resposta.
# app/controllers/checkout_controller.rb
TRACER = OpenTelemetry.tracer_provider.tracer("checkout-controller", "1.0.0")
def create
TRACER.in_span("checkout.request") do |span|
order = CheckoutService.new(
customer_id: checkout_params.fetch(:customer_id),
amount_cents: checkout_params.fetch(:amount_cents),
force_error: checkout_params.fetch(:force_error),
internal_url: internal_ping_url
).call
render json: {
order_id: order.id,
status: order.status,
trace_id: current_trace_id
}, status: :ok
rescue ActionController::ParameterMissing => e
span.record_exception(e)
span.status = OpenTelemetry::Trace::Status.error(e.message)
render json: { error: e.message, trace_id: current_trace_id }, status: :unprocessable_entity
rescue CheckoutService::CheckoutError => e
span.record_exception(e)
span.status = OpenTelemetry::Trace::Status.error(e.message)
render json: { error: e.message, trace_id: current_trace_id }, status: :internal_server_error
end
end
O
trace_idretornado na resposta é o caminho mais rápido para localizar o trace completo no backend da Elven.
Spans manuais por etapa no service object
Crie um helper with_span no service object para centralizar a criação de spans e o tratamento de erros:
# app/services/checkout_service.rb
TRACER = OpenTelemetry.tracer_provider.tracer("checkout-service", "1.0.0")
def call
valid_amount = with_span("checkout.validate_input") { validate_input! }
order = with_span("checkout.persist_order") { persist_order!(valid_amount) }
with_span("checkout.call_internal_service") { call_internal_service! }
with_span("checkout.finalize") { finalize_order!(order) }
order
end
def with_span(name, attributes = {})
TRACER.in_span(name, attributes: attributes) do |span|
yield
rescue StandardError => e
span.record_exception(e)
span.status = OpenTelemetry::Trace::Status.error(e.message)
raise
end
end
Exemplos de spans manuais recomendados:
checkout.validate_inputcheckout.persist_ordercheckout.call_internal_servicecheckout.finalize
Chamada HTTP de saída
Use Net::HTTP normalmente. Com a instrumentação Net::HTTP ativa no initializer, cada chamada gera automaticamente um span outbound correlacionado ao trace da request de origem:
def call_internal_service!
uri = URI.parse(@internal_url)
response = Net::HTTP.get_response(uri)
return response if response.code.to_i.between?(200, 299)
raise CheckoutError, "internal ping failed with status #{response.code}"
end
O que isso gera automaticamente:
- Span outbound HTTP com método, URL e status code
- Correlação automática com o span pai via propagação de contexto
Captura de erros em spans
Em qualquer bloco de span, registre exceções e defina o status de erro explicitamente:
span.record_exception(e)
span.status = OpenTelemetry::Trace::Status.error(e.message)
Isso garante que:
- A exceção aparece como evento dentro do span no Tempo
- O span é marcado com status
ERROR, visível na árvore de traces - O
trace_idda resposta pode ser usado para localizar o trace com erro
Estrutura de código relevante
ruby-otel-app/
├── config/
│ └── initializers/
│ └── opentelemetry.rb # Bootstrap do SDK, exporter e instrumentações
├── app/
│ ├── controllers/
│ │ ├── checkout_controller.rb # Span manual checkout.request + captura de erro
│ │ ├── health_controller.rb # GET /health
│ │ └── internal_controller.rb # GET /internal/ping
│ ├── services/
│ │ └── checkout_service.rb # Spans manuais por etapa de negócio
│ └── models/
│ └── order.rb # Persistência — gera spans de DB automaticamente
├── db/
│ └── migrate/
│ └── *_create_orders.rb
├── config/
│ └── routes.rb
├── Gemfile
├── docker-compose.yml
└── .env.example
Como rodar localmente
1. Preparar variáveis
cp .env.example .env
# Edite o .env e preencha os endpoints do collector
2. Subir PostgreSQL
docker compose up -d
Se a porta 5432 já estiver em uso:
POSTGRES_PORT=5433 docker compose up -d
3. Instalar dependências
bundle install
4. Exportar variáveis do .env
set -a
source .env
set +a
5. Preparar o banco
bin/rails db:prepare
6. Subir a aplicação
bin/rails s
7. Validar saúde
curl -s http://localhost:3000/health
8. Gerar traces de demonstração
Fluxo saudável:
curl -s -X POST http://localhost:3000/checkout <br>
-H "Content-Type: application/json" <br>
-d '{"customer_id":"cust-001","amount_cents":1500}'
Fluxo com erro forçado:
curl -s -X POST http://localhost:3000/checkout <br>
-H "Content-Type: application/json" <br>
-d '{"customer_id":"cust-002","amount_cents":2500,"force_error":true}'
Resposta esperada (fluxo saudável):
{
"order_id": "a1b2c3d4-...",
"status": "completed",
"trace_id": "2b1f3a7b5c9049e18d3cba0f5a1f9c31"
}
Use o
trace_idretornado para localizar o trace completo no backend da Elven.
Como validar no collector da Elven
Após chamar o endpoint, valide no backend nesta ordem:
- Filtre por
service.name = ruby-rails-traces-demo(ou o nome configurado emOTEL_SERVICE_NAME) - Pesquise pelo
trace_idretornado pela API - Confirme a árvore de spans
- Confirme o status de erro nos fluxos com
force_error=true
Árvore de spans esperada (fluxo saudável)
- Span inbound Rails
POST /checkout - Span manual
checkout.request - Span manual
checkout.validate_input - Span manual
checkout.persist_order - Span ActiveRecord do
INSERTemorders - Span manual
checkout.call_internal_service - Span Net::HTTP outbound para
/internal/ping - Span manual
checkout.finalize
No fluxo com erro
- Span
checkout.finalizecom statusERROR - Exceção registrada como evento dentro do span
- Mesmo
trace_iddisponível na resposta da API
Convenções recomendadas para spans manuais
- Nomeie spans com domínio e verbo no formato
dominio.acao— ex.:checkout.persist_order,payment.authorize - Evite spans excessivos: instrumente apenas etapas que agregam diagnóstico real
- Adicione atributos úteis para investigação, sem expor dados sensíveis
- Não coloque PII (CPF, e-mail, token, senha) em atributos de span
- Em blocos de erro, sempre use
record_exception+status=error - Retorne
trace_idem respostas de erro e sucesso para acelerar troubleshooting
Como adaptar para a sua aplicação
Se a aplicação já é Rails
- Adicione as gems ao
Gemfilee rodebundle install - Crie o initializer em
config/initializers/opentelemetry.rb - Ative as instrumentações da sua stack com
c.use - Defina as variáveis de ambiente do collector
- Envolva etapas críticas de negócio em spans manuais com
TRACER.in_span
Se a aplicação usa outros HTTP clients
Substitua opentelemetry-instrumentation-net_http pela gem correspondente:
# Faraday
gem "opentelemetry-instrumentation-faraday"
c.use "OpenTelemetry::Instrumentation::Faraday"
# HTTPrb
gem "opentelemetry-instrumentation-http"
c.use "OpenTelemetry::Instrumentation::HTTP"
Se a aplicação usa workers assíncronos
# Sidekiq
gem "opentelemetry-instrumentation-sidekiq"
c.use "OpenTelemetry::Instrumentation::Sidekiq"
Se a aplicação usa Redis
gem "opentelemetry-instrumentation-redis"
c.use "OpenTelemetry::Instrumentation::Redis"
Testes
bin/rails test
Cobrem:
- Health check (
GET /health) - Endpoint interno (
GET /internal/ping) - Checkout com sucesso (persistência + chamada HTTP de saída)
- Checkout com erro forçado (500 +
trace_idna resposta)
Troubleshooting
Não aparecem traces no collector
- Confirme
OTEL_EXPORTER_OTLP_TRACES_ENDPOINTestá correto e acessível - Confirme que
OTEL_EXPORTER_OTLP_HEADERScontém o token correto com URL-encoding - Confirme
OTEL_TRACES_EXPORTER=otlp - Verifique os logs da app ao subir — as instrumentações devem aparecer como instaladas
- Gere uma requisição nova e procure pelo
trace_idretornado no JSON
O initializer não é carregado
Confirme que o arquivo está em config/initializers/opentelemetry.rb. O Rails carrega todos os arquivos desse diretório automaticamente no boot.
Spans de banco não aparecem
Confirme que OpenTelemetry::Instrumentation::Rails está ativado no initializer. A instrumentação do Rails já inclui ActiveRecord automaticamente.
Spans de HTTP client não aparecem
Confirme que OpenTelemetry::Instrumentation::Net::HTTP está ativado no initializer e que a chamada HTTP usa Net::HTTP (ou a gem instrumentada correspondente).
Token com caracteres especiais não funciona
Use URL-encoding no valor do header:
OTEL_EXPORTER_OTLP_HEADERS=authorization=Bearer%20SEU_TOKEN
Referências
- OpenTelemetry Ruby — Documentação oficial
- OpenTelemetry Ruby SDK
- OpenTelemetry Ruby Contrib
- ruby-otel-app — Repositório de referência
Checklist de deploy
- [ ] Ruby 3.3.x instalado no ambiente de build
- [ ] Gems
opentelemetry-api,opentelemetry-sdk,opentelemetry-exporter-otlpe instrumentações adicionadas aoGemfile - [ ] Initializer criado em
config/initializers/opentelemetry.rb - [ ]
OTEL_SERVICE_NAMEdefinido - [ ]
OTEL_RESOURCE_ATTRIBUTEScomdeployment.environmenteservice.version - [ ]
OTEL_EXPORTER_OTLP_TRACES_ENDPOINTapontando para o collector correto - [ ]
OTEL_EXPORTER_OTLP_HEADERScom token URL-encoded (se necessário) - [ ]
OTEL_TRACES_EXPORTER=otlp - [ ]
OTEL_METRICS_EXPORTER=none - [ ]
OTEL_LOGS_EXPORTER=none - [ ] Instrumentações automáticas ativadas com
c.usepara toda a stack usada - [ ] Spans manuais criados para etapas críticas de negócio
- [ ]
record_exceptionestatus=errorusados em todos os blocos de rescue - [ ]
trace_idretornado nas respostas da API - [ ] Traces aparecem no Tempo com
service.namecorreto - [ ] Árvore de spans reflete o fluxo real da aplicação