Send Browser Data with Honeycomb Web Instrumentation | Honeycomb

Send Browser Data with Honeycomb Web Instrumentation

Honeycomb provides the Honeycomb Web Instrumentation package to help you instrument your applications and send browser data to Honeycomb as quickly and easily as possible. Under the hood, the Honeycomb Web Instrumentation package uses OpenTelemetry for JavaScript.

Although automatic instrumentation for the web exists, you will get the most value by supplementing automatically-generated instrumentation in your application with custom spans and events. To add custom spans and events, which is known as custom instrumentation, use the full OpenTelemetry API.

If you prefer to learn by example, we provide several examples that configure applications to send OpenTelemetry data to Honeycomb.

Why Use the Honeycomb Web Instrumentation package? 

Honeycomb’s Web Instrumentation package gives you all of the capabilities provided by the generic OpenTelemetry Distribution for JavaScript and also allows you to:

  • more easily initialize OpenTelemetry for use with Honeycomb
  • more easily add multi-span attributes
  • more easily configure sampling
  • link directly to your traces in Honeycomb with local visualizations
  • keep any custom instrumentation if you switch to the generic OpenTelemetry Distribution later
  • add automatic instrumentation for Core Web Vitals and customize attributes on those spans

Before You Begin 

Before you can set up instrumentation for your application, you will need to do a few things.

Get Your Honeycomb API Key 

To send data to Honeycomb, you need to:

  1. Sign up for a free Honeycomb account. To sign up, decide whether you would like Honeycomb to store your data in a US-based or EU-based location, then create a Honeycomb account in the US or create a Honeycomb account in the EU. Signup is free!
  2. Create a Honeycomb Ingest API Key. To get started, you can create a key that you expect to swap out when you deploy to production. Name it something helpful, perhaps noting that it’s a Getting Started key. Make note of your API key; for security reasons, you will not be able to see the key again, and you will need it later!
Tip
For setup, make sure you select the “Can create datasets” checkbox so that your data will show up in Honeycomb. Later, when you replace this key with a permanent one, you can uncheck that box.

Install the Honeycomb Web Instrumentation Package 

We make our Honeycomb Web Instrumentation package available as an NPM package, so you can include it in your web bundle.

Navigate to the root directory of your service’s repo, and then install the package:

npm install @honeycombio/opentelemetry-web @opentelemetry/auto-instrumentations-web
yarn add @honeycombio/opentelemetry-web @opentelemetry/auto-instrumentations-web
Tip
If your repo does not contain a yarn.lock file, install with NPM.
Module Description
auto-instrumentations-web OpenTelemetry’s meta package that includes various web automatic instrumentation including request and document load instrumentation.
opentelemetry-web Honeycomb’s Web Instrumentation package that streamlines configuration and allows you to instrument as quickly and easily as possible.

Alternatively, install individual instrumentation packages.

Finally, confirm that the install was successful by opening your package.json file and checking that the Dependencies list now contains @honeycomb/opentelemetry-web.

Configuration Options for Browser Telemetry 

Before adding automatic instrumentation to your application for browser telemetry, choose from the following options to decide how you will send data to Honeycomb. Consider the security aspects of each option.

Tip

Sensitive data is never safe in the browser. We recommend setting up some kind of proxy to accept browser traces before sending them to Honeycomb. This will make sure you are not exposing your Honeycomb API keys in the browser application’s code.

The most common and recommended solution is to use an OpenTelemetry Collector. By using a Collector, you can store sensitive credentials there and ensure that any data being sent to Honeycomb is processed safely (along with any additional data control enabled by the Collector).

Browser code that sends requests directly to Honeycomb requires an API key. This exposes your Honeycomb data to the public. To limit that exposure, we strongly recommend that you create a new API key with permissions that only allows Send Events. This limits the impact of the key and enables you to rotate it often.

The endpoint for the HoneycombWebSDK defaults to sending to the Honeycomb US endpoint https://api.honeycomb.io/v1/traces, so no changes need to be made for the endpoint and the API Key can be set directly in the code. To send data to the Honeycomb EU endpoint, uncomment endpoint in the example below.

The OpenTelemetry initialization needs to happen as early as possible in the webpage. Accomplish this by creating an initialization file, similar to the JavaScript example below.

// index.js or main.js

// other import statements...
import { HoneycombWebSDK } from '@honeycombio/opentelemetry-web';
import { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web';

const configDefaults = {
  ignoreNetworkEvents: true,
  // propagateTraceHeaderCorsUrls: [
  // /.+/g, // Regex to match your backend URLs. Update to the domains you wish to include.
  // ]
}

const sdk = new HoneycombWebSDK({
  // endpoint: "https://api.eu1.honeycomb.io/v1/traces", // Send to EU instance of Honeycomb. Defaults to sending to US instance.
  debug: true, // Set to false for production environment.
  apiKey: '[YOUR API KEY HERE]', // Replace with your Honeycomb Ingest API Key.
  serviceName: '[YOUR APPLICATION NAME HERE]', // Replace with your application name. Honeycomb uses this string to find your dataset when we receive your data. When no matching dataset exists, we create a new one with this name if your API Key has the appropriate permissions.
  instrumentations: [getWebAutoInstrumentations({
    // Loads custom configuration for xml-http-request instrumentation.
    '@opentelemetry/instrumentation-xml-http-request': configDefaults,
    '@opentelemetry/instrumentation-fetch': configDefaults,
    '@opentelemetry/instrumentation-document-load': configDefaults,
  })],
});
sdk.start();

// application instantiation code

To receive browser telemetry, the Collector requires an enabled OTLP/HTTP receiver. The allowed_origins property is required to enable Cross-Origin Resource Sharing (CORS) from the browser to the collector. The Collector will need to be accessible by the browser. It is recommended to put an external load balancer in front of the collector, which will also need to be configured to accept requests from the browser origin.

In the example below, the configuration allows for the OpenTelemetry Collector to accept browser OpenTelemetry tracing, and is required to get data from the browser to Honeycomb.

receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318
        cors:
          allowed_origins:
            - "http://*.<yourdomain>.com"
            - "https://*.<yourdomain>.com"

processors:
  batch:

exporters:
  otlp:
    endpoint: "api.honeycomb.io:443" # US instance
    #endpoint: "api.eu1.honeycomb.io:443" # EU instance
    headers:
      "x-honeycomb-team": "YOUR_API_KEY"

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp]

More configuration options can be found on the Collector Github Repository.

The endpoint for the HoneycombWebSDK defaults to sending to the Honeycomb US endpoint https://api.honeycomb.io/v1/traces. To send data to the Honeycomb EU endpoint, uncomment endpoint in the example below.

To send to a Collector, update the endpoint and omit your API Key, because that will be set in the Collector itself.

// index.js or main.js

