OpenTelemetry Ruby Configuration for Uptrace

This guide shows you how to configure the OpenTelemetry Ruby SDK to export spans and metrics to Uptrace using the OTLP/HTTP protocol.

For detailed API documentation, see:

Overview

OpenTelemetry provides observability for your Ruby applications by collecting telemetry data (traces and metrics) and exporting it to monitoring systems like Uptrace.

Prerequisites

Ensure that you have the following installed locally:

  • Ruby >= 3.0 (JRuby >= 9.3.2.0, TruffleRuby >= 22.1 supported)
  • Bundler for dependency management

Installation Options

uptrace-ruby is a thin wrapper over opentelemetry-ruby that configures the OpenTelemetry SDK to export data to Uptrace. It does not add any new functionality and is provided only for your convenience.

Installation:

Add to Gemfile:

ruby
gem 'uptrace'

Or install the gem:

shell
gem install uptrace

Option 2: Direct OTLP Exporter

Skip the wrapper and use the OTLP exporter directly if you prefer more control over the configuration. See the Direct OTLP Configuration section below.

Quick Start Guide

Get your first trace running in 5 minutes by following these steps:

Step 1: Create an Uptrace Project

  • Create an Uptrace project to obtain your DSN (Data Source Name)
  • Your DSN will look like: https://<secret>@api.uptrace.dev?grpc=4317

Step 2: Install the Package

bash
gem install uptrace

Step 3: Basic Configuration

Copy the code to main.rb:

ruby
#!/usr/bin/env ruby
# frozen_string_literal: true

require 'rubygems'
require 'bundler/setup'
require 'uptrace'

# Configure OpenTelemetry with sensible defaults.
# DSN can be set via UPTRACE_DSN environment variable.
# Example: export UPTRACE_DSN="https://<project_secret>@uptrace.dev?grpc=4317"
Uptrace.configure_opentelemetry(dsn: '') do |c|
  # c is an instance of OpenTelemetry::SDK::Configurator

  # Configure service metadata (helps identify this service in Uptrace).
  c.service_name = 'myservice'
  c.service_version = '1.0.0'

  # Add environment information
  c.resource = OpenTelemetry::SDK::Resources::Resource.create(
    'deployment.environment' => ENV.fetch('RACK_ENV', 'development')
  )
end

# Ensure spans are flushed even if the program exits unexpectedly.
at_exit { OpenTelemetry.tracer_provider.shutdown }

# Register a tracer (usually stored globally).
TRACER = OpenTelemetry.tracer_provider.tracer('my_app', '0.1.0')

# Example trace with nested spans.
TRACER.in_span('main-operation', kind: :server) do |main_span|
  # Simulate an HTTP request span.
  TRACER.in_span('GET /posts/:id', kind: :client) do |http_span|
    http_span.set_attribute('http.method', 'GET')
    http_span.set_attribute('http.route', '/posts/:id')
    http_span.set_attribute('http.url', 'http://localhost:8080/posts/123')
    http_span.set_attribute('http.status_code', 200)
    http_span.record_exception(ArgumentError.new('Invalid parameter'))
  end

  # Simulate a database query span.
  TRACER.in_span('SELECT posts', kind: :client) do |db_span|
    db_span.set_attribute('db.system', 'mysql')
    db_span.set_attribute('db.statement', 'SELECT * FROM posts LIMIT 100')
  end

  # Print the trace URL (clickable in console).
  puts "Trace URL: #{Uptrace.trace_url(main_span)}"
end

Step 4: Run Your Application

Run the code, replacing <FIXME> with your Uptrace DSN:

bash
UPTRACE_DSN="<FIXME>" ruby main.rb

Expected output:

text
trace URL: https://app.uptrace.dev/traces/<trace_id>

Step 5: View Your Trace

Follow the link to view the generated trace:

Configuration Options

Resource Attributes

Resource attributes provide metadata about your service:

ruby
Uptrace.configure_opentelemetry() do |c|
  c.service_name = 'user-service'
  c.service_version = '2.1.0'

  c.resource = OpenTelemetry::SDK::Resources::Resource.create({
    'service.namespace' => 'backend',
    'service.instance.id' => 'instance-001',
    'deployment.environment' => 'production',
    'cloud.provider' => 'aws',
    'cloud.region' => 'us-west-2'
  })
end

Sampling Configuration

For production environments with high traffic, configure sampling to reduce overhead:

ruby
# Set sampling rate through environment variable
ENV['OTEL_TRACES_SAMPLER'] = 'traceidratio'
ENV['OTEL_TRACES_SAMPLER_ARG'] = '0.1'  # Sample 10% of traces

Uptrace.configure_opentelemetry() do |c|
  c.service_name = 'high-traffic-service'
  # Sampling configuration will be read from environment variables
end

Auto-Instrumentation

OpenTelemetry Ruby automatically instruments popular libraries and frameworks without requiring code changes.

Supported Libraries

Auto-instrumentation is available for:

  • Web frameworks: Rails, Sinatra, Rack
  • HTTP clients: Net::HTTP, Faraday, RestClient
  • Databases: ActiveRecord, Redis, PostgreSQL (pg), MySQL (mysql2)
  • Message queues: Sidekiq
  • Other libraries: GraphQL, Bunny (RabbitMQ)

Installation

To use auto-instrumentation in a Ruby project, add it to the Gemfile:

ruby
gem 'opentelemetry-instrumentation-all'

Then run bundle install to install the gem and its dependencies.

Enabling Auto-Instrumentation

The c.use_all() method enables all available instrumentations:

ruby
require 'uptrace'
require 'opentelemetry/instrumentation/all'

Uptrace.configure_opentelemetry(dsn: dsn) do |c|
  c.service_name = 'my-rails-app'
  c.use_all() # enables all instrumentation!
end

Selective Instrumentation

For more control, enable specific instrumentations:

ruby
Uptrace.configure_opentelemetry(dsn: dsn) do |c|
  c.service_name = 'my-app'

  # Enable specific instrumentations
  c.use 'OpenTelemetry::Instrumentation::Rails'
  c.use 'OpenTelemetry::Instrumentation::Net::HTTP'
  c.use 'OpenTelemetry::Instrumentation::Redis'
  c.use 'OpenTelemetry::Instrumentation::PG', {
    # Configure database statement handling
    db_statement: :obfuscate  # Options: :include, :omit, :obfuscate
  }
end

Environment Variable Control

Disable specific instrumentations using environment variables:

bash
# Disable Sinatra instrumentation
export OTEL_RUBY_INSTRUMENTATION_SINATRA_ENABLED=false

# Disable Redis instrumentation
export OTEL_RUBY_INSTRUMENTATION_REDIS_ENABLED=false

Framework Integration

Rails Applications

For Rails applications, create an initializer:

ruby
# config/initializers/opentelemetry.rb
require 'uptrace'
require 'opentelemetry/instrumentation/all'

Uptrace.configure_opentelemetry do |c|
  c.service_name = ENV['OTEL_SERVICE_NAME'] || Rails.application.class.module_parent_name.downcase
  c.service_version = ENV['OTEL_SERVICE_VERSION'] || '1.0.0'

  # Add environment information
  c.resource = OpenTelemetry::SDK::Resources::Resource.create(
    'deployment.environment' => Rails.env
  )

  c.use_all() # enables all instrumentation!
end

Sinatra Applications

For Sinatra applications, initialize early in your app:

ruby
require 'sinatra'
require 'uptrace'
require 'opentelemetry/instrumentation/all'

# Configure OpenTelemetry before defining routes
Uptrace.configure_opentelemetry do |c|
  c.service_name = 'my-sinatra-app'
  c.use_all()
end

get '/hello' do
  'Hello World!'
end

Rack Middleware

For custom Rack applications:

ruby
require 'rack'
require 'uptrace'

Uptrace.configure_opentelemetry do |c|
  c.service_name = 'my-rack-app'
  c.use 'OpenTelemetry::Instrumentation::Rack'
end

app = Proc.new do |env|
  ['200', {'Content-Type' => 'text/html'}, ['Hello World!']]
end

run app

Already Using OTLP Exporter?

Uptrace fully supports the OpenTelemetry Protocol (OTLP) over both gRPC and HTTP transports.
If you already have an OTLP exporter configured, you can continue using it with Uptrace by simply pointing it to the Uptrace OTLP endpoint.

Connecting to Uptrace

Choose an OTLP endpoint from the table below and pass your DSN via the uptrace-dsn header for authentication:

TransportEndpointPort
gRPChttps://api.uptrace.dev:43174317
HTTPhttps://api.uptrace.dev443

When using HTTP transport, you often need to specify the full URL for each signal type:

  • https://api.uptrace.dev/v1/traces
  • https://api.uptrace.dev/v1/logs
  • https://api.uptrace.dev/v1/metrics

Note: Most OpenTelemetry SDKs support both transports. Use HTTP unless you're already familiar with gRPC.

For performance and reliability, we recommend:

  • Use BatchSpanProcessor and BatchLogProcessor for batching spans and logs, reducing the number of export requests.
  • Enable gzip compression to reduce bandwidth usage.
  • Prefer delta metrics temporality (Uptrace converts cumulative metrics automatically).
  • Use Protobuf encoding instead of JSON (Protobuf is more efficient and widely supported).
  • Use HTTP transport for simplicity and fewer configuration issues (unless you're already familiar with gRPC).
  • Optionally, use the AWS X-Ray ID generator to produce trace IDs compatible with AWS X-Ray.

Common Environment Variables

You can use environment variables to configure resource attributes and propagators::

VariableDescription
OTEL_RESOURCE_ATTRIBUTESComma-separated resource attributes, e.g., service.name=myservice,service.version=1.0.0.
OTEL_SERVICE_NAME=myserviceSets the service.name attribute (overrides OTEL_RESOURCE_ATTRIBUTES).
OTEL_PROPAGATORSComma-separated list of context propagators (default: tracecontext,baggage).

Most language SDKs allow configuring the OTLP exporter entirely via environment variables:

shell
# Endpoint (choose HTTP or gRPC)
export OTEL_EXPORTER_OTLP_ENDPOINT="https://api.uptrace.dev"         # HTTP
#export OTEL_EXPORTER_OTLP_ENDPOINT="https://api.uptrace.dev:4317"   # gRPC

# Pass DSN for authentication
export OTEL_EXPORTER_OTLP_HEADERS="uptrace-dsn=<FIXME>"

# Performance optimizations
export OTEL_EXPORTER_OTLP_COMPRESSION=gzip
export OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION=BASE2_EXPONENTIAL_BUCKET_HISTOGRAM
export OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=DELTA

Configure BatchSpanProcessor to balance throughput and payload size:

shell
export OTEL_BSP_EXPORT_TIMEOUT=10000         # Max export timeout (ms)
export OTEL_BSP_MAX_EXPORT_BATCH_SIZE=10000  # Avoid >32MB payloads
export OTEL_BSP_MAX_QUEUE_SIZE=30000         # Adjust for available memory
export OTEL_BSP_MAX_CONCURRENT_EXPORTS=2     # Parallel exports

Exporting Traces

Here is how you can configure the OTLP/gRPC spans exporter for Uptrace following the recommendations above:

ruby
#!/usr/bin/env ruby
# frozen_string_literal: true

require 'bundler/setup'
require 'opentelemetry/sdk'
require 'opentelemetry/exporter/otlp'
require 'opentelemetry-propagator-xray'
require 'opentelemetry/instrumentation/all'

# Fetch Uptrace DSN from environment (required)
dsn = ENV.fetch('UPTRACE_DSN', nil)
abort('Missing UPTRACE_DSN environment variable') unless dsn

puts "Using Uptrace DSN: #{dsn}"

# Configure OTLP exporter to send data to Uptrace
exporter = OpenTelemetry::Exporter::OTLP::Exporter.new(
  endpoint: 'https://api.uptrace.dev/v1/traces',
  headers: { 'uptrace-dsn': dsn }, # Uptrace authentication
  compression: 'gzip'
)

# Use a batch span processor for better performance
span_processor = OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
  exporter,
  max_queue_size: 1000,
  max_export_batch_size: 512 # smaller batch size helps avoid large payloads
)

# Configure the global OpenTelemetry SDK
OpenTelemetry::SDK.configure do |c|
  c.service_name = 'myservice'         # Customize your service name
  c.service_version = '1.0.0'          # Optional: version for observability
  c.id_generator = OpenTelemetry::Propagator::XRay::IDGenerator # Optional: AWS X-Ray style IDs

  c.add_span_processor(span_processor)

  c.use_all
end

# Get a tracer for your app
tracer = OpenTelemetry.tracer_provider.tracer('my_app_or_gem', '1.0.0')

# Create a sample trace
tracer.in_span('main-operation') do |span|
  puts "Trace ID: #{span.context.hex_trace_id}"
end

# Ensure all spans are flushed before exiting
OpenTelemetry.tracer_provider.shutdown

Exporting Logs

Here is how you can configure the OTLP/gRPC logs exporter for Uptrace following the recommendations above:

ruby
#!/usr/bin/env ruby
# frozen_string_literal: true

require 'rubygems'
require 'bundler/setup'

require 'opentelemetry/sdk'
require 'opentelemetry-exporter-otlp'
require 'opentelemetry-logs-sdk'
require 'opentelemetry/exporter/otlp_logs'

# Ensure DSN is set
dsn = ENV.fetch('UPTRACE_DSN', nil)
abort('Missing UPTRACE_DSN environment variable') unless dsn

# Configure OpenTelemetry (for traces, metrics, and logs if desired)
OpenTelemetry::SDK.configure

# Configure log exporter
log_exporter = OpenTelemetry::Exporter::OTLP::Logs::LogsExporter.new(
  endpoint: 'https://api.uptrace.dev/v1/logs',
  headers: { 'uptrace-dsn': dsn },  # Uptrace auth
  compression: 'gzip',              # Reduce bandwidth
  timeout: 10 # Seconds
)

# Attach batch processor (buffers + exports logs)
processor = OpenTelemetry::SDK::Logs::Export::BatchLogRecordProcessor.new(log_exporter)
OpenTelemetry.logger_provider.add_log_record_processor(processor)

# Ensure we flush logs on shutdown
at_exit { OpenTelemetry.logger_provider.shutdown }

# Create a logger (can be reused globally)
LOGGER = OpenTelemetry.logger_provider.logger(name: 'my_app', version: '1.0.0')

# Helper for structured logging
def log_info(message, attrs = {})
  LOGGER.on_emit(
    timestamp: Time.now.utc,
    severity_text: 'INFO',
    body: message,
    attributes: attrs
  )
end

# Example usage
log_info('Application started', service: 'user-service', region: 'eu-west-1')
log_info('Processing completed', status: 'success')

Exporting Metrics

Here is how you can configure the OTLP/gRPC metrics exporter for Uptrace following the recommendations above:

ruby
#!/usr/bin/env ruby
# frozen_string_literal: true

require 'rubygems'
require 'bundler/setup'

require 'opentelemetry/sdk'
require 'opentelemetry/exporter/otlp'

require 'opentelemetry-metrics-sdk'
require 'opentelemetry-exporter-otlp-metrics'

# Fetch Uptrace DSN from environment (required)
dsn = ENV.fetch('UPTRACE_DSN', nil)
abort('Missing UPTRACE_DSN environment variable') unless dsn

puts "Using Uptrace DSN: #{dsn}"

# Configure the OTLP metrics exporter
metric_exporter = OpenTelemetry::Exporter::OTLP::Metrics::MetricsExporter.new(
  endpoint: 'https://api.uptrace.dev/v1/metrics',
  headers: { 'uptrace-dsn': dsn }, # Uptrace authentication
  compression: 'gzip'
)

# Periodic reader pushes metrics every 5 seconds
metric_reader = OpenTelemetry::SDK::Metrics::Export::PeriodicMetricReader.new(
  exporter: metric_exporter,
  export_interval_millis: 5_000,
  export_timeout_millis: 10_000
)

# Initialize the SDK with the custom metric reader
OpenTelemetry::SDK.configure do |c|
  c.add_metric_reader(metric_reader)
end

# Obtain a Meter instance
meter = OpenTelemetry.meter_provider.meter('example-meter')

# Create a histogram instrument
histogram = meter.create_histogram(
  'example_histogram',
  unit: 'items',
  description: 'Example histogram metric'
)

trap('INT') do
  puts "\nShutting down..."
  OpenTelemetry.meter_provider.shutdown
  exit
end

# Record some metric values periodically
loop do
  value = rand(100..200)
  puts "Recording histogram value: #{value}"
  histogram.record(value, attributes: { 'env' => 'dev', 'feature' => 'demo' })
  sleep 1
end

Troubleshooting

Common Issues

Missing traces:

  • Check that UPTRACE_DSN is set correctly
  • Verify network connectivity to api.uptrace.dev:4317
  • Ensure spans are being sampled (check sampling configuration)

SSL/Network errors:

  • Verify firewall allows outbound connections on port 4317
  • Check if corporate proxy is interfering with HTTPS connections

Performance issues:

  • Reduce sampling rate in high-traffic environments
  • Tune batch processor settings (max_queue_size, max_export_batch_size)

Debug Mode

Enable debug logging to troubleshoot issues:

ruby
require 'logger'
require 'uptrace'
require 'opentelemetry/instrumentation/all'

# Enable debug logging for OpenTelemetry
OpenTelemetry.logger = Logger.new(STDOUT)
OpenTelemetry.logger.level = Logger::DEBUG

Uptrace.configure_opentelemetry(dsn: dsn) do |c|
  c.service_name = 'debug-service'
  c.use_all()
end

Health Checks

Implement health checks to monitor telemetry:

ruby
require 'opentelemetry'

def check_telemetry_health
  """Check if telemetry is working correctly"""
  tracer = OpenTelemetry.tracer_provider.tracer('health-check')

  begin
    tracer.in_span('health-check') do |span|
      span.set_attribute('health.status', 'ok')
      span.set_attribute('check.timestamp', Time.now.to_f)

      # Check if span is recording
      unless span.recording?
        return [false, 'Span not recording']
      end

      [true, 'Telemetry is healthy']
    end
  rescue StandardError => e
    [false, "Telemetry check failed: #{e.message}"]
  end
end

# Use in your application
healthy, message = check_telemetry_health
puts "Telemetry health check: #{message}" unless healthy

Checking Instrumentation Status

Verify which instrumentations are active:

ruby
# Check loaded instrumentations
OpenTelemetry.instrumentation_registry.each do |name, instrumentation|
  puts "#{name}: #{instrumentation.enabled? ? 'enabled' : 'disabled'}"
end

What's Next?

Next, instrument more operations to get a more detailed picture. Try to prioritize network calls, disk operations, database queries, errors, and logs.

Now that you have basic tracing working, explore these guides to get more from OpenTelemetry Ruby:

Advanced Configuration:

  • Propagation - Context propagation across services
  • Sampling - Configure sampling for high-traffic applications

Framework-Specific Guides: