OpenTelemetry Ruby Tracing API

Installation

OpenTelemetry-Ruby is the Ruby implementation of OpenTelemetry. It provides the OpenTelemetry Ruby API, which you can use to instrument your application with OpenTelemetry traces.

Install the required packages:

bash
gem install opentelemetry-api opentelemetry-sdk

Or add to your Gemfile:

ruby
gem 'opentelemetry-api'
gem 'opentelemetry-sdk'

Quickstart

Step 1. Let's instrument the following function:

ruby
def create_user(name:, email:)
  User.create(name: name, email: email)
end

Step 2. Wrap the operation with a span:

ruby
require 'opentelemetry'

tracer = OpenTelemetry.tracer_provider.tracer('my_app_or_gem', '1.0.0')

def create_user(name:, email:)
  tracer.in_span('create-user') do |span|
    User.create(name: name, email: email)
  end
end

Step 3. Record exceptions and set status code:

ruby
def create_user(name:, email:)
  tracer.in_span('create-user') do |span|
    begin
      User.create(name: name, email: email)
    rescue StandardError => e
      span.record_exception(e)
      span.set_status(OpenTelemetry::Trace::Status.error(e.message))
      raise  # Re-raise the exception
    end
  end
end

Step 4. Record contextual information with attributes:

ruby
def create_user(name:, email:, user_type: 'regular')
  tracer.in_span('create-user') do |span|
    # Check if span is being recorded to avoid unnecessary work
    if span.recording?
      span.set_attribute('user.name', name)
      span.set_attribute('user.email', email)
      span.set_attribute('user.type', user_type)
    end

    begin
      result = User.create(name: name, email: email, user_type: user_type)

      if span.recording?
        span.set_attribute('operation.result', 'success')
        span.set_attribute('user.created_id', result.id)
      end

      result
    rescue StandardError => e
      span.record_exception(e)
      span.set_status(OpenTelemetry::Trace::Status.error(e.message))
      raise
    end
  end
end

That's it! The operation is now fully instrumented with proper error handling and contextual information.

Tracer

To start creating spans, you need a tracer. You can create a tracer by providing the name and version of the library or application doing the instrumentation:

ruby
require 'opentelemetry'

tracer = OpenTelemetry.tracer_provider.tracer('my_app_or_gem', '1.0.0')

You can have as many tracers as you want, but usually you need only one tracer per application or library. Later, you can use tracer names to identify the instrumentation that produces the spans.

Tracer Configuration

When creating a tracer, you can provide additional metadata:

ruby
tracer = OpenTelemetry.tracer_provider.tracer(
  'my_app_or_gem',                    # Instrumentation name
  '1.0.0',                           # Instrumentation version
  schema_url: 'https://example.com/schema'  # Schema URL (optional)
)

Global Tracer Pattern

For convenience, you can create a global tracer instance:

ruby
# In your application initializer or main file
module MyApp
  TRACER = OpenTelemetry.tracer_provider.tracer('my_app', '1.0.0')
end

# Use throughout your application
def some_operation
  MyApp::TRACER.in_span('some-operation') do |span|
    # Your logic here
  end
end

Creating Spans

Once you have a tracer, creating spans is straightforward:

ruby
require 'opentelemetry'

# Create a span with name "operation-name" and kind="server"
tracer.in_span('operation-name', kind: :server) do |span|
  begin
    do_some_work
  rescue StandardError => e
    span.record_exception(e)
    span.set_status(OpenTelemetry::Trace::Status.error(e.message))
    raise
  end
end

Span Kinds

Specify the type of span using span kinds:

ruby
# For incoming requests (server-side)
tracer.in_span('handle-request', kind: :server) do |span|
  # Handle incoming HTTP request
end

# For outgoing requests (client-side)
tracer.in_span('http-request', kind: :client) do |span|
  # Make HTTP request to external service
end

# For async operations (producer/consumer)
tracer.in_span('publish-message', kind: :producer) do |span|
  # Publish message to queue
end

tracer.in_span('consume-message', kind: :consumer) do |span|
  # Process message from queue
end

# For internal operations (default)
tracer.in_span('internal-operation', kind: :internal) do |span|
  # Internal business logic
end

Span Configuration Options

Configure spans with additional options:

ruby
# Create span with custom configuration
tracer.in_span(
  'complex-operation',
  kind: :internal,
  attributes: { 'operation.type' => 'batch' },  # Set initial attributes
  links: [span_link],                          # Link to other spans
  start_timestamp: start_time                  # Custom start time (optional)
) do |span|
  # Your operation logic