// other import statements...
import { HoneycombWebSDK } from '@honeycombio/opentelemetry-web';
import { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web';

const configDefaults = {
  ignoreNetworkEvents: true,
  // propagateTraceHeaderCorsUrls: [
  // /.+/g, // Regex to match your backend URLs. Update to the domains you wish to include.
  // ]
}

const sdk = new HoneycombWebSDK({
  debug: true, // turn off in production
  serviceName: '[YOUR APPLICATION NAME HERE]', // Replace with your application name. Honeycomb uses this string to find your dataset when we receive your data. When no matching dataset exists, we create a new one with this name if your API Key has the appropriate permissions.
  endpoint: '[YOUR COLLECTOR URL]',
  instrumentations: [getWebAutoInstrumentations({
    // Loads custom configuration for xml-http-request instrumentation.
    '@opentelemetry/instrumentation-xml-http-request': configDefaults,
    '@opentelemetry/instrumentation-fetch': configDefaults,
    '@opentelemetry/instrumentation-document-load': configDefaults,
  })],
});
sdk.start();

// application instantiation code

There may already be an OpenTelemetry Collector in use by other applications. If a Collector is already in use in your environment, it may be possible to make the necessary changes to the existing Collector configuration without needing to deploy something new. See more details about setting up the OpenTelemetry Collector, including deployment patterns to consider.

An alternative to the OpenTelemetry Collector is to create your own custom proxy endpoint. Update the HoneycombWebSDK with the URL of the custom endpoint and omit your API Key, because that will be set in your proxy.

// index.js or main.js

// other import statements...
import { HoneycombWebSDK } from '@honeycombio/opentelemetry-web';
import { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web';

const configDefaults = {
  ignoreNetworkEvents: true,
  // propagateTraceHeaderCorsUrls: [
  // /.+/g, // Regex to match your backend URLs. Update to the domains you wish to include.
  // ]
}

const sdk = new HoneycombWebSDK({
  debug: true, // Set to false for production environment.
  serviceName: '[YOUR APPLICATION NAME HERE]', // Replace with your application name. Honeycomb uses this string to find your dataset when we receive your data. When no matching dataset exists, we create a new one with this name if your API Key has the appropriate permissions.
  endpoint: '[YOUR PROXY ENDPOINT HERE]',
  instrumentations: [getWebAutoInstrumentations({
    // Loads custom configuration for xml-http-request instrumentation.
    '@opentelemetry/instrumentation-xml-http-request': configDefaults,
    '@opentelemetry/instrumentation-fetch': configDefaults,
    '@opentelemetry/instrumentation-document-load': configDefaults,
  })],
});
sdk.start();

// application instantiation code

Set the environment variables like the API Key and Honeycomb endpoint in the server-side code to keep it hidden from public view.

For example, to setup a proxy using a basic Express server, include the following in a post to either https://api.honeycomb.io/v1/traces (if you are using our US instance) or https://api.eu1.honeycomb.io/v1/traces (if you are using our EU instance):

const options = {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-honeycomb-team': process.env.HONEYCOMB_API_KEY,
  },
  body: JSON.stringify(otlpJsonExportedFromFrontend),
};

The server will also need cors setup to allow connections from the browser, and will need to be able to parse json.

Example Express Proxy 

To accept POSTs to http://localhost:3000/v1/traces from the browser at http://localhost:5000 to then send on to Honeycomb, the entire code might look like this:

// frontend.js
const sdk = new HoneycombWebSDK({
  endpoint: "http://localhost:3000/v1/traces",
  serviceName: "your-service-name",
  instrumentations: [getWebAutoInstrumentations()],
})
// backend.js
import { config } from 'dotenv';
import express from 'express';
import fetch from 'node-fetch';
import cors from 'cors';

const app = express();
const port = 3000;

app.use(
  cors({
    origin: ['http://localhost:5000', 'http://127.0.0.1:5000'],
    methods: ['POST'],
    credentials: true,
  }),
);
// Allow parsing of json
app.use(express.json());

// our api relay route
app.post('/v1/traces', async (req, res) => {
  try {
    const otlpJsonExportedFromFrontend = await req.body;

    const options = {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-honeycomb-team': process.env.HONEYCOMB_API_KEY,
      },
      body: JSON.stringify(otlpJsonExportedFromFrontend),
    };

    // sending on to Honeycomb
    const response = await fetch('https://api.honeycomb.io/v1/traces', options) // US instance
    //const response = await fetch('https://api.eu1.honeycomb.io/v1/traces', options) // EU instance
      .then((response) => console.log(response))
      .catch((err) => console.error(err));

    return res.json({
      success: true,
      response,
    });
  } catch (err) {
    return res.status(500).json({
      success: false,
      message: err.message,
    });
  }
});

app.listen(port, () => console.log(`Server listening on port ${port}!`));

If your framework has server-side api routes that separate server-side code from the client-side bundle, that may be a viable option to consider for this endpoint.

Advanced Configuration 

This is the complete list of configuration options for the Honeycomb Web Instrumentation package.

name required? type default value description
apiKey required* string Honeycomb API Key for sending traces directly to Honeycomb.
serviceName optional string unknown_service The name of this browser application. Your telemetry will appear in a Honeycomb dataset with this name.
localVisualizations optional boolean false For each trace created, print a link to the console so that you can find it in Honeycomb. Useful in development environments, but do not use in production.
sampleRate optional number 1 If you want to send a random fraction of traces, then make this a whole number greater than 1. Only 1 in sampleRate traces will be sent, and the rest never be created.
tracesEndpoint optional string ${endpoint}/v1/traces Populate this to send traces to a route other than /v1/ traces.
debug optional boolean false Enable additional logging.
dataset optional string Populate this option only if your Honeycomb environment is a Honeycomb Classic environment.
skipOptionsValidation optional boolean false Do not require any fields.* Use with OpenTelemetry Collector.
spanProcessor optional function Allows you to use an OpenTelemetry SimpleSpanProcessor to modify all spans.
Note
* API key and service name configuration options are required if sending data to Honeycomb directly. If using an OpenTelemetry Collector, configure your API key at the Collector level instead.

