# Instrument Rails with OpenTelemetry

> Send traces and logs from a Ruby on Rails app to Telemetry Machine with the OpenTelemetry Ruby SDK and its Rails instrumentation.

The OpenTelemetry Ruby SDK traces every request and sends your `Rails.logger` records as logs. This page sends traces and logs only, because Ruby's metrics SDK is still experimental.

## Create the datasets

Each signal goes to its own dataset. Create two, and an ingest key that can write to both, with [tm-cli](https://telemetrymachine.com/docs/quick-start#tm-cli) or the [MCP server](https://telemetrymachine.com/docs/quick-start#option-1-set-up-with-a-coding-agent):

```console tab="tm-cli"
$ tm-cli datasets create --name shop-prod-traces --type traces
$ tm-cli datasets create --name shop-prod-logs --type logs
$ tm-cli api-keys create --name shop-api --dataset shop-prod-traces --dataset shop-prod-logs
```

```json tab="MCP"
create_dataset { "name": "shop-prod-traces", "signal_type": "traces" }
create_dataset { "name": "shop-prod-logs", "signal_type": "logs" }
create_ingest_key {
  "name": "shop-api",
  "datasets": ["shop-prod-traces", "shop-prod-logs"]
}
```

tm-cli shows the key once, in `api_key.api_key`. Over MCP, your agent gets it once and saves it in your app's configuration. In the app, [create each dataset](https://telemetrymachine.com/datasets/new), then a key for both under **API keys** in your organization's settings.

## Install

```console
$ bundle add opentelemetry-sdk opentelemetry-exporter-otlp opentelemetry-instrumentation-all
$ bundle add opentelemetry-logs-sdk opentelemetry-exporter-otlp-logs opentelemetry-instrumentation-logger
```

Add an initializer:

```ruby title="config/initializers/opentelemetry.rb"
require "opentelemetry/sdk"
require "opentelemetry/exporter/otlp"
require "opentelemetry-logs-sdk"
require "opentelemetry/exporter/otlp_logs"
require "opentelemetry/instrumentation/all"
require "opentelemetry/instrumentation/logger"

OpenTelemetry::SDK.configure(&:use_all)

# Send what is still buffered when the server stops.
at_exit do
  OpenTelemetry.tracer_provider.shutdown
  OpenTelemetry.logger_provider.shutdown
end
```

## Configure

```bash
export TM_INGEST_KEY='<ingest-key>'
export OTEL_SERVICE_NAME=shop-api
export OTEL_EXPORTER_OTLP_ENDPOINT=https://api.telemetrymachine.com
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
export OTEL_EXPORTER_OTLP_TRACES_HEADERS="X-API-Key=$TM_INGEST_KEY,X-Dataset-Name=shop-prod-traces"
export OTEL_EXPORTER_OTLP_LOGS_HEADERS="X-API-Key=$TM_INGEST_KEY,X-Dataset-Name=shop-prod-logs"
```

## Run

```console
$ bin/rails server
```

## Check it arrived

Send your app a few requests, then open each explorer and choose the dataset:

- **Traces**, `shop-prod-traces`: one server span per request, named after its route, such as `GET /items/:id`.
- **Logs**, `shop-prod-logs`: your `Rails.logger` records, and Rails's own request lines, each with its request's `trace.trace_id`.

Or count spans by name with tm-cli or the MCP server:

```console tab="tm-cli"
$ tm-cli query shop-prod-traces --since 15m --select "name, count(*)" --group-by name
```

```json tab="MCP"
run_query {
  "dataset": "shop-prod-traces",
  "description": "Spans by name",
  "since": "15m",
  "select": "name, count(*)",
  "group_by": "name"
}
```

## Troubleshooting

- **`Use either use_all or use, but not both`:** to configure one instrumentation, pass its options to `use_all`, such as `c.use_all("OpenTelemetry::Instrumentation::Rack" => { untraced_endpoints: ["/up"] })`.
- **Log lines contain codes such as `[1m[36m`:** Rails colors its SQL log lines. Set `config.colorize_logging = false` in `config/application.rb`.

---

All pages: https://telemetrymachine.com/docs/llms.txt