end

Manual Span Management

For advanced use cases, you can manually manage span lifecycle:

ruby
# Start span manually
span = tracer.start_span('manual-operation', kind: :client)

# Set span as current
OpenTelemetry::Trace.with_span(span) do
  begin
    # Your operation
    do_work
    span.set_status(OpenTelemetry::Trace::Status.ok)
  rescue StandardError => e
    span.record_exception(e)
    span.set_status(OpenTelemetry::Trace::Status.error(e.message))
    raise
  ensure
    # Always finish the span
    span.finish
  end
end

Adding Span Attributes

To record contextual information, you can annotate spans with attributes. For example, an HTTP endpoint may have attributes such as http.method = GET and http.route = /projects/:id.

ruby
tracer.in_span('http-request') do |span|
  # Check if span is being recorded to avoid expensive computations
  if span.recording?
    span.set_attribute('http.method', 'GET')
    span.set_attribute('http.route', '/projects/:id')
    span.set_attribute('http.status_code', 200)
    span.set_attribute('user.id', user_id)
  end
end

Setting Multiple Attributes

You can set multiple attributes efficiently:

ruby
span.add_attributes({
  'http.method' => 'POST',
  'http.route' => '/api/users',
  'http.status_code' => 201,
  'user.role' => 'admin',
  'request.size' => request_body.length
})

Semantic Conventions

Use semantic conventions for consistent attribute naming:

ruby
require 'opentelemetry/semantic_conventions'

tracer.in_span('http-request') do |span|
  if span.recording?
    span.add_attributes({
      OpenTelemetry::SemanticConventions::Trace::HTTP_METHOD => 'GET',
      OpenTelemetry::SemanticConventions::Trace::HTTP_ROUTE => '/projects/:id',
      OpenTelemetry::SemanticConventions::Trace::HTTP_STATUS_CODE => 200,
      OpenTelemetry::SemanticConventions::Trace::USER_ID => user_id
    })
  end
end

Adding Span Events

You can annotate spans with events that represent significant moments during the span's lifetime:

ruby
tracer.in_span('file-processing') do |span|
  # Simple event
  span.add_event('cache.miss')

  # Event with attributes
  span.add_event('user.login', {
    'user.id' => '12345',
    'user.role' => 'admin',
    'login.method' => 'oauth'
  })

  # Event with timestamp
  span.add_event(
    'database.query.start',
    {
      'db.statement' => 'SELECT * FROM users WHERE id = ?',
      'db.operation' => 'SELECT'
    },
    timestamp: Time.now.to_f * 1_000_000_000  # nanoseconds
  )
end

Logging with Events

Events are particularly useful for structured logging:

ruby
def process_order(order_id)
  tracer.in_span('process-order') do |span|
    span.add_event('log', {
      'log.severity' => 'info',
      'log.message' => 'Order processing started',
      'order.id' => order_id
    })

    begin
      order = Order.find(order_id)

      span.add_event('log', {
        'log.severity' => 'info',
        'log.message' => 'Order found and validated',
        'order.status' => order.status
      })

      # Process order...

    rescue ActiveRecord::RecordNotFound => e
      span.add_event('log', {
        'log.severity' => 'error',
        'log.message' => 'Order not found',
        'order.id' => order_id,
        'error.type' => 'RecordNotFound'
      })
      raise
    end
  end
end

Setting Span Status

Use status codes to indicate the outcome of operations:

ruby
tracer.in_span('database-operation') do |span|
  begin
    result = perform_database_operation

    # Successful operation (default is UNSET)
    span.set_status(OpenTelemetry::Trace::Status.ok)
    result
  rescue ActiveRecord::ConnectionTimeoutError => e
    # Operation with error
    span.set_status(OpenTelemetry::Trace::Status.error('Database connection timeout'))
    span.record_exception(e)
    raise
  rescue StandardError => e
    # Generic error
    span.set_status(OpenTelemetry::Trace::Status.error(e.message))
    span.record_exception(e)
    raise
  end
end

Status Codes

OpenTelemetry defines three status codes:

ruby
# UNSET (default) - The operation completed without known error
span.set_status(OpenTelemetry::Trace::Status.unset)

# OK - The operation completed successfully
span.set_status(OpenTelemetry::Trace::Status.ok)

# ERROR - The operation failed
span.set_status(OpenTelemetry::Trace::Status.error('Something went wrong'))

