Ruby SDK

Full observability for Ruby services — HTTP, databases, queues, and logging captured automatically via OpenTelemetry

Installation

gem install obtrace-sdk-ruby

Or add to your Gemfile:

Gemfile
gem "obtrace-sdk-ruby"

Requires Ruby 3.0+.

Minimum Setup

config/initializers/obtrace.rb
require "obtrace_sdk"
 
OBTRACE = ObtraceSDK::Client.new(ObtraceSDK::Config.new(
  api_key: ENV.fetch("OBTRACE_API_KEY"),
  service_name: "order-api"
))

Two fields are required: api_key and service_name. Everything else has defaults.

Built on OpenTelemetry

The Ruby SDK uses opentelemetry-sdk with auto-detection of installed instrumentations. When you initialize the client, it scans for installed OTel instrumentation gems and enables them automatically. The Rails Railtie auto-configures the full OTel pipeline including resource attributes and the OTLP exporter pointed at your Obtrace ingest endpoint.

What's Captured Automatically

After creating an ObtraceSDK::Client, the SDK configures OpenTelemetry and enables all detected instrumentations. You don't need to write any extra code for this:

WhatHowNeeds code?
All raised exceptionsTracePoint(:raise) intercepts every raise and sends it as an error logNo
Unhandled fatal exceptionsFatal exceptions that reach the top of the stack are captured at process exitNo
Outbound HTTPNet::HTTP and Faraday instrumented via OTel with spans and traceparent injectionNo
Inbound HTTPrack, rails, sinatra requests instrumented via OTelNo
Databasespg, mysql2 queries instrumented with spans when OTel gems are installedNo
Redisredis gem operations instrumented via OTelNo
SidekiqJob execution instrumented with spans via OTelNo
gRPCClient and server calls instrumented via OTelNo
RabbitMQbunny gem operations instrumented via OTelNo
Logger outputLogger instances are intercepted and forwarded as structured logs with level mappingNo
Shutdown flushat_exit flushes buffered telemetry on process exitNo

Opting Out of Auto HTTP Instrumentation

OBTRACE = ObtraceSDK::Client.new(ObtraceSDK::Config.new(
  api_key: ENV.fetch("OBTRACE_API_KEY"),
  service_name: "order-api",
  auto_instrument_http: false
))

Opting Out of Logger Capture

OBTRACE = ObtraceSDK::Client.new(ObtraceSDK::Config.new(
  api_key: ENV.fetch("OBTRACE_API_KEY"),
  service_name: "order-api",
  auto_capture_logger: false
))

Optional: Custom Telemetry

The automatic exception, HTTP, and Logger capture covers basic observability. For business-specific events, use the SDK methods directly.

Logging

Use client.log for structured events. Logs are best for state transitions, errors, and audit trails.

OBTRACE.log("info", "order.created",
  "order.id" => order_id,
  "order.total" => 149.99,
  "customer.tier" => "premium"
)
 
OBTRACE.log("error", "payment.declined",
  "order.id" => order_id,
  "payment.provider" => "stripe",
  "decline.code" => "insufficient_funds"
)
 
OBTRACE.log("warn", "inventory.low",
  "sku" => "WIDGET-42",
  "remaining" => 3,
  "reorder.threshold" => 10
)

Levels: trace, debug, info, warn, error, fatal.

Metrics

Use client.metric for numerical measurements you want to track over time.

OBTRACE.metric("http.server.request.duration", 42.5, "ms",
  "http.method" => "POST",
  "http.route" => "/api/orders"
)
 
OBTRACE.metric("queue.depth", 847, "1",
  "queue.name" => "order-processing"
)
 
OBTRACE.metric("cache.hit_ratio", 0.92, "1",
  "cache.name" => "product-catalog"
)
 
OBTRACE.metric("db.pool.active_connections", 14, "1")

The unit parameter follows OTLP conventions: "ms" for milliseconds, "By" for bytes, "1" for dimensionless values.

Tracing / Spans

Use client.span for operations with duration. Spans appear in the trace waterfall and are best for database queries, HTTP calls, and queue processing.

OBTRACE.span("db.query SELECT orders", attrs: {
  "db.system" => "postgresql",
  "db.statement" => "SELECT * FROM orders WHERE customer_id = $1",
  "db.operation" => "SELECT",
  "db.sql.table" => "orders"
})
 
OBTRACE.span("http.client POST /payments", attrs: {
  "http.method" => "POST",
  "http.url" => "https://api.stripe.com/v1/charges",
  "http.status_code" => 201,
  "payment.amount" => 149.99
})
 
OBTRACE.span("queue.publish order.created", attrs: {
  "messaging.system" => "rabbitmq",
  "messaging.destination" => "order.created"
})

Framework Integration

Rails (Railtie Auto-Init)

The SDK includes a Rails Railtie that automatically initializes when Rails boots. Add your credentials to config/credentials.yml.enc or environment variables, then add the gem to your Gemfile:

Gemfile
gem "obtrace-sdk-ruby"

Set the required environment variables:

OBTRACE_API_KEY=your-key
OBTRACE_SERVICE_NAME=order-api

The Railtie auto-initializes ObtraceSDK::Client, configures OpenTelemetry with the OTLP exporter, enables all detected OTel instrumentations (ActiveRecord, ActionPack, Rack, Net::HTTP, Faraday, Redis, Sidekiq, pg, mysql2, etc.), registers Rack middleware for request instrumentation, and wires up shutdown flushing. No initializer file needed.

To access the client instance:

ObtraceSDK.client.log("info", "order.created", "order.id" => order_id)

Rails (Manual)

If you prefer manual setup over the Railtie:

config/initializers/obtrace.rb
require "obtrace_sdk"
 
OBTRACE = ObtraceSDK::Client.new(ObtraceSDK::Config.new(
  api_key: ENV.fetch("OBTRACE_API_KEY"),
  service_name: "order-api",
  env: Rails.env,
  service_version: ENV.fetch("OBTRACE_SERVICE_VERSION", "1.0.0")
))
app/middleware/obtrace_middleware.rb
class ObtraceMiddleware
  def initialize(app)
    @app = app
  end
 
  def call(env)
    start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
    status, headers, body = @app.call(env)
    duration_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000
 
    method = env["REQUEST_METHOD"]
    route = env["action_dispatch.request.path_parameters"]&.values_at(:controller, :action)&.compact&.join("#") || env["PATH_INFO"]
 
    OBTRACE.span("http.server #{method} #{route}", attrs: {
      "http.method" => method,
      "http.route" => route,
      "http.status_code" => status
    })
 
    OBTRACE.metric("http.server.request.duration", duration_ms, "ms",
      "http.method" => method,
      "http.route" => route,
      "http.status_code" => status
    )
 
    [status, headers, body]
  end
end

Register in config/application.rb:

config.middleware.use ObtraceMiddleware

Rack

For Sinatra or plain Rack apps, use the same middleware pattern:

config.ru
require "obtrace_sdk"
require_relative "app"
 
OBTRACE = ObtraceSDK::Client.new(ObtraceSDK::Config.new(
  api_key: ENV.fetch("OBTRACE_API_KEY"),
  service_name: "order-api",
  env: ENV.fetch("RACK_ENV", "production")
))
 
use ObtraceMiddleware
run App

Sidekiq / Background Jobs

For background workers, instrument each job and flush periodically:

app/jobs/process_order_job.rb
class ProcessOrderJob
  include Sidekiq::Job
 
  def perform(order_id)
    order = Order.find(order_id)
 
    OBTRACE.span("job.process ProcessOrderJob", attrs: {
      "job.system" => "sidekiq",
      "order.id" => order_id
    })
 
    charge_customer(order)
    OBTRACE.metric("orders.processed", 1, "1")
  rescue => e
    OBTRACE.log("error", "order.processing.failed",
      "order.id" => order_id,
      "error.type" => e.class.name,
      "error.message" => e.message
    )
    raise
  end
end

Add a Sidekiq middleware to flush after each job:

config/initializers/sidekiq.rb
Sidekiq.configure_server do |config|
  config.server_middleware do |chain|
    chain.add Class.new {
      def call(_worker, _job, _queue)
        yield
      ensure
        OBTRACE.flush
      end
    }
  end
end

Configuration Reference

ParameterTypeRequiredDefaultDescription
api_keyStringYesObtrace API key
service_nameStringYesStable name for this service
tenant_idString?Nofrom keyTenant identifier
project_idString?Nofrom keyProject identifier
app_idString?NoApplication identifier within the project
envStringNo"production"Deployment environment
service_versionString?NoVersion string for this deployment
auto_instrument_httpBooleanNotruePatch Net::HTTP with automatic spans and trace propagation
auto_capture_loggerBooleanNotrueIntercept Logger output and forward as structured logs
validate_semantic_metricsBooleanNofalseWarn on non-standard metric names
debugBooleanNofalseEnable verbose logging to stderr
max_queue_sizeIntegerNo2048Maximum buffered items before dropping
flush_interval_msIntegerNo5000Auto-flush interval in milliseconds

Validation Checklist

After deploying, verify:

  • service_name, env, and service_version are stable across restarts and match what you see in the Obtrace UI
  • At least one real request path emits both a log and a span
  • No 401 or 403 errors appear in stderr when debug is true
  • The at_exit hook is firing (check that telemetry arrives from short-lived scripts without explicit flush calls)
  • Outbound Net::HTTP calls show auto-generated spans
  • Logger output appears as structured logs in the Obtrace UI
  • Sidekiq or other long-running workers flush after each job rather than relying solely on the exit hook
  • Metrics use OTLP-standard units (ms, By, 1) not custom strings