Skip to content
Maple Docs
Open app

Ruby instrumentation

Instrument a Ruby or Rails application with OpenTelemetry and send traces to Maple.

This guide sets up the OpenTelemetry Ruby SDK so your Rails, Sinatra or plain Ruby application sends traces to Maple, with instrumentation for Rails, Active Record, Sidekiq, Net::HTTP and the other libraries in opentelemetry-instrumentation-all.

To have a coding agent do this setup, use the maple-onboard skill, and maple-audit to check an existing setup.

Prerequisites

  • Ruby 3.3+ (the version current releases of opentelemetry-sdk require)
  • Bundler
  • An ingest key from Settings → Ingestion in Maple. Use the private key (maple_sk_…) for server applications.

Install

Add the SDK, the OTLP exporter and the instrumentation bundle to your Gemfile:

# Gemfile
gem "opentelemetry-sdk"
gem "opentelemetry-exporter-otlp"
gem "opentelemetry-instrumentation-all"
bundle install

opentelemetry-instrumentation-all pulls in the instrumentation for every library it supports. Each one only turns on when its library is loaded, so unused ones cost nothing.

Configure Rails

Create an initializer. It runs in every Rails process, including the console, runners and Sidekiq workers:

# 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.use_all
end

use_all enables every installed instrumentation: Rails (Action Pack, Action View, Active Record, Active Job, Active Support), Rack, Net::HTTP, Faraday, pg, mysql2, Redis, Sidekiq and more. Each request becomes a server span, named after the method and route, with child spans for the queries and outgoing calls it made.

The batch span processor restarts its export thread after a fork, so Puma in cluster mode and other forking servers need no extra setup.

Environment variables

The SDK and the exporter read the standard variables, so the endpoint and key stay out of source:

export OTEL_SERVICE_NAME="my-rails-app"
export OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.maple.dev"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer YOUR_INGEST_KEY"
export OTEL_RESOURCE_ATTRIBUTES="deployment.environment.name=production,vcs.repository.url.full=https://github.com/acme/my-rails-app"

EU organizations use https://ingest.eu.maple.dev. The exporter appends /v1/traces to the endpoint itself. OTEL_SERVICE_NAME overrides the service_name set in the initializer, so you can drop that line once the variable is set everywhere.

The Ruby exporter speaks OTLP over HTTP with protobuf encoding only. Leave OTEL_EXPORTER_OTLP_PROTOCOL unset or set it to http/protobuf; with grpc the SDK logs a warning and exports nothing. To compress batches, set OTEL_EXPORTER_OTLP_COMPRESSION=gzip.

Sinatra and plain Ruby

Outside Rails, configure the SDK before your app is defined. For Sinatra, add gem "sinatra" as usual and put the setup at the top of the app file or in config.ru:

# app.rb
require "opentelemetry/sdk"
require "opentelemetry/exporter/otlp"
require "opentelemetry/instrumentation/all"

OpenTelemetry::SDK.configure do |c|
  c.use_all
end

require "sinatra"

get "/hello" do
  "hello"
end

For a script or a one-off job, create your own spans and shut the provider down before exit, so the last batch is sent:

require "opentelemetry/sdk"
require "opentelemetry/exporter/otlp"

OpenTelemetry::SDK.configure
at_exit { OpenTelemetry.tracer_provider.shutdown }

tracer = OpenTelemetry.tracer_provider.tracer("nightly-import")

tracer.in_span("import-orders") do |span|
  span.set_attribute("import.source", "s3")
  import_orders
end

Sidekiq

With the Rails initializer, Sidekiq needs no extra setup: the Sidekiq server process loads Rails and runs the initializer, and use_all enables the Sidekiq instrumentation. Each enqueue becomes a producer span and each job run a consumer span.

By default the job’s span starts a new trace and links back to the span that enqueued it. To make the job a child of the request that enqueued it, so both appear in one trace, set propagation_style to :child:

OpenTelemetry::SDK.configure do |c|
  c.use_all({
    "OpenTelemetry::Instrumentation::Sidekiq" => { propagation_style: :child },
  })
end

Long-running jobs that enqueue many others make very large traces with :child, so keep the default for those.

Custom spans

class ProcessOrder
  TRACER = OpenTelemetry.tracer_provider.tracer("my-rails-app")

  def call(order_id)
    TRACER.in_span("process-order") do |span|
      span.set_attribute("order.id", order_id)
      span.set_attribute("payment.method", "card")
      charge_payment(order_id)
    end
  end
end

in_span records any exception raised inside the block on the span, sets its status to error and re-raises. To mark a failure you handle yourself, call span.record_exception(e) and span.status = OpenTelemetry::Trace::Status.error(e.message).

Service map edges come from instrumented client spans that propagate traceparent to an instrumented callee, not from attributes such as peer.service. See Service map.

Attributes Maple uses

Most attributes are optional. These unlock specific features.

Commits and GitHub

Set vcs.ref.head.revision (the full 40-character commit SHA) and vcs.repository.url.full on the resource. They drive deploy markers, the Releases page and, with the GitHub integration, commit hover cards on traces. Read the SHA from your CI or host at build time, such as GITHUB_SHA or HEROKU_SLUG_COMMIT, not from git at runtime:

export OTEL_RESOURCE_ATTRIBUTES="vcs.repository.url.full=https://github.com/acme/api,vcs.ref.head.revision=$GITHUB_SHA"

Databases

The pg, mysql2 and trilogy instrumentations set the standard db.* attributes for you. The database system and name draw the database node on the service map, and the query text groups statements into query shapes. The pg and mysql2 instrumentations obfuscate literal values in the query text by default; keep that on. See Database queries for the full list.

Logs and metrics

The Ruby SDK’s trace support is stable. Its metrics and logs SDKs (opentelemetry-metrics-sdk, opentelemetry-logs-sdk and their OTLP exporters) are still experimental, and their APIs can change between releases. This guide covers traces only. If you try the experimental exporters, they use the same endpoint and headers.

Verify

  1. Start your application and send it a few requests, or enqueue a Sidekiq job.
  2. In Maple, open Explore → Traces. The SDK sends spans in batches every 5 seconds by default.
  3. Each request should show up as one trace with a single root server span, named after the method and route (for example GET /orders/:id on Rails 7.1 and later), and child spans for the queries and outgoing calls it made.

Your service also appears on the Services page once its first spans arrive.

Troubleshooting

  • 401 responses. The key is wrong, was copied from the other region, or the header is malformed. OTEL_EXPORTER_OTLP_HEADERS takes Authorization=Bearer YOUR_INGEST_KEY. See Ingest API status codes.
  • The grpc transport protocol is not supported in the log. OTEL_EXPORTER_OTLP_PROTOCOL is set to grpc, often inherited from a shared environment. Unset it or set http/protobuf.
  • please add opentelemetry-exporter-otlp to your Gemfile in the log. The exporter gem is missing from the bundle, so spans are not exported.
  • Network. From the machine running the app, run curl -i https://ingest.maple.dev/v1/traces -X POST. Any HTTP status code means the host can reach Maple. A timeout or DNS error means a firewall or proxy is blocking outbound HTTPS.
  • Nothing exported. Set OTEL_LOG_LEVEL=debug to print the SDK’s export errors. For a quick local check, set OTEL_TRACES_EXPORTER=console to print spans to stdout instead of sending them. Scripts that exit right away must call OpenTelemetry.tracer_provider.shutdown first.

Next steps