Auto-Instrumenting Ruby Apps with OpenTelemetry

Ruby & OpenTelemetry Instrumentation

In this tutorial, we will go through a working example of a Ruby application auto-instrumented with OpenTelemetry. To keep things simple, we will create a basic “Hello World” application, instrument it with OpenTelemetry’s Ruby client library to generate trace data and send it to an OpenTelemetry Collector. The Collector will then export the trace data to an external distributed tracing analytics tool of our choice. 

OpenTelemetry structure. Source: OpenTelemetry Guide
OpenTelemetry structure. Source: OpenTelemetry Guide

If you are new to the OpenTelemetry open source project or if you are looking for more application instrumentation options, check out this guide.

Our Example Application

Our example application is a local Sinatra server that responds with “Hello World!“ every time we access it. Let’s first create a dedicated directory for this application.

mkdir sinatra-hello-world-otel
cd sinatra-hello-world-otel

Now we can create a Gemfile:

nano Gemfile

Next we’ll set the following configuration:

source "http://rubygems.org"

gem 'rake'
gem 'thin'
gem 'sinatra'

Our next step is creating the Server file…

nano server.rb

…with the following code:

require 'rubygems'
require 'bundler/setup'
Bundler.require
get '/' do
 'Hello world!'
end

We now have a good and simple example application that we can start by running ruby server.rb. The server will run at http://127.0.0.1:4567

We’ll get back to our Server and Gemfile files in the instrumentation steps below. 

Installing the Ruby OpenTelemetry Client Gems

Now that we have our example application up and running, it’s time to proceed to installing the Ruby OpenTelemetry client library. First of all, we will need to install the following three gems:

  • opentelemetry-sdk
  • opentelemetry-exporter-otlp
  • opentelemetry-instrumentation-all

To install the client gems, we will need to run the following command from our application directory – sinatra-hello-world-otel:

gem install opentelemetry-sdk
gem install opentelemetry-exporter-otlp
gem install opentelemetry-instrumentation-all

These gems will install all necessary libraries to instrument the code and export traces using OpenTelemetry Protocol (OTLP).

Note: For the sake of simplicity, we used the opentelemetry-instrumentation-all library, which includes instrumentation packages for all Ruby libraries, including Sinatra HTTP server. Of course, if you want to keep things as light as possible, you can selectively install only those packages that are applicable to your application, such as opentelemetry-instrumentation-sinatra.

Defining the Instrumentation in Our Application

After installing the gems, we need to add them to our Gemfile configuration as follows:

gem "opentelemetry-api"
gem "opentelemetry-sdk"
gem "opentelemetry-exporter-otlp"
gem 'opentelemetry-instrumentation-sinatra'

Our next step is enabling the instrumentation in our Server file by adding the following code:

OpenTelemetry::SDK.configure do |c|
c.service_name = 'ruby-otlp'
 c.use_all
end

And the following requirements:

require 'opentelemetry/sdk'
require 'opentelemetry/exporter/otlp'
require 'rubygems'
require 'bundler/setup'

This is what our Gemfile and Server files look like now:

Gemfile

source "https://rubygems.org"

gem "opentelemetry-api"
gem "opentelemetry-sdk"
gem "opentelemetry-exporter-otlp"
gem 'opentelemetry-instrumentation-sinatra'
gem "sinatra"
gem 'thin'

Server

require 'opentelemetry/sdk'
require 'opentelemetry/exporter/otlp'
require 'rubygems'
require 'bundler/setup'
Bundler.require
OpenTelemetry::SDK.configure do |c|
 c.service_name = 'ruby-otlp'
 c.use_all
end
get '/' do
 'Hello world!'
end

That’s it for instrumenting our app! There’s no additional coding needed to generate the individual spans (the trace building blocks) in each respective part of our application. That’s the beauty of auto-instrumentation. How is this magic done, you ask? The c.use_all statement above states that the application will be using all instrumentation provided by OpenTelemetry and send all available traces from our application. 

Now let’s complete the configuration and install the bundler:

bundler install

After this step, we need to specify the endpoint that we will be using to export traces. In our example, this is going to be localhost with port 55681, which is the default port that OpenTelemetry uses to send traces in OTLP/HTTP format:

export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:55681

Downloading and Configuring the OpenTelemetry Collector

The last component that we will need, is the OpenTelemetry Collector, which we can download here. In our example, we will be using the otelcontribcol_darwin_amd64 flavor, but you can choose any other version of the collector from the list, as long as the collector is compatible with your operating system.

The data collection and export settings in the OpenTelemetry Collector are defined by a config file in YAML format. We will create this file in the same directory (sinatra-hello-world-otel) as the collector file that we have just downloaded and call it config.yaml. This file will have the following configuration:

receivers:  
  otlp:
    protocols:
      grpc:
      http:

exporters:
  logzio:
    account_token: "<<TRACING-SHIPPING-TOKEN>>"
    #region: "<<LOGZIO_ACCOUNT_REGION_CODE>>" - (Optional)

processors:
  batch:

extensions:
  pprof:
    endpoint: :1777
  zpages:
    endpoint: :55679
  health_check:

service:
  extensions: [health_check, pprof, zpages]
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [logzio]

In this example we send the traces to Logz.io’s Distributed Tracing service, so we configure the collector with the Logz.io exporter which will send traces to the Logz.io account defined by the account token (if you don’t have an account you can get a free one here). However, you can also export the trace data from OpenTelemetry Collector to any other tracing backend by adding the required exporter configuration to this file (you can read more on exporters options here).

Running It All Together

Now that we have everything set up, let’s send some traces.

First, we need to start the OpenTelemetry Collector. We do this by specifying the path to the collector and the required config file. In our example, we run both files from our sinatra-hello-world-otel directory as follows:

./otelcontribcol_darwin_amd64 --config ./config.yaml

The collector is now running and listening to incoming traces on port 55681.

Our next step is to start our application:

ruby server.rb

All that is left for us to do at this point is to visit http://127.0.0.1:4567 and refresh the page, triggering our app to generate and emit a trace of that transaction (repeat that a few times to have several sample traces to look at). The Collector will then pick up these traces and send them to the distributed tracing backend defined by the exporter in the collector config file.

Let’s check the Jaeger UI to make sure our traces arrived ok:

The traces are in Jaeger, ready for us to visualize and analyze them. 

Summary

As you can see, OpenTelemetry makes it pretty simple to automatically instrument Ruby applications. All we had to do, was:

  • Install relevant Ruby OpenTelemetry client gems
  • Define the gems in our Gemfile and the application file
  • Add code to enable auto-instrumentation in our application file
  • Configure the exporter endpoint for our application
  • Download the OpenTelemetry Collector and configure it to receive the trace data and send it to our tracing analytics tool

For more information on OpenTelemetry instrumentation, visit this guide. If you’re interested in manual instrumentation options for Ruby, check out the OpenTelemetry GitHub repository. If you are interested in trying this integration out using Logz.io backend, feel free to sign up for a free account and then use our documentation to set up auto-instrumentation for your own Ruby application. 

    Internal

    Organize Your Kubernetes Logs On One Unified SaaS Platform

    Learn More