OpenTelemetry provides powerful observability capabilities for Rails applications. Let’s explore how to configure logging.

Basic Setup

Create a Rails app with rails new -T rails-otel-demo

The configuration lives in config/initializers/open_telemetry.rb and demonstrates several key concepts:

OpenTelemetry::SDK.configure do |c|
  # Configuration goes here
end

(The examples I found use opentelemetry.rb for this filename, but the official project name is OpenTelemetry, so by convention it should have the underscore.)

Resource Attributes

The OTel Ruby SDK supports many of the standard OTel environment variables. You can find a list of them in the spec compliance matrix.

For example if you are using the OTEL_RESOURCE_ATTRIBUTES environment variable, it should have the following ‘comma separated, key-value pair’ format for the value:

OTEL_RESOURCE_ATTRIBUTES="k8s.namespace.name=the-namespace,k8s.pod.uid=a2b3c4d5-e6f7"

Configuration Precedence

By default, values set in the code will override values set in environment variables. The code in this example is written to prefer the OTel standard environment variables and fall back to a value set in the code if necessary.

  1. Environment variables (like OTEL_SERVICE_NAME and OTEL_SERVICE_ATTRIBUTES)
  2. Programmatic configuration
  3. Default values

For example:

c.service_name = ENV.fetch('OTEL_SERVICE_NAME', 'from_config_initializer')

Then if you run the app with

$ OTEL_LOGS_EXPORTER=otlp OTEL_SERVICE_NAME=from_envar bundle exec rails server -p 3001

you will see that the service_name label in Loki is set to from_envar rather than from_config_initializer.

Similarly if you know that the Kubernetes Namespace Name is set as K8S_NAMESPACE in your deployed environment, you can write

  SCR = OpenTelemetry::SemanticConventions::Resource

  SCR::K8S_NAMESPACE_NAME => resource_attrs[SCR::K8S_NAMESPACE_NAME] || ENV.fetch('K8S_NAMESPACE', 'unknown_namespace')

(The code for parsing the resource attributes from the environment variable is in config/initializers/open_telemetry.rb.)

This can help if other parts of your CI/CD process are injecting enviroment variables other than the standard ones, and/or you are using a service such as Seekrit or Doppler to control them.

In cases where a developer is implementing OTel logging but does not have control over the deployment environment, workarounds like this may be necessary until the organization can standardize on the OTel environment variable names, at which point the code can be removed as the Ruby SDK will detect and use the environment variable values.

Semantic Conventions

The code uses OpenTelemetry’s semantic conventions for standardized attribute naming:

c.resource = OpenTelemetry::SDK::Resources::Resource.create(
  OpenTelemetry::SemanticConventions::Resource::DEPLOYMENT_ENVIRONMENT => Rails.env.to_s
)

When running the app locally, this sets deployment.environment to development, which shows up as the value for the deployment_environment label in Loki.

Types

Note that with the Ruby SDK, all the attribute keys must be strings, and all the values must be string or number (or arrays of strings or numbers).

Simply using Rails.env as the value of an attribute will NOT work! It has a string representation that looks like what we need, but it is NOT a String.

$ bundle exec rails console

rails-otel-demo(dev)> Rails.env
=> "development"

rails-otel-demo(dev)> Rails.env.class
=> ActiveSupport::EnvironmentInquirer

Grafana docker-otel-lgtm

To see the logs show up in Loki using Grafana locally, try out Grafana’s docker-otel-lgtm project:

git clone https://github.com/grafana/docker-otel-lgtm.git
cd docker-otel-lgtm
./run-lgtm.sh

Then visit http://localhost:3000

Note that the default port for the Rails app is also 3000, so start the Rails app with -p 3001 to avoid a conflict.

Inflections

Don’t forget to un-comment the code in config/initializers/inflections.rb and add one for OTel. Otherwise, if you have any names with _otel it will get capitalized as Otel which is incorrect.

  ActiveSupport::Inflector.inflections(:en) do |inflect|
    inflect.acronym "RESTful"
    inflect.acronym "OTel"
  end

References

AI

I used Anthropic’s Claude 3.5 Sonnet for help with some of the code and to draft this blog post. It really is awesome. Keep notes in the README.md file of a project while you are learning something new, and then ask it (using the VS Code Github Copilot plugin) “@workspace look at the open_telemetry.rb, logger.rb, and README.md files and write a blog post explaining what I’ve learned”.

Copyright 2025 Wendy Smoak - This post first appeared on wsmoak.net and is CC BY-NC licensed.