Recording Exceptions

OpenTelemetry provides a convenient method to record exceptions:

ruby
def risky_operation
  tracer.in_span('risky-operation') do |span|
    begin
      perform_risky_work
    rescue StandardError => e
      span.record_exception(e)
      span.set_status(OpenTelemetry::Trace::Status.error(e.message))
      raise  # Re-raise if needed
    end
  end
end

Exception with Additional Context

ruby
def connect_to_database(host, port)
  tracer.in_span('database-connection') do |span|
    retry_count = 0

    begin
      establish_connection(host, port)
    rescue ConnectionError => e
      retry_count += 1

      span.record_exception(e, attributes: {
        'db.host' => host,
        'db.port' => port,
        'retry.count' => retry_count,
        'connection.timeout' => 30
      })

      span.set_status(OpenTelemetry::Trace::Status.error('Database connection failed'))
      raise
    end
  end
end

Current Span and Context

OpenTelemetry stores the active span in a context that is automatically managed. You can access the current span from anywhere in your application.

Getting the Current Span

ruby
require 'opentelemetry'

def add_user_info_to_span(user)
  # Get the currently active span
  current_span = OpenTelemetry::Trace.current_span

  if current_span.recording?
    current_span.add_attributes({
      'user.id' => user.id,
      'user.email' => user.email,
      'user.role' => user.role
    })
  end
end

# Usage within an existing span
tracer.in_span('user-operation') do |span|
  user = User.find(user_id)
  add_user_info_to_span(user)  # This will add attributes to the current span

  # perform user operation...
end

Context Nesting

Contexts are automatically nested when using in_span:

ruby
# Parent span
tracer.in_span('parent-operation') do |parent_span|
  parent_span.set_attribute('operation.type', 'parent')

  # Child span - automatically becomes a child of the parent
  tracer.in_span('child-operation') do |child_span|
    child_span.set_attribute('operation.type', 'child')

    # Both spans are properly nested
    puts "Parent: #{parent_span.context.hex_span_id}"
    puts "Child: #{child_span.context.hex_span_id}"
    puts "Child parent: #{child_span.context.hex_trace_id}"
  end
end

Manual Context Management

For advanced scenarios, you can manually manage context:

ruby
# Create span without auto-activation
span = tracer.start_span('manual-span')

# Manually set as current span
OpenTelemetry::Trace.with_span(span) do
  # Span is now active within this block
  current = OpenTelemetry::Trace.current_span
  current.set_attribute('manual', true)

  do_work
end

# Always finish manually created spans
span.finish

Best Practices

Error Handling Pattern

Use consistent error handling patterns:

ruby
def service_operation(params)
  tracer.in_span('service-operation') do |span|
    if span.recording?
      span.add_attributes({
        'operation.type' => 'service_call',
        'params.count' => params.length
      })
    end

    begin
      result = perform_operation(params)

      span.set_status(OpenTelemetry::Trace::Status.ok)
      if span.recording?
        span.set_attribute('operation.result', 'success')
        span.set_attribute('result.count', result.length)
      end

      result
    rescue StandardError => e
      span.record_exception(e)
      span.set_status(OpenTelemetry::Trace::Status.error(e.message))

      if span.recording?
        span.set_attribute('operation.result', 'failure')
        span.set_attribute('error.type', e.class.name)
      end

      raise
    end
  end
end

Context Manager Helper

Create a helper module for automatic span management:

ruby
module TracingHelper
  def with_traced_operation(name, **attributes)
    tracer = OpenTelemetry.tracer_provider.tracer(self.class.name, '1.0.0')

    tracer.in_span(name, attributes: attributes) do |span|
      begin
        result = yield(span)
        span.set_status(OpenTelemetry::Trace::Status.ok)
        result
      rescue StandardError => e
        span.record_exception(e)
        span.set_status(OpenTelemetry::Trace::Status.error(e.message))
        raise
      end
    end
  end
end

# Usage
class UserService
  include TracingHelper

  def create_user(params)
    with_traced_operation('create-user', operation: 'create') do |span|
      user = User.create!(params)
      span.set_attribute('user.id', user.id) if span.recording?
      user
    end
  end
end

Attribute Optimization

Only set attributes when the span is being recorded:

ruby
tracer.in_span('expensive-operation') do |span|
  # Only do expensive operations if the span is being recorded
  if span.recording?
    expensive_attribute = calculate_expensive_value
    span.set_attribute('expensive.attribute', expensive_attribute)
  end

  # Always do the main work
  perform_operation
