> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bronto.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Send Ruby logs, metrics, and traces to Bronto

> Instrument Ruby and Rails applications with the OpenTelemetry Ruby SDK to send logs, metrics, and traces to Bronto over OTLP/HTTP, via an OTel Collector or directly.

One initialiser sends **logs, metrics, and traces** from your Ruby application to Bronto. `OpenTelemetry::SDK.configure` sets up the shared resource and tracing — instrumenting Rails, Rack, Active Record, Faraday, and Redis through `use_all` — and a logger provider and metric reader add the other two signals.

The application exports to an [OpenTelemetry Collector](/agent-setup/open-telemetry), which forwards to Bronto. If you don't run a Collector, [export directly to Bronto](#direct-export-to-bronto) instead — the code is identical, only two environment variables change.

<Note>
  The Ruby logs and metrics SDKs are newer than tracing and their APIs are still settling. Pin your gem versions and check the [OpenTelemetry Ruby documentation](https://opentelemetry.io/docs/languages/ruby/) for the release you deploy. Tracing is stable.
</Note>

## Prerequisites

* Ruby 3.0 or later, and Bundler
* An OTel Collector reachable from your application, with `logs`, `metrics`, and `traces` pipelines forwarding to Bronto — see [Connect OpenTelemetry Collector to Bronto](/agent-setup/open-telemetry)

## Install dependencies

```ruby Gemfile theme={"dark"}
gem 'opentelemetry-sdk'
gem 'opentelemetry-instrumentation-all'
gem 'opentelemetry-exporter-otlp'
gem 'opentelemetry-logs-sdk'
gem 'opentelemetry-exporter-otlp-logs'
gem 'opentelemetry-metrics-sdk'
gem 'opentelemetry-exporter-otlp-metrics'
```

```bash theme={"dark"}
bundle install
```

## Configure the environment

The exporters read their endpoint and identity from environment variables, so nothing in the code below is environment-specific.

```bash theme={"dark"}
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
export OTEL_SERVICE_NAME="my-service"
export OTEL_RESOURCE_ATTRIBUTES="service.namespace=my-team,deployment.environment=production"
```

The exporters append the signal path (`/v1/logs`, `/v1/metrics`, `/v1/traces`) to `OTEL_EXPORTER_OTLP_ENDPOINT` automatically.

Two resource attributes determine how Bronto organises your data:

| OTel attribute      | Bronto concept | Set with                   |
| ------------------- | -------------- | -------------------------- |
| `service.name`      | Dataset        | `OTEL_SERVICE_NAME`        |
| `service.namespace` | Collection     | `OTEL_RESOURCE_ATTRIBUTES` |

<Note>
  `http://localhost:4318` is the standard OTLP/HTTP address for a Collector on the same host. Use the address reachable from your application if the Collector runs in another container, pod, or host. No authentication is needed between the application and the Collector — the Collector holds the Bronto API key.
</Note>

## Initialise the SDK

Load this once at startup — in Rails, as `config/initializers/opentelemetry.rb`:

```ruby otel.rb theme={"dark"}
require 'opentelemetry/sdk'
require 'opentelemetry/instrumentation/all'
require 'opentelemetry/logs/sdk'
require 'opentelemetry/metrics/sdk'
require 'opentelemetry-exporter-otlp'
require 'opentelemetry-exporter-otlp-logs'
require 'opentelemetry-exporter-otlp-metrics'

# Resource, tracing, and every available instrumentation library.
# Service identity comes from OTEL_SERVICE_NAME and OTEL_RESOURCE_ATTRIBUTES.
OpenTelemetry::SDK.configure do |c|
  c.use_all
end

# Logs
logger_provider = OpenTelemetry::SDK::Logs::LoggerProvider.new(
  resource: OpenTelemetry::SDK::Resources::Resource.create
)
logger_provider.add_log_record_processor(
  OpenTelemetry::SDK::Logs::Export::BatchLogRecordProcessor.new(
    OpenTelemetry::Exporter::OTLP::Logs::LogsExporter.new
  )
)
OpenTelemetry::Logs.logger_provider = logger_provider

# Metrics
OpenTelemetry.meter_provider.add_metric_reader(
  OpenTelemetry::SDK::Metrics::Export::PeriodicMetricReader.new(
    exporter: OpenTelemetry::Exporter::OTLP::Metrics::MetricsExporter.new
  )
)

at_exit { logger_provider.shutdown }
```

## Instrument your application

Spans and instruments come from the global providers:

```ruby theme={"dark"}
tracer = OpenTelemetry.tracer_provider.tracer('my-service')
payments = OpenTelemetry.meter_provider.meter('my-service').create_counter('app.payments')

tracer.in_span('process-payment') do |span|
  span.set_attribute('payment.amount', 99.99)
  payments.add(1)
end
```

<Note>
  **Logs need an explicit bridge.** The official SDK does not yet ship a handler for Ruby's standard `Logger`, so application logs are emitted through the OTel Logs API directly:

  ```ruby theme={"dark"}
  logger = OpenTelemetry::Logs.logger_provider.logger(name: 'my-service')

  logger.on_emit(
    timestamp: Time.now,
    severity_number: OpenTelemetry::Logs::SeverityNumber::INFO,
    severity_text: 'INFO',
    body: 'Processing payment',
    attributes: { 'payment.amount' => 99.99 }
  )
  ```

  A log emitted inside an active span picks up that span's `trace_id` and `span_id` from the current context. Community bridges for Rails and Sidekiq are in development — until one lands, an alternative is to write JSON logs to a file and tail them with the Collector's `filelog` receiver.
</Note>

## Verify

Run your application, then check each signal in Bronto, filtering by the `service.name` you set:

* **Logs** — the [Search](https://app.bronto.io/search) page, in the dataset named after your service
* **Metrics** — the Metric Explorer
* **Traces** — the Explore Traces page

If nothing arrives:

* Confirm the Collector is running and reachable at `OTEL_EXPORTER_OTLP_ENDPOINT`, and that its pipelines include an `otlp` receiver and the Bronto exporters.
* Confirm the initialiser runs before the first span or log emission.
* The batch processors export on a background thread — keep the `at_exit` shutdown so short-lived scripts flush before exit.

For signal-specific reference material, see [Send Metrics to Bronto](/metrics/send-metrics) and [Send Traces to Bronto](/tracing/send-traces).

## Direct export to Bronto

Without a Collector, the application exports straight to Bronto over OTLP/HTTP. The code in `otel.rb` is unchanged — point the endpoint at your Bronto region and add your API key:

```bash theme={"dark"}
export OTEL_EXPORTER_OTLP_ENDPOINT="https://ingestion.eu.bronto.io"   # or ingestion.us.bronto.io
export OTEL_EXPORTER_OTLP_HEADERS="x-bronto-api-key=<YOUR_API_KEY>"
```

| Region | Base endpoint                    |
| ------ | -------------------------------- |
| EU     | `https://ingestion.eu.bronto.io` |
| US     | `https://ingestion.us.bronto.io` |

See [API Keys](/Account-Management/API-Keys) for how to create a key with ingestion permissions.

<Tip>
  Prefer the Collector for multi-service environments — it batches, filters, and enriches telemetry, and keeps the API key out of every application. Direct export suits single services and simple architectures.
</Tip>

## GenAI semantic conventions

If your application calls an LLM, OpenTelemetry's [GenAI semantic conventions](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-spans.md) define `gen_ai.*` span attributes for model, token usage, and prompt and response content.

### Manual spans

Ruby has no first-party instrumentation emitting GenAI-semconv spans, so set the attributes yourself around each model call:

```ruby theme={"dark"}
tracer = OpenTelemetry.tracer_provider.tracer('my-service')

tracer.in_span('chat gpt-4o-mini') do |span|
  span.set_attribute('gen_ai.provider.name', 'openai')
  span.set_attribute('gen_ai.request.model', 'gpt-4o-mini')
  span.set_attribute('gen_ai.usage.input_tokens', 33)
  span.set_attribute('gen_ai.usage.output_tokens', 74)
  # Bronto flattens array attributes — this is searchable as finish_reasons.0
  span.set_attribute('gen_ai.response.finish_reasons', ['stop'])
end
```

Message content (`gen_ai.input.messages` / `gen_ai.output.messages`) is opt-in by convention and off by default in the languages that have auto-instrumentation. Since you are setting attributes by hand, apply the same discipline — gate prompt and response content behind your own config flag rather than always sending it.

See the [GenAI span conventions](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-spans.md) for the full attribute list.

For the recommended attribute set and Bronto search queries, see [LLM Observability](/ai-features/llm-observability).
