Ross Hagan

How to add Honeycomb to a Remix.run app on Cloudflare Workers

January 2, 2022

With a new paradigm of cloudflare workers and other functions at the edge, and their alluring promise of no cold starts and great performance, one thing we especially want to keep is observability.

Who doesn't want to see those low low numbers in a pretty graph? Knowing what our systems are doing remains evergreen in value. So using honeycomb is a pretty sensible choice.

So how does telemetry translate to this new world?

Key Takeaways

  • How to get the most basic shape of data from a Cloudflare Worker into honeycomb
  • Basic steps to instrumenting a Remix app

Adding Honeycomb to Cloudflare Workers

This is focused on a remix.run application, so we'll assume that you have already got a Remix app targeting the Workers deployment strategy.

You'll need to install the cloudflare package:

npm install @cloudflare/workers-honeycomb-logger

Then your worker/index.js needs updating to look like:

import {
  createEventHandler,
} from "@remix-run/cloudflare-workers";
import {
  wrapEventListener,
} from '@cloudflare/workers-honeycomb-logger'
import * as build from "../build";

const hc_config = {
  apiKey: HONEYCOMB_API_KEY,
  dataset: 'test-dataset',
}

addEventListener("fetch",
  wrapEventListener(
    hc_config,
    createEventHandler({
      build,
      // Puts the tracer into your Remix `context`        
      getLoadContext: (event) => {
        return {
          tracer: event.request.tracer
        }
      }
    })
  ),
);

That's it... you're getting data into honeycomb. Well, except you're probably not seeing it appear just yet outside of the raw data.

Getting raw data to traces

You've got data coming into a dataset, but it's really just raw JSON and honeycomb isn't converting it to traces. So, once you've got a dataset in Honeycomb:

  1. Go to Datasets on the admin page
  2. Go to Settings for your dataset
  3. In the dataset's settings access the Schema tab
  4. Turn on automatically unpack nested json and set a depth of 2

That alone should start getting you traces.

You might also want to take a look at the Definitions tab, and do some mapping between the raw input and what you want honeycomb to use.

Instrumenting the Remix app code

So you'll have noticed in the worker/index.js that we have added a getLoadContext function which receives the original FetchEvent. Thanks to the wrapEventListener that fetch event now has the tracer attached to the request, so we pass it along into remix.

This means that in your app code, you can access the tracer. For example, in an action function you can pull in the context, which has the tracer on it.

export const action: ActionFunction = async ({ request, context }) => {
    const authSpan = context.tracer.startChildSpan('authorize');
    const authorizeResult = await authorize(request);
    authSpan.finish();
    // other code
}

Making fetch (traces) happen

(Unoriginal, but who doesn't love a Mean Girls quote...)

Automatic tracing of fetches doesn't come for free, but the tracer utility provides a fetch function that wraps native fetch. So using the context.tracer.fetch will give you wrapped fetch calls pretty easily.

This came in handy for tracing fauna database calls in a pretty trivial way. You can of course put in manual spans before and after calls, but thankfully the faunadb Client accepts a fetch parameter, so you can just pass in the tracer's wrapped fetch.

const faunaClient = new faunadb.Client({
    domain: FAUNA_DB_DOMAIN,
    secret: accessToken,
    fetch: (url, init) => context.tracer.fetch(url, init)
});

Marvel at your beautiful fetches coming through!

Wrap-up

I had originally had this article comment on the limited insights - thinking they were limited to the top level request.

Humble thanks to Liz Fong-Jones pointing me in the direction of the original cloudflare honeycomb logger library author, Erwin var der Koogh, I was prompted to dig in further.

So the picture is much better than I'd originally thought on first pass, we can get those beautiful insights still. Maybe not as easily as a native node.js environment and automatic instrumentation, but this might also be something I just need to spend more time on learning!

Looking forward to seeing how to instrument the frontend of a remix app with Honeycomb next, too!