end

Span Naming

Use descriptive, low-cardinality span names:

ruby
# Good: Low cardinality
tracer.in_span('GET /users/:id') do |span|
  span.set_attribute('http.route', '/users/:id')
  span.set_attribute('user.id', user_id)
end

# Bad: High cardinality
tracer.in_span("GET /users/#{user_id}") do |span|
  # This creates a new span name for each user_id
end

Integration Examples

HTTP Client Instrumentation

ruby
require 'net/http'
require 'opentelemetry/semantic_conventions'

class HttpClient
  include OpenTelemetry::SemanticConventions::Trace

  def initialize
    @tracer = OpenTelemetry.tracer_provider.tracer('http_client', '1.0.0')
  end

  def get(url, headers = {})
    uri = URI(url)

    @tracer.in_span("HTTP GET", kind: :client) do |span|
      if span.recording?
        span.add_attributes({
          HTTP_METHOD => 'GET',
          HTTP_URL => url,
          HTTP_SCHEME => uri.scheme,
          HTTP_HOST => uri.host,
          HTTP_TARGET => uri.path
        })
      end

      begin
        # Inject trace context into outgoing request
        OpenTelemetry.propagation.inject(headers)

        # Make HTTP request
        response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
          request = Net::HTTP::Get.new(uri)
          headers.each { |key, value| request[key] = value }
          http.request(request)
        end

        if span.recording?
          span.add_attributes({
            HTTP_STATUS_CODE => response.code.to_i,
            HTTP_RESPONSE_SIZE => response.body&.length || 0
          })
        end

        # Set status based on HTTP status code
        if response.code.to_i >= 400
          span.set_status(OpenTelemetry::Trace::Status.error("HTTP #{response.code}"))
        else
          span.set_status(OpenTelemetry::Trace::Status.ok)
        end

        response
      rescue StandardError => e
        span.record_exception(e)
        span.set_status(OpenTelemetry::Trace::Status.error('HTTP request failed'))
        raise
      end
    end
  end
end

# Usage
client = HttpClient.new
response = client.get('https://api.example.com/users')

Database Query Instrumentation

ruby
require 'pg'
require 'opentelemetry/semantic_conventions'

class DatabaseClient
  include OpenTelemetry::SemanticConventions::Trace

  def initialize(connection_params)
    @connection_params = connection_params
    @tracer = OpenTelemetry.tracer_provider.tracer('database_client', '1.0.0')
  end

  def execute_query(sql, params = [])
    @tracer.in_span('db.query', kind: :client) do |span|
      if span.recording?
        span.add_attributes({
          DB_SYSTEM => 'postgresql',
          DB_STATEMENT => sql,
          DB_NAME => @connection_params[:dbname],
          DB_USER => @connection_params[:user],
          DB_CONNECTION_STRING => connection_string_for_tracing
        })
      end

      start_time = Time.now

      begin
        conn = PG.connect(@connection_params)

        span.add_event('db.connection.established')

        result = if params.any?
                  conn.exec_params(sql, params)
                else
                  conn.exec(sql)
                end

        duration = Time.now - start_time

        if span.recording?
          span.add_attributes({
            DB_ROWS_AFFECTED => result.ntuples,
            'db.duration' => duration
          })
        end

        span.add_event('db.query.completed', {
          'db.rows_returned' => result.ntuples
        })

        span.set_status(OpenTelemetry::Trace::Status.ok)
        result
      rescue PG::Error => e
        span.record_exception(e)
        span.set_status(OpenTelemetry::Trace::Status.error("Database error: #{e.message}"))
        raise
      ensure
        conn&.close
        span.add_event('db.connection.closed')
      end
    end
  end

  private

  def connection_string_for_tracing
    # Return safe connection string without password
    "postgresql://#{@connection_params[:user]}@#{@connection_params[:host]}:#{@connection_params[:port]}/#{@connection_params[:dbname]}"
  end
end

# Usage
db = DatabaseClient.new({
  host: 'localhost',
  port: 5432,
  dbname: 'myapp',
  user: 'myuser',
  password: 'mypassword'
})

result = db.execute_query('SELECT * FROM users WHERE id = $1', [123])

Background Job Instrumentation

