Ruby

Honeycomb libraries are built to help you instrument your code easily. Honeycomb is most powerful when it has a wide surface of structured data to work with, so the library is geared towards building up Events and sending each Event into Honeycomb.

Each Event holds multiple key/value pairs (“attributes”). Values can be numbers (integers or floating point numbers) or strings (version numbers, customer IDs, email addresses, host names, etc.). These values together give a full picture of what is happening in your code as it does the work represented by the Event.

In order to help gather information that appears at different parts of the application and has different scope, the library has several methods of adding attributes to an event or series of events.

Libhoney has three levels of scope: global, Builder, and Event. The scope to which you add an attribute determines the final Events that contain that value.

Installation

To install the stable release:

gem install libhoney

If you’re using bundler, you can also reference the git repo and stay on the bleeding age by putting this in your Gemfile:

gem 'libhoney', :git => 'http://github.com/honeycombio/libhoney-rb.git'

Documentation

Initialization

Initialize the library by passing in your Team Write Key and the default dataset name to which it should send events.

require 'libhoney'

libhoney = Libhoney::Client.new(:writekey => "YOUR_WRITE_KEY",
                                :dataset => "honeycomb-ruby-example")

# ... Do work and capture events

# Call close to flush any pending calls to Honeycomb
libhoney.close(true)

Further configuration options can be found in the API reference.

Note: the Libhoney::Client initialization params may contain an api_host key, which defaults to Honeycomb’s API server. Overriding this with an empty string is a good way to drop events in a test environment.

Building and Sending Events

Once initialized, libhoney is ready to send events. Events go through three phases:

Upon calling .send(), the event is dispatched to be sent to Honeycomb. All libraries set defaults that will allow your application to function as smoothly as possible during error conditions. When creating events faster than they can be sent, overflowed events will be dropped instead of backing up and slowing down your application.

In its simplest form, you can add a single attribute to an event with the .add_field(k, v) method. If you add the same key multiple times, only the last value added will be kept.

More complex structures (hashes and objects—things that can be serialized into a JSON object) can be added to an event with the .add(data) method.

Events can have metadata associated with them that is not sent to Honeycomb. This metadata is used to identify the event when processing the response. More detail about metadata is below in the Response section.

Handling Responses

Sending an event is an asynchronous action and will avoid blocking by default. .send() will enqueue the event to be sent as soon as possible (thus, the return value doesn’t indicate that the event was successfully sent). Use the queue returned by .responses to check whether events were successfully received by Honeycomb’s servers.

Before sending an event, you have the option to attach metadata to that event. This metadata is not sent to Honeycomb; instead, it’s used to help you match up individual responses with sent events. When sending an event, libhoney will take the metadata from the event and attach it to the response object for you to consume. Add metadata by calling .add_metadata(k, v) on an event.

Responses are represented as hashes with the following keys:

You don’t have to process responses if you’re not interested in them—simply ignoring them is perfectly safe. Unread responses will be dropped.

Examples

Honeycomb can calculate all sorts of statistics, so send the data you care about and let us crunch the averages, percentiles, lower/upper bounds, cardinality—whatever you want—for you.

Simple: Send a blob immediately

require 'libhoney'

libhoney = Libhoney::Client.new(:writekey => "YOUR_WRITE_KEY",
                                :dataset => "honeycomb-ruby-example")

libhoney.send_now({
  "duration_ms": 153.12,
  "method": "get",
  "hostname": "appserver15",
  "payload_length": 27
})

# Call close to flush any pending calls to Honeycomb
libhoney.close(true)

Intermediate: Override some attributes

# ... Initialization code ...
params = {
  "hostname": "foo.local",
  "built": false,
  "user_id": -1
}

libhoney.add(params)

builder = libhoney.builder({ "builder": true })

# Spawn a new event and override the timestamp
event = builder.event()
event.add_field("user_id", 15)
event.add_field("latency_ms", Time.now() - start)
event.timestamp = Time.utc(2016, 2, 29, 1, 1, 1)
event.send()

Further examples can be found on GitHub.

Middleware Examples: Rack

Rack is a widely-used interface for HTTP servers running Ruby apps (and often using Ruby-based web frameworks like Rails). Each inbound HTTP request as received by a framework like Rack maps nicely to Honeycomb events, representing “a single thing of interest that happened” in a given system.

Rack middlewares are simply classes that have access to the environment and response object in the application’s request-response chain.

As such, you can define a simple honey_middleware.rb as in the following:

require "libhoney"

module Rack
  module Honey
    class Middleware
      def initialize(app, options = {})
        @app, @options = app, options

        @honey = Libhoney::Client.new(:writekey => options[:writekey],
                                      :dataset  => options[:dataset],
                                      :api_host => options[:api_host])
      end

      def call(env)
        ev = @honey.event
        request_started_at = Time.now
        status, headers, response = @app.call(env)
        request_ended_at = Time.now

        ev.add(headers)
        if headers[CONTENT_LENGTH] != nil
          # Content-Length (if present) is a string.  let's change it to an int.
          ev.add_field(CONTENT_LENGTH, headers[CONTENT_LENGTH].to_i)
        end
        add_field(ev, 'HTTP_STATUS', status)
        add_field(ev, 'REQUEST_TIME_MS', (request_ended_at - request_started_at) * 1000)

        # we can't use `ev.add(env)` because json serialization fails.
        # pull out some interesting and potentially useful fields.
        add_env(ev, env, 'rack.version')
        add_env(ev, env, 'rack.multithread')
        add_env(ev, env, 'rack.multiprocess')
        add_env(ev, env, 'rack.run_once')
        add_env(ev, env, 'REQUEST_METHOD')
        add_env(ev, env, 'REQUEST_PATH')
        add_env(ev, env, 'REQUEST_URI')
        add_env(ev, env, 'HTTP_VERSION')
        add_env(ev, env, 'HTTP_HOST')
        add_env(ev, env, 'HTTP_CACHE_CONTROL')
        add_env(ev, env, 'HTTP_USER_AGENT')
        add_env(ev, env, 'HTTP_ACCEPT')
        add_env(ev, env, 'REMOTE_ADDR')
        ev.send

        [status, headers, response]
      end
    end
  end
end

See the example honeycomb-rack repo on GitHub for more sample code demonstrating how to use events, builders, fields, and dynamic fields, specifically in the context of Rack middleware.

Advanced Usage: Utilizing Builders

Builders are, at their simplest, a convenient way to avoid repeating common attributes that may not apply globally. Creating a builder for a given component allows a variety of different events to be spawned and sent within the component, without having to repeat the component name as an attribute for each.

You can clone builders—the cloned builder will have a copy of all the fields and dynamic fields in the original. As your application forks down into more and more specific functionality, you can create more detailed builders. The final event creation in the leaves of your application’s tree will have all the data you’ve added along the way in addition to the specifics of this event.

The global scope is essentially a specialized builder, for capturing attributes that are likely useful to all events (e.g. hostname, environment, etc). Adding this kind of peripheral and normally unavailable information to every event gives you enormous power to identify patterns that would otherwise be invisible in the context of a single request.

Advanced Usage: Dynamic Fields

The top-level libhoney and Builders support .add_dynamic_field(func). Adding a dynamic field to a Builder or top-level libhoney ensures that each time an event is created, the provided function is executed and the returned key/value pair is added to the event. This may be useful for including dynamic process information such as memory used, number of threads, concurrent requests, and so on to each event. Adding this kind of dynamic data to an event makes it easy to understand the application’s context when looking at an individual event or error condition.

Contributions

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

All contributions will be released under the Apache License 2.0.