Send Logs with Libhoney for Ruby

Libhoney for Ruby is Honeycomb’s structured logging library for Ruby applications. It is a low-level library that helps you send structured events to Honeycomb’s Events API.

Tip
If you are instrumenting a new application for tracing, we recommend that you use OpenTelemetry instead.

Installation 

gem 'libhoney'

# or, to follow the bleeding edge:
# gem 'libhoney', git: 'https://github.com/honeycombio/libhoney-rb.git'

This gem has some native dependencies, so if you see an error along the lines of “Failed to build gem native extension”, you may need to install the Ruby development headers and a C++ compiler. For example, on Ubuntu:

sudo apt-get install build-essential ruby-dev

Note that libhoney requires Ruby 2.3 or greater.

Initialization 

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

require 'libhoney'

libhoney = Libhoney::Client.new(writekey: 'YOUR_API_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:

  • Creation event = builder.event()
  • Adding fields event.add_field('key', 'val'), event.add(dataMap)
  • Transmission event.send()

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, such as hashes and objects 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, and thus, the return value does not 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 is 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:

  • metadata: the metadata you attached to the event to which this response corresponds
  • status_code: the HTTP status code returned by Honeycomb when trying to send the event. 2xx indicates success.
  • duration: the number of seconds it took to send the event.
  • error: when the event does not even get to create a HTTP attempt, the reason will be in this field. For example, when sampled or dropped because of a queue overflow.

You do not have to process responses if you are not interested in them. 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 an Event 

require 'libhoney'

libhoney = Libhoney::Client.new(writekey: 'YOUR_API_KEY',
                                dataset: 'honeycomb-ruby-example')

ev = libhoney.event
ev.add({
  'duration_ms': 153.12,
  'method': 'get',
  'hostname': 'appserver15',
  'payload_length': 27
})
ev.send

# 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) # Timestamp is a special field that must be set via the timestamp method, it cannot be set by the add_field method
event.send()

Check out the documentation for Libhoney::Client for more detailed API documentation.

Further examples can be found on GitHub.

Proxy Configuration 

Libhoney will use any https_proxy, http_proxy, and no_proxy environment variables set for the process for all event traffic to Honeycomb.

Note
Proxy configuration via environment variables was added in Libhoney version 1.18.0. Proxy support was added in Libhoney version 1.14.0 and was configured via a proxy_config Array passed during client initialization. Array-style proxy configuration will be deprecated in Libhoney 2.0.
require 'libhoney'

libhoney = Libhoney::Client.new(writekey: 'YOUR_API_KEY',
                                dataset: 'honeycomb-ruby-example'
                                proxy_config: ['myproxy.example.com', 8080])

# ... or if your proxy requires authentication

libhoney = Libhoney::Client.new(writekey: 'YOUR_API_KEY',
                                dataset: 'honeycomb-ruby-example'
                                proxy_config: ['myproxy.example.com', 8080, 'username', 'password'])

# ... Do work and capture events

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

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 have 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, such as hostname, environment, and so on. 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.

Debugging Instrumentation 

If you have instrumented your code to send events to Honeycomb, you may want to verify that you are sending the events you expected at the right time with the desired fields. To support this use case, libhoney provides a LogClient that outputs events to standard error, which you can swap in for the usual Client.

Example usage:

honeycomb = Libhoney::LogClient.new

my_app = MyApp.new(..., honeycomb, ...)
my_app.do_stuff

# should output events to standard error

Note that this change will disable sending events to Honeycomb, so you will want to revert this change once you have verified that the events are coming through appropriately.

Testing Instrumented Code 

Once you have instrumented your code to send events to Honeycomb, you may want to consider writing tests that verify your code is producing the events you expect, annotating them with the right information, and so on. That way, if your code changes and breaks the instrumentation, you will find out straight away, instead of at 3 a.m. when you need that data available for debugging!

To support this use case, libhoney provides a TestClient, which you can swap in for the usual Client.

Example usage:

fakehoney = Libhoney::TestClient.new

my_app = MyApp.new(..., fakehoney, ...)
my_app.do_stuff

expect(fakehoney.events.size).to eq 3

first_event = fakehoney.events[0]
expect(first_event.data['hovercraft_contents']).to eq 'Eels'

For more details, see the docs for TestClient and Event.

Troubleshooting 

Refer to Common Issues with Sending Data in Honeycomb.

Contributions 

We gladly accept features, bug fixes, and other changes to libhoney. 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.