We use cookies or similar technologies to personalize your online experience and tailor marketing to you. Many of our product features require cookies to function properly. Your use of this site and online product constitutes your consent to these personalization technologies. Read our Privacy Policy to find out more.

X

Beeline for Ruby

The Ruby Beeline for Honeycomb is quick and easy way to instrument your Ruby application. It includes several optional wrappers that automatically instrument HTTP requests and database queries. It also supports tracing out of the box, linking database queries to the HTTP request from which they originated. While this is a great way to get general insights about your app as quickly as possible, as you forge ahead on your observability journey you may find you’d like to add new events or traces to add more details specific to your app. The Ruby Beeline provides simple interfaces for adding both.

To see an example of the Ruby Beeline in action, try out the Ruby-Gatekeeper Example App.

If you’d like to see more options in the Ruby Beeline, please file an issue or vote up one already filed! You can also contact us at support@honeycomb.io.

Requirements

You can find your API key on your Team Settings page. If you don’t have a API key yet, sign up for a Honeycomb trial.

Quick installation

To install the Ruby Beeline for your application:

  1. Add honeycomb-beeline to your Gemfile:

    gem 'honeycomb-beeline'
  2. Initialize the Ruby Beeline in your app’s startup script. Depending on how your app is configured, this might be in config.ru or another location.

    If you are instrumenting a rails application the call to Honeycomb.init must appear before the call to require the environment file in config.ru.

    # config.ru
    require 'honeycomb-beeline'
    
    Honeycomb.init
    
    require ::File.expand_path('../config/environment', __FILE__)
    run Rails.application

    For Sinatra apps, the Ruby Beeline attempts to install the Beeline middleware automatically. If you have an unusual Sinatra configuration, you may need to do this manually. To install manually, add the following line of code in your Sinatra app:

    use Rack::Honeycomb::Middleware

    Add the Honeycomb middleware manually as below

    use Rack::Honeycomb::Middleware

  3. Configure your Honeycomb API key and dataset. You can specify your key and dataset using environment variables or by modifying your Honeycomb.init call.

    HONEYCOMB_WRITEKEY="YOUR_API_KEY"
    HONEYCOMB_DATASET=MyRubyApp
    HONEYCOMB_SERVICE=MyRubyApp

    Pass your key and dataset in your Honeycomb.init call like this:

    Honeycomb.init(writekey: 'YOUR_API_KEY', dataset: 'MyRubyApp')

    You can also specify service_name in your Honeycomb.init call, otherwise the Beeline uses your dataset value as your service name.

    You are currently logged in to the team, so we have populated the write key here to the first write key for that team.

Note: Your Honeycomb API Key is sensitive information. You should not check it into a source code repository.

Using Traces

A Complete Trace

The Ruby Beeline has a complete tracing API that you can use on your own or in addition to the default automatic instrumentation. Tracing can be reduced to a few basic steps:

  1. Start a trace - this creates a “root span” which is the top of your trace
  2. Create one or more “child spans”. Each span covers a notable section of your application: a database call, an external API call, a long computation, etc.
  3. Close child spans in reverse order.
  4. Close trace (root span).


Starting and ending a trace or span

Tracing tells a story of what transpired inside your transaction or workflow, but to tell that story, you need spans! Spans represent noteworthy parts of your application that you’d like to instrument. Examples include: database queries, external service calls, long computations, batch processing, and more. If it’s a potential bottleneck or point of failure, consider enclosing it in a span.

If you are using a supported framework then you shouldn’t need to start your own traces as this is automatically handled by the beeline.

span is the general starting point for adding traces and spans. It will start a trace if one is not active. You’ll want to call it at the start of an interesting transaction, operation, or workflow that you want to instrument.

span takes a block which should encompass the operation that you want to include in your trace. Once the block completes the spans event data will be sent to Honeycomb.

Distributed Tracing

Distributed Tracing enables you trace and visualize interactions between multiple instrumented services. For example, your users may interact with a front-end API service which talks to two internal APIs to fulfill their request. To get the complete picture of a transaction, you want Distributed Tracing.

Distributed Tracing requires the following:

The Python, Go, Ruby, and NodeJS beelines all support the exchange of trace context between HTTP services using the X-Honeycomb-Trace header, which can be added to outbound requests. Upon receiving a request from an upstream service with this header, trace context can be extracted and a local trace can be started.

Here is an example of the header contents:

1;trace_id=weofijwoeifj,parent_id=owefjoweifj,context=SGVsbG8gV29ybGQ=

