Send Logs with Libhoney for JavaScript

Libhoney for JavaScript is Honeycomb’s structured logging library for JavaScript 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.
Warning
For direct use in browser-side JavaScript applications, make sure to generate a separate API key that can only send events. Leaking an API key with those permissions would allow malicious users to access other data such as markers. See our Browser JS guide for more information on how you can more safely send data about your web app from the client to Honeycomb.

Installation 

npm install libhoney --save
yarn add libhoney

Initialization 

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

Using ES6 modules:

import Libhoney from "libhoney";

let hny = new Libhoney({
  writeKey: "YOUR_API_KEY",
  dataset: "honeycomb-js-example"
  // disabled: true // uncomment for testing or development.
});

Using require:

var Libhoney = require("libhoney");
var hny = new Libhoney({
  writeKey: "YOUR_API_KEY",
  dataset: "honeycomb-js-example"
});

Using a Proxy 

To route event traffic through a proxy, configure it by passing an additional item, like:

import Libhoney from "libhoney";

let hny = new Libhoney({
  proxy: "https://proxy-address-or-name:port",
  writeKey: "YOUR_API_KEY",
  dataset: "honeycomb-js-example"
  // disabled: true // uncomment for testing or development.
});

Find further configuration options in the source code.

Note
To silence libhoney in a test or development environment, include { disabled: true } in the new libhoney(...) initialization.

Building and Sending Events 

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

  • Creation event = libhoney.newEvent()
  • Adding fields event.addField("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. If you create events faster than they can be sent, overflowed events are 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 .addField(k, v) method. If you add the same key multiple times, only the last value added is kept.

Other JavaScript objects 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—calling .send() will enqueue the event to be sent as soon as possible. Assign a responseCallback 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 populating the .metadata attribute directly on an event.

For instance:

let hny = new Libhoney({
  writeKey: "YOUR_API_KEY",
  dataset: "honeycomb-js-example",
  responseCallback: responses => {
    responses.forEach(resp => {
      console.log(resp);
    });
  }
});

let ev = hny.newEvent();
ev.addField("latencyMs", 240);
ev.metadata = { id: "recognize-me-later" };
ev.send();

This will print out the asynchronous responses from sending the event or batch of events. A status code of 200 or 202 indicates the event was received by our API successfully, whereas other status codes could indicate issues such as write key authentication failures or rate limiting.

{ status_code: 202,
  duration: 320,
  metadata: {id: "recognize-me-later"},
  error: undefined }

Responses have a number of fields describing the result of an attempted event send:

  • 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 milliseconds 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—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 

import Libhoney from "libhoney";

let hny = new Libhoney({
  writeKey: "YOUR_API_KEY",
  dataset: "honeycomb-js-example"
});

hny.sendNow({
  message: "Test Honeycomb event",
  randomFloat: Math.random(),
  hostname: os.hostname(),
  favoriteColor: "chartreuse"
});

Intermediate: Override Some Attributes 

// ... Initialization code ...
let params = {
  hostname: "foo.local",
  built: false,
  userId: -1
};

hny.add(params);

let builder = hny.newBuilder({ built: true });

// Spawn a new event and override the timestamp
let event = builder.newEvent();
event.addField("userId", 15);
event.addField("latencyMs", Date.now() - start);
event.timestamp = new Date(Date.UTC(2016, 1, 29, 1, 1, 1));
event.send();

Further examples can be found on GitHub.

Middleware Examples: Express 

Express is light, flexible, and built to make it easy to drop in utility and middleware functionality to augment your application logic. Each inbound HTTP request as received by a framework like Express maps nicely to Honeycomb events, representing “a single thing of interest that happened” in a given system.

Express middleware functions are simply functions that have access to the request object, response object, and next middleware function in the application’s request-response chain.

As such, you can define a simple express-honey.js file as in the following:

import Libhoney from "libhoney";

module.exports = function(options) {
  let honey = new Libhoney(options);
  return function(req, res, next) {
    honey.sendNow({
      app: req.app,
      baseUrl: req.baseUrl,
      fresh: req.fresh,
      hostname: req.hostname,
      ip: req.ip,
      method: req.method,
      originalUrl: req.originalUrl,
      params: req.params,
      path: req.path,
      protocol: req.protocol,
      query: req.query,
      route: req.route,
      secure: req.secure,
      xhr: req.xhr
    });
    next();
  };
};

And, in your Express app.js, configure your new express-honey and include it in the execution path like the following:

let express = require("express");
let hny = require("./express-honey");
let app = express();

app.use(
  hny({
    apiHost: process.env["HONEY_API_HOST"],
    writeKey: process.env["YOUR_API_KEY"],
    dataset: process.env["HONEY_DATASET"]
  })
);

// ... additional Express handling code here

See the examples/ directory on GitHub for more sample code demonstrating how to use events, builders, fields, and dynamic fields, specifically in the context of Express middleware.

Advanced Usage: Using 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 (for example, hostname or environment). 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 .addDynamicField(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.

Troubleshooting 

Refer to Common Issues with Sending Data in Honeycomb for Libhoney.

Contributions 

Features, bug fixes and other changes to libhoney are gladly accepted. Please open issues or a pull request with your change.

All contributions will be released under the Apache License 2.0.