Expert guidance for configuring and deploying the OpenTelemetry Collector. Use when setting up a Collector pipeline, configuring receivers, exporters, or processors, deploying a Collector to Kubernetes or Docker, or forwarding telemetry to Dash0. Triggers on requests involving collector, pipeline, OTLP receiver, exporter, or Dash0 collector setup.
100
100%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Advisory
Suggest reviewing before use
Instrument Ruby applications to generate traces, logs, and metrics for deep insights into behavior and performance.
bundle add opentelemetry-sdk opentelemetry-instrumentation-all opentelemetry-exporter-otlpNote: Installing the gems alone is insufficient—you must initialize the SDK AND enable exporters.
All environment variables that control the SDK behavior:
| Variable | Required | Default | Description |
|---|---|---|---|
OTEL_SERVICE_NAME | Yes | unknown_service | Identifies your service in telemetry data |
OTEL_TRACES_EXPORTER | Yes | none | Must set to otlp to export traces |
OTEL_METRICS_EXPORTER | No | none | Set to otlp to export metrics |
OTEL_LOGS_EXPORTER | No | none | Set to otlp to export logs |
OTEL_EXPORTER_OTLP_ENDPOINT | Yes | http://localhost:4318 | OTLP collector endpoint |
OTEL_EXPORTER_OTLP_HEADERS | No | - | Headers for authentication (e.g., Authorization=Bearer TOKEN) |
OTEL_EXPORTER_OTLP_PROTOCOL | No | http/protobuf | Protocol: grpc, http/protobuf, or http/json |
OTEL_RESOURCE_ATTRIBUTES | No | - | Additional resource attributes (e.g., deployment.environment=production) |
Critical: Without OTEL_TRACES_EXPORTER=otlp, the SDK defaults to none and no telemetry is exported.
https://<region>.your-platform.comorder-api, checkout-service)The SDK must be initialized in code on startup, before any application or framework code runs.
Rails projects — create config/initializers/opentelemetry.rb:
require 'opentelemetry/sdk'
require 'opentelemetry/instrumentation/all'
OpenTelemetry::SDK.configure do |c|
c.use_all()
endNon-Rails projects — add to your application entry point before any other requires:
require 'opentelemetry/sdk'
require 'opentelemetry/instrumentation/all'
OpenTelemetry::SDK.configure do |c|
c.use_all()
endexport OTEL_SERVICE_NAME="my-service"This step is required - without it, no telemetry is sent:
# Required for traces
export OTEL_TRACES_EXPORTER="otlp"
# Optional: also export metrics and logs
export OTEL_METRICS_EXPORTER="otlp"
export OTEL_LOGS_EXPORTER="otlp"export OTEL_EXPORTER_OTLP_ENDPOINT="https://<OTLP_ENDPOINT>"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer YOUR_AUTH_TOKEN"export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer YOUR_AUTH_TOKEN,Dash0-Dataset=my-dataset"# Service identification
export OTEL_SERVICE_NAME="my-service"
# Enable exporters (required!)
export OTEL_TRACES_EXPORTER="otlp"
export OTEL_METRICS_EXPORTER="otlp"
export OTEL_LOGS_EXPORTER="otlp"
# Configure endpoint
export OTEL_EXPORTER_OTLP_ENDPOINT="https://<OTLP_ENDPOINT>"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer YOUR_AUTH_TOKEN"
bundle exec rails serverAdd the dotenv gem and create a .env file:
.env:
OTEL_SERVICE_NAME=my-service
OTEL_TRACES_EXPORTER=otlp
OTEL_METRICS_EXPORTER=otlp
OTEL_LOGS_EXPORTER=otlp
OTEL_EXPORTER_OTLP_ENDPOINT=https://<OTLP_ENDPOINT>
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer YOUR_AUTH_TOKENRun with:
bundle exec rails serverFor development without a collector, use the console exporter to see telemetry in your terminal:
export OTEL_SERVICE_NAME="my-service"
export OTEL_TRACES_EXPORTER="console"
export OTEL_METRICS_EXPORTER="console"
export OTEL_LOGS_EXPORTER="console"
bundle exec rails serverThis prints spans, metrics, and logs directly to stdout—useful for verifying instrumentation works before configuring a remote backend.
If you set OTEL_TRACES_EXPORTER=otlp but have no collector running, you will see connection errors.
This is expected behavior.
Options:
console exporter during development (recommended for quick testing)Set service.name, service.version, and deployment.environment.name for every deployment.
See resource attributes for the full list of required and recommended attributes.
See Kubernetes deployment for pod metadata injection, resource attributes, and Dash0 Kubernetes Operator guidance.
The auto-instrumentation package automatically instruments:
| Category | Libraries |
|---|---|
| HTTP | Rack, Rails, Sinatra, Faraday, Net::HTTP |
| Database | PG, MySQL2, ActiveRecord |
| Cache | Redis, Dalli |
| Messaging | Sidekiq, Resque, Bunny |
| External | RestClient, Ethon, HTTP.rb |
| GraphQL | GraphQL |
| Logging | Logger |
Refer to the OpenTelemetry Ruby Contrib repository for the complete list.
Add business context to auto-instrumented traces:
tracer = OpenTelemetry.tracer_provider.tracer('my-service')
def process_order(order)
tracer = OpenTelemetry.tracer_provider.tracer('my-service')
tracer.in_span('order.process') do |span|
span.set_attribute('order.id', order.id)
span.set_attribute('order.total', order.total)
result = save_order(order)
result
rescue StandardError => e
span.status = OpenTelemetry::Trace::Status.error(e.message)
ctx = span.context
logger.error('order.process.failed',
trace_id: ctx.hex_trace_id,
span_id: ctx.hex_span_id,
'exception.type': e.class.name,
'exception.message': e.message,
'exception.stacktrace': e.backtrace&.join("\n"))
raise
end
endAuto-instrumentation creates spans you do not control directly (e.g., the SERVER span for an HTTP request).
To enrich these spans with business context or set their status, retrieve the active span from the current context.
See adding attributes to auto-instrumented spans for when to use this pattern.
span = OpenTelemetry::Trace.current_span
span.set_attribute('order.id', params[:order_id])
span.set_attribute('tenant.id', request.headers['X-Tenant-Id'])OpenTelemetry::Trace.current_span returns a non-recording span if no span is active.
Calling set_attribute or status= on a non-recording span is a no-op, so no guard is needed.
See span status code for the full rules. This section shows how to apply them in Ruby.
ERRORThe argument to Status.error is the status message.
It must contain the error class and a short explanation — enough to understand the failure without opening the full trace.
# BAD: no status message
span.status = OpenTelemetry::Trace::Status.error
# BAD: generic message with no diagnostic value
span.status = OpenTelemetry::Trace::Status.error('something went wrong')
# GOOD: specific message with error class and context
span.status = OpenTelemetry::Trace::Status.error("#{e.class}: #{e.message}")Do not include backtraces in the status message.
Record those in a log record with exception.stacktrace instead.
# BAD: backtrace in the status message
span.status = OpenTelemetry::Trace::Status.error(e.full_message)
# GOOD: short message only
span.status = OpenTelemetry::Trace::Status.error(e.message)OK only for confirmed successSet status to OK when application logic has explicitly verified the operation succeeded.
Leave status UNSET if the code simply did not encounter an error.
# GOOD: explicit confirmation from downstream
response = Net::HTTP.get_response(uri)
if response.is_a?(Net::HTTPSuccess)
span.status = OpenTelemetry::Trace::Status.ok
end
# BAD: setting OK speculatively
span.status = OpenTelemetry::Trace::Status.ok
some_method # might still fail after this pointConfigure your logging framework to serialize exceptions into a single structured field so that stack traces do not break the one-line-per-record contract. See logs for general guidance on structured logging and exception stack traces.
semantic_logger produces single-line JSON with exceptions serialized into structured fields.
require 'semantic_logger'
SemanticLogger.add_appender(io: $stdout, formatter: :json)
logger = SemanticLogger['OrderService']
begin
process_order(order)
rescue StandardError => e
logger.error('order.failed', exception: e, order_id: order.id)
endThe JSON formatter serializes the exception class, message, and backtrace into structured fields, keeping each log record on a single line.
For Rails applications, lograge replaces the default multi-line request log with a single-line JSON entry.
# config/environments/production.rb
config.lograge.enabled = true
config.lograge.formatter = Lograge::Formatters::Json.newLograge does not handle exception backtraces directly. Pair it with semantic_logger or a JSON formatter that serializes exceptions as single-line fields.
The Ruby SDK does not register shutdown hooks automatically.
Register an at_exit hook to flush and shut down providers before the process terminates, so buffered spans, metrics, and log records are not lost.
at_exit do
OpenTelemetry.tracer_provider.shutdown if OpenTelemetry.respond_to?(:tracer_provider)
OpenTelemetry.meter_provider.shutdown if OpenTelemetry.respond_to?(:meter_provider)
OpenTelemetry.logger_provider.shutdown if OpenTelemetry.respond_to?(:logger_provider)
endPlace the at_exit block immediately after OpenTelemetry::SDK.configure in your initializer.
shutdown flushes pending batches and releases resources.
The call blocks until export completes or the timeout expires (default 30 seconds).
Check exporters are enabled:
echo $OTEL_TRACES_EXPORTER # Should be "otlp" or "console", not emptyThe SDK defaults OTEL_TRACES_EXPORTER to none, which silently discards all telemetry.
Verify SDK is initialized:
Ensure the OpenTelemetry::SDK.configure block runs before your application code.
In Rails, this means placing it in config/initializers/opentelemetry.rb.
This means the SDK is working but cannot reach the collector:
OTEL_TRACES_EXPORTER=consoleOTEL_EXPORTER_OTLP_ENDPOINT is correctEnsure c.use_all() is called inside the OpenTelemetry::SDK.configure block.
Verify that the opentelemetry-instrumentation-all gem is installed.
Some libraries require their instrumentation gem to be explicitly added to the Gemfile.
Usually means OTEL_TRACES_EXPORTER (or metrics/logs) is not set.
Set it explicitly:
export OTEL_TRACES_EXPORTER="otlp"