trace_id is the upstream trace ID that we want to continue. parent_id is the ID of the originating (parent) span. Context is a base64 encoded JSON string containing key/value pairs that make up the trace fields. These are optional fields that the upstream service wants to propagate to the entire trace.

For more information about tracing, see our tracing docs.

Extracting Trace Context in Rails, Sinatra, and Rack

The middleware in the Ruby Beeline already looks for an incoming X-Honeycomb-Trace header, and, if found, starts a trace using that context information.

Extracting Trace Context Manually

If your framework of choice does not have automatic instrumentation in the Beeline, you can still get trace context using the trace API and use it to start your trace manually.

# req is a generic incoming request with a hash of headers
header = req.headers['X-Honeycomb-Trace']

Honeycomb.trace_from_encoded_context(header) do
  Honeycomb.span do
    # an interesting operation
  end
end

Sending trace context downstream

If you are using the Faraday gem, the beeline can instrument outbound requests for you.

This will inject the X-Honeycomb-Trace header automatically on all requests.

Be careful when calling third-party services with the X-Honeycomb-Trace header. If you have added sensitive fields, these will be sent in the header, potentially exposing them.

Adding context to events

To add custom fields to your events, use Rack::Honeycomb.add_field. This example adds user.email to each "type": "http_server" event:

get '/hello' do
  user = authenticate_user()

  # this will add a custom field 'app.user.email' to the http_server event
  Rack::Honeycomb.add_field(env, 'user.email', user.email)

  "Hello, #{user.name}!"
end

Additional fields are added under the app. namespace. For example, the field above would appear in your event as app.big_num. The namespace groups your fields together to make them easy to find and examine.

These additional fields are your opportunity to add important and detailed context to your instrumentation. Put a timer around a section of code, add per- user information, include details about what it took to craft a response, and so on. It is expected that some fields will only be present on some requests. Error handlers are a great example of this; they will obviously only exist when an error has occurred.

It is common practice to add in these fields along the way as they are processed in different levels of middleware. For example, if you have an authentication middleware, it would add a field with the authenticated user’s ID and name as soon as it resolves them. Later on in the call stack, you might add additional fields describing what the user is trying to achieve with this specific HTTP request.

Instrumented packages

HTTP server wrappers:

Database wrappers:

HTTP client wrappers:

Sampling events

To sample a portion of events for very high throughput services, include an integer sample_rate in the initialization of the Ruby Beeline. This sends 1/n of all events, so a sample rate of 5 would send 20% of your events. Try starting with a value of 10:

require 'honeycomb-beeline'

Honeycomb.init(
  sample_rate: 10
)

Troubleshooting the Beeline

There are two general approaches to finding out what’s wrong when the Ruby Beeline isn’t doing what you expect.

Debug mode

Debug mode prints events to STDERR rather than sending them to Honeycomb. It also logs startup messages to STDERR.

There are two ways to enable debug mode:

Honeycomb.init(debug: true)

Enable additional logging

The Ruby Beeline logs additional messages at startup if it can’t determine which auto-installed instrumentations to install or if it can’t configure them.

For other messages, the Ruby Beeline logs error and warn messages. To change the log level, pass a Logger object to Honeycomb.init like this:

require 'logger'
logger = Logger.new($stderr)
logger.level = Logger::INFO           # available levels: FATAL, ERROR, WARN, INFO, DEBUG
Honeycomb.init(logger: logger)

Example event

Below is a sample event from the Ruby Beeline. This example is an http_server event, generated when your app handles an incoming HTTP request.

{
  "meta.beeline_version": "0.2.0",
  "meta.local_hostname": "myhost",
  "service_name": "my-test-app",
  "meta.package": "rack",
  "meta.package_version": "1.3",
  "type": "http_server",
  "name": "GET /dashboard",
  "request.method": "GET",
  "request.path": "/dashboard",
  "request.protocol": "https",
  "request.http_version": "HTTP/1.1",
  "request.host": "my-test-app.example.com",
  "request.remote_addr": "192.168.1.5",
  "request.header.user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
  "trace.trace_id": "b694512a-833f-4b35-be5f-6c742ba18e12",
  "trace.span_id": "c35cc326-ed90-4881-a4a8-68526d252f2e",
  "response.status_code": 200,
  "duration_ms": 303.057396
}

Queries to try

Here are some examples to get you started querying your app’s behavior:

Which of my app’s routes are the slowest?

Where is my app spending the most time?

Which users are using the endpoint that I’d like to deprecate? (Using a custom field user.email)

Contributions

Features, bug fixes and other changes to Beelines are gladly accepted. Please open issues or a pull request with your change via GitHub. Remember to add your name to the CONTRIBUTORS file!

All contributions will be released under the Apache License 2.0.