Network Events and Telemetry Volume 

In the Honeycomb Web Instrumentation, we pass in the ignoreNetworkEvents: true configuration to all automatic instrumentation packages. This configuration option reduces the number of events emitted from OpenTelemetry by about 90%. This configuration also means you do not collect network timing for all network requests, such as domainLookup, requestStart, requestEnd, and so on.

IgnoreNetworkEvents defaults to false. If you wish to collect timing events, you can remove the ignoreNetworkEvents: true call for one or all of the instrumentations that the configDefaults object is passed into, similar to the below example:

  // Turn on for fetch requests only.

  const configDefaults = {
    ignoreNetworkEvents: true, // <- no change
    // propagateTraceHeaderCorsUrls: [
    // /.+/g, // Regex to match your backend URLs. Update to the domains you wish to include.
    // ]
  }

  instrumentations: [getWebAutoInstrumentations({
    // Loads custom configuration for xml-http-request instrumentation.
    '@opentelemetry/instrumentation-xml-http-request': configDefaults,
    // '@opentelemetry/instrumentation-fetch': configDefaults,
    '@opentelemetry/instrumentation-document-load': configDefaults,
  })],

  /**************************************/
  // Turn on for all requests.

  const configDefaults = {
    ignoreNetworkEvents: false, // <- Changed from true
    // propagateTraceHeaderCorsUrls: [
    // /.+/g, // Regex to match your backend URLs. Update to the domains you wish to include.
    // ]
  }

  instrumentations: [getWebAutoInstrumentations({
    // Loads custom configuration for xml-http-request instrumentation.
    '@opentelemetry/instrumentation-xml-http-request': configDefaults,
    '@opentelemetry/instrumentation-fetch': configDefaults,
    '@opentelemetry/instrumentation-document-load': configDefaults,
  })],

Add Application-Specific Resource Attributes 

To optimize your data collection, we recommend that you add custom attributes that are specific to your application to every span. You can specify extra attributes through the resourceAttributes configuration option. This data will be available on every span your instrumentation emits, which makes it easier to correlate your data to important business information.

// index.js or main.js

// other import statements...
import { HoneycombWebSDK } from '@honeycombio/opentelemetry-web';
import { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web';

const configDefaults = {
  ignoreNetworkEvents: true,
  // propagateTraceHeaderCorsUrls: [
  // /.+/g, // Regex to match your backend URLs. Update to the domains you wish to include.
  // ]
}

const sdk = new HoneycombWebSDK({
  // endpoint: "https://api.eu1.honeycomb.io/v1/traces", // Send to EU instance of Honeycomb. Defaults to sending to US instance.
  debug: true, // Set to false for production environment.
  apiKey: '[YOUR API KEY HERE]', // Replace with your Honeycomb Ingest API Key.
  serviceName: '[YOUR APPLICATION NAME HERE]', // Replace with your application name. Honeycomb uses this string to find your dataset when we receive your data. When no matching dataset exists, we create a new one with this name if your API Key has the appropriate permissions.
  instrumentations: [getWebAutoInstrumentations({
    // Loads custom configuration for xml-http-request instrumentation.
    '@opentelemetry/instrumentation-xml-http-request': configDefaults,
    '@opentelemetry/instrumentation-fetch': configDefaults,
    '@opentelemetry/instrumentation-document-load': configDefaults,
  })],
  resourceAttributes: { // Data in this object is applied to every trace emitted.
    "user.id": user.id, // Specific to your app.
    "user.role": user.role, // Specific to your app.
  },
});
sdk.start();

// Application instantiation code

Connecting the Frontend and Backend Traces 

You can connect your frontend request traces to your backend traces, which allows you to trace a request all the way from your browser through your distributed system. To connect your frontend traces to your backend traces, you must include the trace context header in the request. You can do this using either automatic instrumentation or manual instrumentation.

Automatically Propagate the Trace Context Header 

Use request automatic instrumentation to automatically send spans for every HTTP request. @opentelemetry/instrumentation-xml-http-request automatically instruments XHR requests and @opentelemetry/instrumentation-fetch automatically instruments fetch requests. If your browser application uses a request library to make requests, such as axios or superagent, these requests are also automatically instrumented by enabling the xml-http-request or fetch instrumentation, depending on what the library uses to make requests.

When using the Honeycomb Instrumentation snippet (as documented on this page), uncomment the propagateTraceHeaderCorsUrls array and add regex to include all target domains. This method allows you to propagate to your backend services without leaking trace IDs to third-party services.

const configDefaults = {
  ignoreNetworkEvents: true,
  propagateTraceHeaderCorsUrls: [
    /.+/g, // Regex to match your backend URLs. Update to the domains you wish to include.
  ]
}

Manually Propagate the Trace Context Header 

It is also possible to manually propagate the trace context header if automatic instrumentation is not an option:

// General request handler, instrumented with OTel
// Forwards traceparent header to connect spans created in the browser
// with spans created on the backend
const request = async (url, method = 'GET', headers, body) => {
  return trace
    .getTracer('request-tracer')
    .startActiveSpan(`Request: ${method} ${url}`, async (span) => {

      // construct W3C traceparent header
      const traceparent = `00-${span.spanContext().traceId}-${span.spanContext().spanId}-01`;

      try {
        const response = await fetch(url, {
          method,
          headers: {
            ...headers,
            // set traceparent header
            traceparent: traceparent,
          },
          body,
        });
        span.setAttributes({
          'http.method': method,
          'http.url': url,
          'response.status_code': response.status,
        });
        if (response.ok && response.status >= 200 && response.status < 400) {
          span.setStatus({ code: SpanStatusCode.OK });
          return response.text();
        } else {
          throw new Error(`Request Error ${response.status} ${response.statusText}`);
        }
      } catch (error) {
        span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
        throw new Error(error);
      } finally {
        span.end();
      }
    });
};

Other Automatic Instrumentation 

There are several automatic instrumentation libraries available for the browser. They tend to produce a lot of span events so we recommend adding them with some caution.

npm install --save \
    @opentelemetry/instrumentation-user-interaction \
    @opentelemetry/instrumentation-long-task
yarn add \
    @opentelemetry/instrumentation-user-interaction \
    @opentelemetry/instrumentation-long-task

User Interaction 

@opentelemetry/instrumentation-user-interaction produces spans for user interactions with a browser web application. Import the package and add the output to the instrumentations array in the configuration:

import { UserInteractionInstrumentation } from '@opentelemetry/instrumentation-user-interaction';

const sdk = new HoneycombWebSDK({
  apiKey: '[YOUR API KEY HERE]', // Replace with your Honeycomb Ingest API Key.
  serviceName: '[YOUR APPLICATION NAME HERE]' // Replace with your application name. Honeycomb uses this string to find your dataset when we receive your data. When no matching dataset exists, we create a new one with this name if your API Key has the appropriate permissions.
  instrumentations: [getWebAutoInstrumentations({
      // Loads custom configuration for xml-http-request instrumentation.
      '@opentelemetry/instrumentation-xml-http-request': configDefaults,
      '@opentelemetry/instrumentation-fetch': configDefaults,
      '@opentelemetry/instrumentation-document-load': configDefaults,
    }),

    new UserInteractionInstrumentation(),
  ],
});

sdk.start();

By default, only click events are automatically instrumented. To automatically instrument other events, specify the events that should be captured for telemetry. Most browser events are supported.

import { UserInteractionInstrumentation } from '@opentelemetry/instrumentation-user-interaction';

const sdk = new HoneycombWebSDK({
  apiKey: '[YOUR API KEY HERE]', // Replace with your Honeycomb Ingest API Key.
  serviceName: '[YOUR APPLICATION NAME HERE]' // Replace with your application name. Honeycomb uses this string to find your dataset when we receive your data. When no matching dataset exists, we create a new one with this name if your API Key has the appropriate permissions.
  instrumentations: [getWebAutoInstrumentations({
      // Loads custom configuration for xml-http-request instrumentation.
      '@opentelemetry/instrumentation-xml-http-request': configDefaults,
      '@opentelemetry/instrumentation-fetch': configDefaults,
      '@opentelemetry/instrumentation-document-load': configDefaults,
    }),

    new UserInteractionInstrumentation({
      eventNames: ['submit', 'click', 'keypress'],
    }),
  ],
});

sdk.start();

To attach extra attributes to user interaction spans, provide a callback function:

import { UserInteractionInstrumentation } from '@opentelemetry/instrumentation-user-interaction';

const sdk = new HoneycombWebSDK({
  apiKey: '[YOUR API KEY HERE]', // Replace with your Honeycomb Ingest API Key.
  serviceName: '[YOUR APPLICATION NAME HERE]' // Replace with your application name. Honeycomb uses this string to find your dataset when we receive your data. When no matching dataset exists, we create a new one with this name if your API Key has the appropriate permissions.
  instrumentations: [getWebAutoInstrumentations({
      // Loads custom configuration for xml-http-request instrumentation.
      '@opentelemetry/instrumentation-xml-http-request': configDefaults,
      '@opentelemetry/instrumentation-fetch': configDefaults,
      '@opentelemetry/instrumentation-document-load': configDefaults,
    }),

    new UserInteractionInstrumentation({
      eventNames: ['submit', 'click', 'keypress'],
      shouldPreventSpanCreation: (event, element, span) => {
        span.setAttribute('target.id', element.id)
        // etc..
      }
    }),
  ],
});

sdk.start();

Long Tasks 

@opentelemetry/instrumentation-long-task provides automatic instrumentation for Long Task API.

import { UserInteractionInstrumentation } from '@opentelemetry/instrumentation-user-interaction';

const sdk = new HoneycombWebSDK({
  apiKey: '[YOUR API KEY HERE]', // Replace with your Honeycomb Ingest API Key.
  serviceName: '[YOUR APPLICATION NAME HERE]' // Replace with your application name. Honeycomb uses this string to find your dataset when we receive your data. When no matching dataset exists, we create a new one with this name if your API Key has the appropriate permissions.
  instrumentations: [getWebAutoInstrumentations({
      // Loads custom configuration for xml-http-request instrumentation.
      '@opentelemetry/instrumentation-xml-http-request': configDefaults,
      '@opentelemetry/instrumentation-fetch': configDefaults,
      '@opentelemetry/instrumentation-document-load': configDefaults,
    }),

    new LongTaskInstrumentation({
      observerCallback: (span, longtaskEvent) => {
        span.setAttribute('location.pathname', window.location.pathname)
      }
    }),
  ],
});

sdk.start();

Add Custom Instrumentation 

Automatic instrumentation is the easiest way to get started with instrumenting your code, but to get the most insight into your system, you should add custom, or manual, instrumentation where appropriate. To do this, use the OpenTelemetry API to access the currently executing span and add attributes to it, and/or to create new spans.

To learn more about custom, or manual, instrumentation, visit the comprehensive set of topics covered by Manual Instrumentation for JavaScript in OpenTelemetry’s documentation.

Acquire Dependencies 

Adding custom instrumentation requires the the OpenTelemetry API package. If not installed yet, use this command for installation:

npm install --save @opentelemetry/api
yarn add @opentelemetry/api

To customize your instrumentation, you need to add instrumentation for actions. Refer to the sample code below that instruments the window load event and a button:

import { trace, context, } from '@opentelemetry/api';

const tracer = trace.getTracer();

const rootSpan = tracer.startActiveSpan('document_load', span => {
  //start span when navigating to page
  span.setAttribute('pageUrlwindow', window.location.href);
  window.onload = (event) => {
    // ... do loading things
    // ... attach timing information
    span.end(); //once page is loaded, end the span
  };

  button.clicked = (event) => {
    tracer.startActiveSpan('button_clicked', btnSpan => {
      // Add your attributes to describe the button clicked here
      btnSpan.setAttribute('some.attribute', 'some.value');

      btnSpan.end();
    });
  }
});

Add Attributes to Spans 

Adding attributes to a currently executing span in a trace can be useful. For example, you may have an application or service that handles users, and you want to associate the user with the span when querying your service in Honeycomb. To do this, get the current span from the context and set an attribute with the user ID:

import opentelemetry from '@opentelemetry/api';

function handleUser(user: User) {
  let activeSpan = opentelemetry.trace.getActiveSpan();
  activeSpan.setAttribute("user.id", user.getId());
}
const opentelemetry = require("@opentelemetry/api");

function handleUser(user) {
  let activeSpan = opentelemetry.trace.getActiveSpan();
  activeSpan.setAttribute("user.id", user.getId());
}

This will add a user.id field to the current span so that you can use the field in WHERE, GROUP BY or ORDER clauses in the Honeycomb query builder.

Initialize a Tracer 

To start manually tracing, you must initialize a tracer.

import opentelemetry from '@opentelemetry/api';

const tracer = opentelemetry.trace.getTracer("tracer.name.here");
const opentelemetry = require("@opentelemetry/api");

const tracer = opentelemetry.trace.getTracer("tracer.name.here");

When you create a Tracer, OpenTelemetry requires you to give it a name as a string. This string is the only required parameter.

When traces are sent to Honeycomb, the name of the Tracer is turned into the library.name field, which can be used to show all spans created from a particular tracer.

In general, pick a name that matches the appropriate scope for your traces. If you have one tracer for each service, then use the service name. If you have multiple tracers that live in different “layers” of your application, then use the name that corresponds to that “layer”.

The library.name field is also used with traces created from instrumentation libraries.

You can then use this tracer to create custom spans.

Create New Spans 

Automatic instrumentation can show the shape of requests to your system, but only you know the truly important parts. To get the full picture of what is happening, you must add custom instrumentation and create some custom spans. To do this, grab the tracer from the OpenTelemetry API:

import opentelemetry from '@opentelemetry/api';

const tracer = opentelemetry.trace.getTracer("my-service-tracer");

function runQuery() {
  tracer.startActiveSpan("expensive-query", (span) => {
    // ... do cool stuff
    span.end();
  });
}
const opentelemetry = require("@opentelemetry/api");

const tracer = opentelemetry.trace.getTracer("my-service-tracer");

function runQuery() {
  tracer.startActiveSpan("expensive-query", (span) => {
    // ... do cool stuff
    span.end();
  });
}

Add Multi-Span Attributes 

Sometimes you want to add the same attribute to many spans within the same trace. This attribute may include variables calculated during your program, or other useful values for correlation or debugging purposes.

To add this attribute, leverage the OpenTelemetry concept of baggage. Baggage allows you to add a key with a value as an attribute to every subsequent child span within the current application context.

import {
  Context,
  context,
  propagation,
} from '@opentelemetry/api';

tracer.startActiveSpan('main', (span) => {
  span.setAttribute('app.username', name); // add to current span

  // new context based on current, with key/values added to baggage
  const ctx: Context = propagation.setBaggage(
    context.active(),
    propagation.createBaggage({ 'app.username': { value: name } })
  );

  // within the new context, do some work and baggage will be
  // applied as attributes on child spans
  context.with(ctx, () => {
    tracer.startActiveSpan('childSpan', (childSpan) => {
      doTheWork();
      childSpan.end();
    });
  });

  span.end();
});
tracer.startActiveSpan('main', (span) => {
  span.setAttribute('app.username', name); // add to current span

  // new context based on current, with key/values added to baggage
  const ctx = propagation.setBaggage(
    context.active(),
    propagation.createBaggage({ 'app.username': { value: name } })
  );

  // within the new context, do some work and baggage will be
  // applied as attributes on child spans
  context.with(ctx, () => {
    tracer.startActiveSpan('childSpan', (childSpan) => {
      doTheWork();
      childSpan.end();
    });
  });

  span.end();
});

Sampling 

Deterministic head sampling can be used with the Honeycomb Web Instrumentation package, with or without manual instrumentation.

The package will read these variables and expect an integer that represents the sample rate you would like to apply. For example, a value of 5 means that one out of every five traces will be sent to Honeycomb.

To add sampling to the package, add sampleRate to the HoneycombWebSDK:

const sdk = new HoneycombWebSDK({
  apiKey: "your-api-key",
  serviceName: "your-service-name",
  instrumentations: [getWebAutoInstrumentations()],
  sampleRate: 5,
})

The value of your sample rate must be a positive integer.

If you have multiple services that communicate with each other, it is important that they have the same sampling configuration. Otherwise, each service might make a different sampling decision, resulting in incomplete or broken traces. You can sample using a standalone proxy as an alternative, like Honeycomb Refinery, or when you have more robust sampling needs.

Distributed Trace Propagation 

When a service calls another service, you want to ensure that the relevant trace information is propagated from one service to the other. This allows Honeycomb to connect the two services in a trace.

Distributed tracing enables you to 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. In order to have traces connect spans for all these services, it is necessary to propagate trace context between these services, usually by using an HTTP header.

Both the sending and receiving service must use the same propagation format, and both services must be configured to send data to the same Honeycomb environment.

Note: Any Baggage attributes that you set in your application will be attached to outgoing network requests as a header. If your service communicates to a third party API, do NOT put sensitive information in the Baggage attributes.

Trace context propagation is done by sending and parsing headers that conform to the W3C Trace Context specification.

By default, the Honeycomb’s Web Instrumentation package uses the W3C trace context format.

If you opt to use a different trace context specification than W3C, ensure that both the sending and receiving service are using the same propagation format, and that both services are configured to send data to the same Honeycomb environment.

Visualizing Traces Locally 

Honeycomb’s Web Instrumentation package can create a link to a trace visualization in the Honeycomb UI for local traces. Local visualizations enables a faster feedback cycle when adding, modifying, or verifying instrumentation.

To enable local visualizations:

  1. Set the local visualizations option to true in the HoneycombWebSDK:

    const sdk = new HoneycombWebSDK({
      apiKey: "your-api-key",
      serviceName: "your-service-name",
      instrumentations: [getWebAutoInstrumentations()],
      localVisualizations: true,
    })
    

    The output displays the name of the root span and a link to Honeycomb that shows its trace. For example:

    Trace for root-span-name
    Honeycomb link: <link to Honeycomb trace>
    
  2. Select the link to view the trace in detail within the Honeycomb UI.

Note
In production, disable local visualization. Local visualization creates additional overhead to create the link to a trace in Honeycomb and print it to the console.

Debug Mode 

To enable debugging when running the Honeycomb Web Instrumentation package, set debug to true the HoneycombWebSDK:

const sdk = new HoneycombWebSDK({
  apiKey: "your-api-key",
  serviceName: "your-service-name",
  instrumentations: [getWebAutoInstrumentations()],
  localVisualizations: true,
  debug: true
})

When the debug setting is enabled, the Honeycomb Web Instrumentation package configures a DiagConsoleLogger that logs telemetry to the console with the log level of Debug.

The debug setting in the Honeycomb Web Instrumentation package will also output to the console the main options configuration, including but not limited to API Key and endpoint.

Keep in mind that printing to the console is not recommended for production and should only be used for debugging purposes.

OTLP Protobuf Definitions 

Honeycomb supports receiving telemetry data via OpenTelemetry’s native protocol, OTLP, over gRPC, HTTP/protobuf, and HTTP/JSON. The minimum supported versions of OTLP protobuf definitions are 0.7.0 for traces and metrics.

If the protobuf version in use by the SDK does not match a supported version by Honeycomb, a different version of the SDK may need to be used. If the SDK’s protobuf version is older than the minimum supported version, and telemetry is not appearing as expected in Honeycomb, upgrade the SDK to a version with the supported protobuf definitions. If using an added dependency on a proto library, ensure the version of protobuf definitions matches the supported version of the SDK.

Receiving 464 Errors 

You may receive a 464 error response from the Honeycomb API when sending telemetry using gRPC and HTTP1. The gRPC format depends on using HTTP2 and any request over HTTP1 will be rejected by the Honeycomb servers.

Troubleshooting 

To explore common issues when sending data, visit Common Issues with Sending Data in Honeycomb.