SigNoz Cloud - This page is relevant for SigNoz Cloud editions.
Self-Host - This page is relevant for self-hosted SigNoz editions.

Send Metrics from Ruby Application Using OpenTelemetry

This guide shows you how to send metrics from your Ruby application (Rails, Sinatra, Sidekiq) to SigNoz using OpenTelemetry.

⚠️ Warning

The OpenTelemetry Ruby Metrics SDK is currently in Development status (not yet stable). APIs may change in future releases. While it supports synchronous instruments (Counter, UpDownCounter, Histogram) and observable instruments, some features like cumulative temporality, metrics views, and exemplars are not yet implemented. See the official SDK documentation for current status.

Prerequisites

  • Ruby 3.1 or later
  • Bundler installed
  • A SigNoz Cloud account or self-hosted SigNoz instance

Send metrics to SigNoz

Step 1. Set environment variables

Set the following environment variables to configure the OpenTelemetry exporter:

export OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443"
export OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>"
export OTEL_SERVICE_NAME="<service-name>"
export OTEL_METRICS_EXPORTER="otlp"
export OTEL_TRACES_EXPORTER="none"
export OTEL_LOGS_EXPORTER="none"
export OTEL_METRIC_EXPORT_INTERVAL="60000"

Verify these values:

  • <region>: Your SigNoz Cloud region
  • <your-ingestion-key>: Your SigNoz ingestion key.
  • <service-name>: A descriptive name for your service (e.g., payment-service).
  • OTEL_METRICS_EXPORTER: Set to otlp to export metrics to SigNoz.
  • OTEL_METRIC_EXPORT_INTERVAL: How often to export metrics in milliseconds (default: 60000 for 60 seconds).

Step 2. Install OpenTelemetry packages

Add the following gems to your Gemfile:

Gemfile
gem 'opentelemetry-sdk'
gem 'opentelemetry-metrics-api'
gem 'opentelemetry-metrics-sdk'
gem 'opentelemetry-exporter-otlp-metrics'

Run bundle install:

bundle install

Step 3. Configure OpenTelemetry SDK

Create an initializer file to configure the SDK:

config/initializers/opentelemetry.rb
require 'opentelemetry/sdk'
require 'opentelemetry-metrics-sdk'
require 'opentelemetry/exporter/otlp_metrics'

# Configure the SDK
OpenTelemetry::SDK.configure do |c|
  c.service_name = ENV['OTEL_SERVICE_NAME'] || 'my-ruby-service'
end

With OTEL_METRICS_EXPORTER=otlp, the SDK uses the OTLP metrics exporter configured by OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_EXPORTER_OTLP_HEADERS. Metrics are exported periodically based on OTEL_METRIC_EXPORT_INTERVAL.

Step 4. Add custom metrics

Add custom metrics to your application using OpenTelemetry.meter_provider.meter:

app.rb
require_relative 'config/initializers/opentelemetry'

# Get a meter - MeterProvider is configured by the initializer
METER = OpenTelemetry.meter_provider.meter('my.meter.name')

# Create a counter instrument
REQUEST_COUNTER = METER.create_counter(
  'requests.total',
  unit: '1',
  description: 'Total number of requests'
)

def handle_request(endpoint)
  # Record the metric with attributes
  REQUEST_COUNTER.add(1, attributes: { 'endpoint' => endpoint })
  # ... handle the request ...
end
Info

This example shows a Counter, which only increases. OpenTelemetry supports other metric types like UpDownCounter, Histogram, and Observable Gauge. See Custom Metrics Examples for complete examples of each type.

Step 5. Run your application

Run your application normally:

ruby app.rb

For Rails applications:

rails server
⚠️ Warning

Before your application exits, ensure any remaining metrics are flushed by shutting down the meter provider:

at_exit do
  OpenTelemetry.meter_provider.shutdown
end

Validate

Once you have configured your application to start sending metrics to SigNoz, you can start visualizing the metrics in the metrics explorer.

Info

Want more metrics? For system-level metrics like CPU, memory, and thread counts, see Runtime Metrics.

Custom Metrics Examples

OpenTelemetry supports six metric instrument types. Here's a complete example demonstrating all of them:

Synchronous Instruments

Synchronous instruments are called inline with your application code.

Counter - A value that only increases (e.g., total requests, bytes sent):

counter = meter.create_counter(
  'requests.total',
  unit: '1',
  description: 'Total number of requests'
)

# Record a value
counter.add(1, attributes: { 'endpoint' => '/api/users' })

UpDownCounter - A value that can increase or decrease (e.g., active connections, queue size):

updown_counter = meter.create_up_down_counter(
  'connections.active',
  unit: '1',
  description: 'Number of active connections'
)

# Increment
updown_counter.add(1)

# Decrement
updown_counter.add(-1)

Histogram - A distribution of values (e.g., request latency, response sizes):

histogram = meter.create_histogram(
  'request.duration',
  unit: 'ms',
  description: 'Request duration in milliseconds'
)

# Record a value
histogram.record(45.5, attributes: { 'endpoint' => '/api/users' })

Asynchronous Instruments

Asynchronous instruments use callback procs that are invoked during metric collection. The callback must return a numeric value that will be recorded.

Info

In some other OpenTelemetry SDKs, observable callbacks receive an observer object and call observer.observe(value, attributes) to record values. Ruby's SDK uses a simpler model: the callback proc returns a numeric value directly, and the SDK records it automatically.

ObservableCounter - Observes a monotonically increasing value (e.g., CPU time, page faults):

cpu_callback = proc do
  # Return the CPU time in seconds
  Process.times.utime + Process.times.stime
end

cpu_counter = meter.create_observable_counter(
  'system.cpu.time',
  callback: cpu_callback,
  unit: 's',
  description: 'CPU time'
)

ObservableUpDownCounter - Observes a value that can go up or down (e.g., memory usage, thread count):

thread_callback = proc do
  # Return the current thread count
  Thread.list.count
end

meter.create_observable_up_down_counter(
  'process.threads.count',
  callback: thread_callback,
  unit: '1',
  description: 'Current thread count'
)

ObservableGauge - Observes a point-in-time value (e.g., temperature, current memory usage):

require 'get_process_mem'

process_mem = GetProcessMem.new
memory_callback = proc do
  # Return resident set size in bytes (cross-platform, no shell command)
  process_mem.bytes.to_i
end

meter.create_observable_gauge(
  'process.memory.usage',
  callback: memory_callback,
  unit: 'By',
  description: 'Current memory usage in bytes'
)

Runtime Metrics

Ruby does not have a built-in runtime metrics package like Python or Node.js. However, you can collect system and process metrics using observable instruments.

Create a Runtime Metrics Helper

Install an additional gem for cross-platform process memory:

Gemfile
gem 'get_process_mem'

Create a helper module to collect common runtime metrics:

lib/runtime_metrics.rb
require 'get_process_mem'

module RuntimeMetrics
  def self.setup(meter)
    # Memory usage (returns bytes)
    process_mem = GetProcessMem.new
    memory_callback = proc do
      process_mem.bytes.to_i
    end
    meter.create_observable_gauge(
      'process.memory.usage',
      callback: memory_callback,
      unit: 'By',
      description: 'Process memory usage in bytes'
    )

    # Thread count
    thread_callback = proc { Thread.list.count }
    meter.create_observable_up_down_counter(
      'process.threads.count',
      callback: thread_callback,
      unit: '1',
      description: 'Number of threads in the process'
    )

    # CPU time (user mode)
    cpu_user_callback = proc { Process.times.utime }
    meter.create_observable_counter(
      'process.cpu.time.user',
      callback: cpu_user_callback,
      unit: 's',
      description: 'Process CPU time in user mode'
    )

    # CPU time (system mode)
    cpu_system_callback = proc { Process.times.stime }
    meter.create_observable_counter(
      'process.cpu.time.system',
      callback: cpu_system_callback,
      unit: 's',
      description: 'Process CPU time in system mode'
    )

    # GC stats (Ruby specific)
    gc_count_callback = proc { GC.count }
    meter.create_observable_counter(
      'ruby.gc.count',
      callback: gc_count_callback,
      unit: '1',
      description: 'Number of garbage collections'
    )

    heap_callback = proc { GC.stat[:heap_live_slots] }
    meter.create_observable_gauge(
      'ruby.gc.heap_live_slots',
      callback: heap_callback,
      unit: '1',
      description: 'Number of live objects in the heap'
    )
  end
end

Usage

Enable runtime metrics in your initializer:

config/initializers/opentelemetry.rb
require 'opentelemetry/sdk'
require 'opentelemetry-metrics-sdk'
require 'opentelemetry/exporter/otlp_metrics'
require_relative '../../lib/runtime_metrics'

OpenTelemetry::SDK.configure do |c|
  c.service_name = ENV['OTEL_SERVICE_NAME'] || 'my-ruby-service'
end

# Enable runtime metrics
meter = OpenTelemetry.meter_provider.meter('runtime')
RuntimeMetrics.setup(meter)

Exported Metrics

The runtime metrics helper exports the following metrics:

Process Metrics:

  • process.memory.usage - Process memory usage in bytes (gauge)
  • process.threads.count - Number of threads in the process (up-down counter)
  • process.cpu.time.user - Process CPU time in user mode (counter)
  • process.cpu.time.system - Process CPU time in system mode (counter)

Ruby Runtime Metrics:

  • ruby.gc.count - Total number of garbage collections (counter)
  • ruby.gc.heap_live_slots - Number of live objects in the heap (gauge)

Setup OpenTelemetry Collector (Optional)

What is the OpenTelemetry Collector?

Think of the OTel Collector as a middleman between your app and SigNoz. Instead of your application sending data directly to SigNoz, it sends everything to the Collector first, which then forwards it along.

Why use it?

  • Cleaning up data — Filter out noisy metrics you don't care about, or remove sensitive info before it leaves your servers.
  • Keeping your app lightweight — Let the Collector handle batching, retries, and compression instead of your application code.
  • Adding context automatically — The Collector can tag your data with useful info like which Kubernetes pod or cloud region it came from.
  • Future flexibility — Want to send data to multiple backends later? The Collector makes that easy without changing your app.

See Switch from direct export to Collector for step-by-step instructions to convert your setup.

For more details, see Why use the OpenTelemetry Collector? and the Collector configuration guide.

Troubleshooting

Metrics not appearing?

  1. Check Environment Variables: Ensure OTEL_EXPORTER_OTLP_ENDPOINT is set to the base URL only:

    • https://ingest.<region>.signoz.cloud:443
    • Do not append /v1/metrics. The Ruby OTLP metrics exporter adds this path automatically.
  2. Check Exporter Protocol: The Ruby OTLP metrics exporter uses HTTP by default (port 4318). Ensure your endpoint matches.

  3. Verify Metrics are Exported: The SDK uses a PeriodicMetricReader that exports at the interval set by OTEL_METRIC_EXPORT_INTERVAL. Ensure you call shutdown before your application exits to flush remaining metrics:

    OpenTelemetry.meter_provider.shutdown
    
  4. Enable Debug Logging: Check for errors in your application logs. You can enable verbose output:

    OpenTelemetry.logger = Logger.new(STDOUT)
    OpenTelemetry.logger.level = Logger::DEBUG
    
  5. Verify Package Installation: Ensure all required gems are installed:

    bundle list | grep opentelemetry
    

Authentication errors

If you see errors like "Unauthorized" or "403 Forbidden":

  • Verify your ingestion key is correct in OTEL_EXPORTER_OTLP_HEADERS
  • Ensure the header format is exactly: signoz-ingestion-key=<your-key> (no extra spaces)
  • Check that your ingestion key is active in the SigNoz Cloud dashboard

"Connection Refused" errors

  • If running locally and sending to SigNoz Cloud, check your internet connection and firewall.
  • If sending to a self-hosted collector, ensure the collector is running and listening on port 4317 (gRPC) or 4318 (HTTP).

SSL/TLS errors

If you encounter SSL certificate errors:

  • Ensure you're using https:// in the endpoint URL
  • For self-hosted setups with self-signed certificates, you may need to configure the exporter to trust your CA

Metrics not exported on application exit

Ruby applications may exit before the periodic exporter has a chance to send metrics. Add a shutdown hook:

at_exit do
  OpenTelemetry.meter_provider.shutdown
end

Next Steps

Last updated: March 5, 2026

Edit on GitHub