ruby
# Sidekiq job with tracing
class UserNotificationJob
  include Sidekiq::Job

  def perform(user_id, notification_type)
    tracer = OpenTelemetry.tracer_provider.tracer('background_jobs', '1.0.0')

    tracer.in_span('process-notification', kind: :consumer) do |span|
      if span.recording?
        span.add_attributes({
          'job.type' => 'user_notification',
          'user.id' => user_id,
          'notification.type' => notification_type,
          'job.queue' => 'default'
        })
      end

      begin
        user = User.find(user_id)

        span.add_event('user.found', {
          'user.email' => user.email,
          'user.status' => user.status
        })

        case notification_type
        when 'email'
          send_email_notification(user, span)
        when 'sms'
          send_sms_notification(user, span)
        else
          raise ArgumentError, "Unknown notification type: #{notification_type}"
        end

        span.set_status(OpenTelemetry::Trace::Status.ok)

      rescue ActiveRecord::RecordNotFound => e
        span.record_exception(e)
        span.set_status(OpenTelemetry::Trace::Status.error('User not found'))
        raise
      rescue StandardError => e
        span.record_exception(e)
        span.set_status(OpenTelemetry::Trace::Status.error(e.message))
        raise
      end
    end
  end

  private

  def send_email_notification(user, parent_span)
    tracer = OpenTelemetry.tracer_provider.tracer('email_service', '1.0.0')

    tracer.in_span('send-email', kind: :client) do |span|
      if span.recording?
        span.add_attributes({
          'email.to' => user.email,
          'email.provider' => 'sendgrid'
        })
      end

      # Send email logic
      EmailService.send_notification(user)

      span.add_event('email.sent')
      span.set_status(OpenTelemetry::Trace::Status.ok)
    end
  end
end

# Usage
UserNotificationJob.perform_async(123, 'email')

Rails Controller Instrumentation

ruby
class UsersController < ApplicationController
  before_action :setup_tracing

  def show
    @tracer.in_span('users#show', kind: :server) do |span|
      if span.recording?
        span.add_attributes({
          'http.route' => '/users/:id',
          'user.id' => params[:id],
          'request.format' => request.format.to_s
        })
      end

      begin
        @user = User.find(params[:id])

        span.add_event('user.found', {
          'user.status' => @user.status,
          'user.created_at' => @user.created_at.iso8601
        })

        render json: @user

      rescue ActiveRecord::RecordNotFound => e
        span.record_exception(e)
        span.set_status(OpenTelemetry::Trace::Status.error('User not found'))

        if span.recording?
          span.set_attribute('http.status_code', 404)
        end

        render json: { error: 'User not found' }, status: :not_found
      end
    end
  end

  private

  def setup_tracing
    @tracer = OpenTelemetry.tracer_provider.tracer('rails_controllers', '1.0.0')
  end
end

Performance Considerations

Conditional Instrumentation

For high-performance scenarios, consider conditional instrumentation:

ruby
class PerformanceCriticalService
  def initialize(enable_tracing: true)
    @enable_tracing = enable_tracing
    @tracer = OpenTelemetry.tracer_provider.tracer('critical_service', '1.0.0') if enable_tracing
  end

  def perform_operation
    if @enable_tracing
      @tracer.in_span('critical-operation') do |span|
        perform_work
      end
    else
      perform_work
    end
  end

  private

  def perform_work
    # Your actual business logic
  end
end

# Usage with conditional tracing based on environment
service = PerformanceCriticalService.new(
  enable_tracing: Rails.env.development? || Rails.env.staging?
)

Sampling Awareness

Check if spans are being sampled to avoid unnecessary work:

ruby
tracer.in_span('operation') do |span|
  # Only do expensive operations if the span is being recorded
  if span.recording?
    expensive_data = calculate_expensive_metrics
    span.set_attribute('expensive.data', expensive_data)
  end

  # Always do the main work
  perform_main_operation
end

Batch Attribute Setting

Set multiple attributes efficiently:

ruby
# Efficient: Set all attributes at once
attributes = {
  'http.method' => 'GET',
  'http.route' => '/api/users',
  'http.status_code' => 200,
  'user.id' => user_id,
  'user.role' => user_role
}

tracer.in_span('http-request') do |span|
  if span.recording?
    span.add_attributes(attributes)
  end

  # Process request...
end

Memory Management

For long-running applications, be mindful of memory usage:

ruby
# Avoid holding references to spans after they complete
def process_batch(items)
  tracer.in_span('batch-processing') do |span|
    span.set_attribute('batch.size', items.length) if span.recording?

    items.each_with_index do |item, index|
      # Create child spans for individual items
      tracer.in_span('process-item') do |item_span|
        if item_span.recording?
          item_span.set_attribute('item.index', index)
          item_span.set_attribute('item.id', item.id)
        end

        process_single_item(item)
      end
      # Span is automatically finished and can be garbage collected
    end
  end
end

Auto-instrumentation

OpenTelemetry Ruby allows you to automatically instrument Ruby applications using various instrumentation gems.

Installation

First, you need to install the instrumentation gems for the libraries you want to automatically instrument:

bash
# Install specific instrumentations
gem install opentelemetry-instrumentation-rails
gem install opentelemetry-instrumentation-net_http
gem install opentelemetry-instrumentation-faraday
gem install opentelemetry-instrumentation-redis
gem install opentelemetry-instrumentation-pg

# Or install all available instrumentations
gem install opentelemetry-instrumentation-all

Add to your Gemfile:

ruby
gem 'opentelemetry-instrumentation-all'
# or specific gems
gem 'opentelemetry-instrumentation-rails'
gem 'opentelemetry-instrumentation-net_http'

Supported Libraries

Auto-instrumentation is available for:

  • Web frameworks: Rails, Sinatra, Rack
  • HTTP clients: Net::HTTP, Faraday, RestClient, HTTPClient
  • Databases: ActiveRecord, Redis, PG (PostgreSQL), MySQL2, MongoDB
  • Background jobs: Sidekiq, DelayedJob
  • Other libraries: GraphQL, Bunny (RabbitMQ), Elasticsearch

Enabling Auto-Instrumentation

Enable all available instrumentations:

ruby
require 'opentelemetry/sdk'
require 'opentelemetry/instrumentation/all'

OpenTelemetry::SDK.configure do |c|
  c.service_name = 'my-ruby-app'
  c.service_version = '1.0.0'

  c.use_all() # enables all available instrumentation
end

Selective Auto-Instrumentation

Enable specific instrumentations with configuration:

ruby
OpenTelemetry::SDK.configure do |c|
  c.service_name = 'my-app'

  # Enable Rails instrumentation
  c.use 'OpenTelemetry::Instrumentation::Rails'

  # Enable HTTP client instrumentation
  c.use 'OpenTelemetry::Instrumentation::Net::HTTP'
  c.use 'OpenTelemetry::Instrumentation::Faraday'

  # Enable database instrumentation with configuration
  c.use 'OpenTelemetry::Instrumentation::PG', {
    # Configure database statement handling
    db_statement: :obfuscate  # Options: :include, :omit, :obfuscate
  }

  # Enable Redis with custom configuration
  c.use 'OpenTelemetry::Instrumentation::Redis', {
    peer_service: 'redis-cache'
  }
end

Environment Variable Control

Disable specific instrumentations using environment variables:

bash
# Disable specific instrumentations
export OTEL_RUBY_INSTRUMENTATION_RAILS_ENABLED=false
export OTEL_RUBY_INSTRUMENTATION_REDIS_ENABLED=false

# Configure instrumentation options
export OTEL_RUBY_INSTRUMENTATION_PG_CONFIG_OPTS="db_statement=obfuscate"

How Auto-instrumentation Works

Auto-instrumentation works by:

  1. Method wrapping: Instrumentations wrap existing methods to add tracing
  2. Middleware injection: Web frameworks get middleware for request tracing
  3. Callback hooks: Libraries use existing callback mechanisms to inject spans
  4. Context propagation: Automatic injection/extraction of trace context

Example of what happens automatically:

ruby
# Your original code
class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
    render json: @user
  end
end

# What auto-instrumentation adds (conceptually)
class UsersController < ApplicationController
  def show
    tracer.in_span('UsersController#show') do |span|
      span.set_attribute('http.route', '/users/:id')

      # Database query is also automatically traced
      @user = User.find(params[:id])  # Creates a 'SELECT' span

      render json: @user
    end
  end
end

OpenTelemetry APM

Uptrace is a OpenTelemetry APM that supports distributed tracing, metrics, and logs. You can use it to monitor applications and troubleshoot issues.

Uptrace Overview

Uptrace comes with an intuitive query builder, rich dashboards, alerting rules with notifications, and integrations for most languages and frameworks.

Uptrace can process billions of spans and metrics on a single server and allows you to monitor your applications at 10x lower cost.

In just a few minutes, you can try Uptrace by visiting the cloud demo (no login required) or running it locally with Docker. The source code is available on GitHub.

What's Next?

Now that you understand the OpenTelemetry Ruby Tracing API, explore these related topics: