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.

Tip
If you’re using micro frontend architecture, visit Observability and Micro Frontends to see our recommendations for implementing OpenTelemetry and the Honeycomb OpenTelemetry Web SDK.

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();
});

Custom Span Processing 

The OpenTelemetry SDK uses span processors as synchronous hooks for when a span starts and when a span ends. This lets you mutate spans after they have been created by automatic instrumentation or manually. Some examples of the actions you can take on spans in a span processor include:

  • Add attributes
  • Add span events
  • Add span links
  • Update the name of a span
  • Record an exception on a span
  • Get the span context to create child spans
  • Stop spans from being sent

Example: Basic Custom Span Processor 

Here is a basic example of a custom span processor:

class TestSpanProcessorOne implements SpanProcessor {
  onStart(span: Span): void {
    span.setAttributes({
      'processor1.name': 'TestSpanProcessorOne',
    });
  }

  onEnd(): void {}

  forceFlush() {
    return Promise.resolve();
  }

  shutdown() {
    return Promise.resolve();
  }
}

To use it, add the span processor to your configuration when you initialize the Honeycomb Web SDK:

const sdk = new HoneycombWebSDK({
  debug: true,
  apiKey: 'api-key-goes-here',
  serviceName: 'hny-web-distro',
  // ... other config
  spanProcessors: [new TestSpanProcessorOne()],
});

sdk.start();

Example: Adding User Information After SDK Initialization 

Here is an example span processor that adds custom information for users:

import { SpanProcessor } from '@opentelemetry/sdk-trace-base';
import { Span } from '@opentelemetry/api';

export class UserInfoSpanProcessor implements SpanProcessor {
  userInfo: { userId: string; customerId: string; role: string } | undefined;
  constructor() {
    const getUserInfo = (): Promise<{
      userId: string;
      customerId: string;
      role: string;
    }> => {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve({
            userId: '1234',
            customerId: '5678',
            role: 'admin',
          });
        }, 2000);
      });
    };

    getUserInfo().then(
      (userInfo: { userId: string; customerId: string; role: string }) => {
        this.userInfo = userInfo;
      },
    );
  }

  onStart(span: Span) {
    if (this.userInfo) {
      span.setAttributes({
        'app.user.id': this.userInfo.userId,
        'app.user.customer_id': this.userInfo.customerId,
        'app.user.role': this.userInfo.role,
      });
    }
  }

  onEnd() {}

  forceFlush() {
    return Promise.resolve();
  }

  shutdown() {
    return Promise.resolve();
  }
}

To use it, add the span processor to your configuration when you initialize the Honeycomb Web SDK:

const sdk = new HoneycombWebSDK({
  debug: true,
  apiKey: 'api-key-goes-here',
  serviceName: 'hny-web-distro',
  // ... other configuration
  spanProcessors: [new UserInfoSpanProcessor()],
});

sdk.start();

Example: Dynamic page routes with React Router 

Here is an example span processor that adds attributes to spans based on the state of the React Router. It sets the page.route attribute to the generic dynamic route, and records the span as an error if there are errors in the router state.

import { SpanProcessor } from '@opentelemetry/sdk-trace-base';
import { Span } from '@opentelemetry/api';

/**
 * SpanProcessor that adds attributes to spans based on the state of the React Router
 * Sets the page.route attribute to the generic dynamic route
 * Records the span as an error if there are errors in the router state (e.g. 404)
 */
export class ReactRouterSpanProcessor implements SpanProcessor {
  router;
  route;
  constructor({ router }: { router }) {
    this.router = router;
    this.route =
      router.state.matches[router.state.matches.length - 1]?.route.path;

    this.router.subscribe((state: any) => {
      this.route = state.matches[state.matches.length - 1]?.route.path;
    });
  }

  onStart(span: Span) {
    const { errors } = this.router.state;

    // If there are errors, set the span status to error and record the error message
    if (errors !== null) {
      span.setStatus({
        code: 2,
        message: errors[0].data,
      });
    }

    // Set the page.route as the generic dynamic route, making things easier to query
    // e.g. /name/:name/pet/:pet instead of name/123/pet/456
    // url.path attribute will have the more specific computed route
    span.setAttributes({ 'page.route': this.route });
  }

  onEnd() {}

  forceFlush() {
    return Promise.resolve();
  }

  shutdown() {
    return Promise.resolve();
  }
}

To use it, add the span processor to your configuration when you initialize the Honeycomb Web SDK:

const sdk = new HoneycombWebSDK({
  debug: true,
  apiKey: 'api-key-goes-here',
  serviceName: 'hny-web-distro',
  // ... other configuration
  spanProcessors: [new ReactRouterSpanProcessor({ router: router })],
});

sdk.start();

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.

Dataset Not Appearing in Honeycomb 

The apiKey variable is used to send your data to Honeycomb. Make sure you have replaced the placeholder value for it with your Honeycomb Ingest API Key and that your API key permissions include “Can create datasets”.

If Honeycomb is successfully instantiating but your API key is not included, you should see output similar to the following in your browser console:

@opentelemetry/api: Registered a global for diag v1.7.0
@honeycombio/opentelemetry-web: Honeycomb Web SDK Debug Mode Enabled
@honeycombio/opentelemetry-web: API Key configured for traces: '<YOUR_API_KEY>'
@honeycombio/opentelemetry-web: Service Name configured for traces: '<YOUR_SERVICE_NAME>'
@honeycombio/opentelemetry-web: Endpoint configured for traces: 'https://api.honeycomb.io/v1/traces'
@honeycombio/opentelemetry-web: Sample Rate configured for traces: '1'

Dataset in Honeycomb Has Unexpected Name 

We use the serviceName variable to name your dataset in Honeycomb. Be sure you have replaced the placeholder value for it with a name that you will find useful.

Next.js Application “Navigator Is Undefined” Error 

If a “navigator is undefined” error appears when you attempt to start your local server while following Next.js instructions, the instrumentation is being run in a server-side rendering path.

To fix this, try adding the 'use client'; directive at the top of the file where you instantiate Honeycomb’s web instrumentation. Adding the 'use client'; directive tells React to only execute the file in a client environment.

If you are still seeing an error after adding the 'use client'; directive, try wrapping the function in a try/catch block. By wrapping the function, you can catch the error and avoid instantiation in server-side environments, ensuring that your application starts up even if the code is executed in a server-side environment.

try {
 const sdk = new HoneycombWebSDK({ 
    debug: true, 
    apiKey: '[YOUR API KEY HERE]' // Replace with your Honeycomb Ingest API Key  
    serviceName: '[YOUR APPLICATION NAME HERE]', // Replace with your application name. Honeycomb will name your dataset using this variable. 
    instrumentations: [getWebAutoInstrumentations()], // Adds automatic instrumentation  
    }); 
    
    sdk.start(); 
 } catch (e) {}

Available Attributes 

Every default attribute available in the Honeycomb Web Instrumentation package.

Browser Resource Attributes 

Attribute Description Example Value
user_agent.original User agent string of the browser. Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36
browser.mobile Indicates whether the browser is on a mobile device. true, false
browser.touch_screen_enabled Indicates whether the device has a touch screen. true, false
browser.language Language setting of the browser. en-US
browser.name Name of the browser. Chrome
browser.version Browser version. 91.0.4472.114
device.type Device type. desktop, tablet, mobile
network.effectiveType Effective network type. 5g, 4g, 3g
screen.width Screen width in pixels. 1440
screen.height Screen height in pixels. 900
screen.size Computed size of the screen based on width. large

Entry Page Resource Attributes 

Attribute Description Example
entry_page.url Full URL of the entry page. https://example.com
entry_page.path Path of the URL. /home
entry_page.search Query string of the URL. ?query=example
entry_page.hash Fragment identifier of the URL. #section1
entry_page.hostname Hostname of the URL. example.com
entry_page.referrer Referrer URL of the entry page. https://referrer.com

Honeycomb Metadata Resource Attributes 

Attribute Description Example
honeycomb.distro.version Version of the Honeycomb OpenTelemetry distribution. 1.0.0
honeycomb.distro.runtime_version Runtime environment of the Honeycomb OpenTelemetry distribution. browser

Browser span attributes 

Attribute Description Example
browser.width Inner width of the browser window. 1920
browser.height Inner height of the browser window. 1080
page.url Full URL of the page. https://example.com
page.hash Hash portion of the page URL. #section1
page.route Pathname of the page URL. /home
page.hostname Hostname of the page URL. example.com
page.search Query string portion of the page URL. ?query=example
url.path Pathname of the URL. /home
session.id Randomly generated session ID. Generated during SDK initialization and does not persist in storage. 004f84cb95b6cd7bd00e5f38b37e2f7e

Largest Contentful Paint (LCP) Attributes 

Attribute Description Example
lcp.id Unique identifier for the metric. v4-1724854181686-4396946267816
lcp.delta Change in value since the last measurement in milliseconds. 200
lcp.value Current value of LCP, in milliseconds. Measures how long it took the largest element on the page to paint. 200
lcp.rating Metric rating. good, needs-improvement, poor
lcp.navigation_type Type of navigation that occurred. restore, navigate, reload, back-forward, prerender, back-forward-cache
lcp.element HTML element associated with the metric. #my-element>div.menu>h1
lcp.url URL of the LCP image resource. If the LCP element is a text node, this value will not be set. https://my-image.com/image.png
lcp.time_to_first_byte Time between when the user initiates loading the page and the browser receives the first byte of the response (TTFB), in milliseconds. 200
lcp.resource_load_delay Delta between TTFB and when the browser starts loading the LCP resource, in milliseconds. If there is no resource, returns 0. 400
lcp.resource_load_duration Total time it takes to load the LCP resource, in millseconds. If no resource exists, returns 0. 200
lcp.element_render_delay Delta between when the LCP resource finishes loading and the LCP element is fully rendered, in milliseconds. 200
lcp.resource_load_time Same as lcp.resource_load_duration. Will be deprecated in a future version. 200

Cumulative Layout Shift (CLS) Attributes 

Attribute Description Example
cls.id Unique identifier for the metric. v4-1724856546003-8941918093361
cls.delta Change in value since the last measurement. 0.1
cls.value Current value of CLS. Tracks how much visible content shifts in the viewport. 0.1
cls.rating Metric rating. good, needs-improvement, poor
cls.navigation_type Type of navigation that occurred. restore, navigate, reload, back-forward, prerender, back-forward-cache
cls.largest_shift_target Selector identifying the first element (in document order) that shifted when the single largest layout shift contributing to the page’s CLS score occurred. #my-element>div.menu>h1
cls.element Same as cls.largest_shift_target. #my-element>div.menu>h1
cls.largest_shift_time Timestamp indicating when the single largest layout shift contributing to the page’s CLS score occurred. DOMHighResTimeStamp
cls.largest_shift_value Layout shift score of the single largest layout shift contributing to the page’s CLS score. 0.1
cls.load_state Document loading state when the largest layout shift contribution to the page’s CLS score occurred. loading, dom-content-loading, complete, dom-interactive
cls.had_recent_input Indicates whether recent user input occurred before the largest layout shift. true, false

Interaction to Next Paint (INP) Attributes 

Attribute Description Example
inp.id Unique identifier for the metric. v4-1724856546003-8941918093361
inp.delta Change in value since the last measurement. 100
inp.value Current value of INP, in milliseconds. Measures the time between when a user interacts with a web page (clicks, taps, or keyboard inputs) and the next time the page visually updates in response to that interaction. 100
inp.rating Metric rating. good, needs-improvement, poor
inp.navigation_type Type of navigation that occurred. restore, navigate, reload, back-forward, prerender, back-forward-cache
inp.input_delay Delay between user input and the start of input processing. 120
inp.interaction_target Selector identifying the element the user first interacted with as part of the frame where the INP candidate interaction occurred. If this value is an empty string, usually this means the element was removed from the DOM after the interaction. button#submit
inp.interaction_time Timestamp indicating when the user first interacted during the frame where the INP candidate interaction occurred. If more than one interaction occurred within the frame, only the first time is reported. DOMHighResTimeStamp
inp.interaction_type Type of interaction, based on the event type of the event entry for the interaction (the first event entry containing an interactionId dispatched in a given animation frame). For pointerdown, pointerup, or click events, this will be pointer. For keydown or keyup events, this will be keyboard. pointer, keyboard
inp.load_state Loading state of the document at the time when the interaction corresponding to INP occurred. If the interaction occurred while the document was loading and executing the script (usually in the dom-interactive phase), it can result in long delays. loading, dom-content-loading, complete, dom-interactive
inp.next_paint_time Time of the next paint after the interaction. DOMHighResTimeStamp
inp.presentation_delay Time between when the browser finished processing all event listeners for the user interaction and the next frame was presented on the screen and visible to the user. This time includes work on the main thread (such as requestAnimationFrame() callbacks, ResizeObserver and IntersectionObserver callbacks, and style/layout calculation) as well as off-main-thread work (such as compositor, GPU, and raster work). 50
inp.processing_duration Time between when the first event listener started running in response to the user interaction and when all event listener processing had finished. 30
inp.duration Sum of input delay, processing duration, and presentation delay. 150
inp.element Same as inp.interaction_target. Will be deprecated in a future version. button#submit
inp.event_type Same as inp.interaction_type. Will be deprecated in a future version. pointer, keyboard

Interaction to Next Paint (INP) With Long Animation Frame Timing Attributes 

Attribute Description Example
inp.duration Duration of the performance entry. DOMHighResTimeStamp
inp.entryType Type of the performance entry. mark, measure
inp.name Name of the performance entry. name
inp.renderStart Render start time of the performance entry. DOMHighResTimeStamp
inp.startTime Start time of the performance entry. DOMHighResTimeStamp

Interaction to Next Paint (INP) With Performance Script Timing Attributes 

Attribute Description Example
inp.entry_type Type of the script performance entry. script
inp.start_time Time when the function was invoked, which indicates the start time of the script rather than the start time of the frame. (Each entry in the performance timeline has a start time.) DOMHighResTimeStamp
inp.execution_start Time after compilation (if this script was parsed/compiled). Otherwise, matches inp.start_time. DOMHighResTimeStamp
inp.duration Duration between start time and the time that the subsequent microtask queue finished processing. 1.55
inp.forced_style_and_layout_duration Total time spent in forced layout/style inside this function. 100
inp.invoker Various pieces of information about the invoker of the script. For callbacks, Object.functionName of the invoker (example: Window.setTimeout). For element event listeners, TAGNAME#id.onevent or TAGNAME[src=src].onevent. For script blocks, the script source URL. For promises, the invoker of the promise (example: Window.fetch.then). For promise resolvers, all of the handlers of the promise, mixed together as one long script. IMG#id.onload, Window.requestAnimationFrame, Response.json.then
inp.pause_duration Total time spent in pausing synchronous operations (alert, synchronous XHR). 100
inp.source_url Source URL of the script. https://example.com
inp.source_function_name Name of the source function. myFunction
inp.source_char_position Character position in the source. 256
inp.window_attribution Relationship between the (same-origin) window where this script was executed and this window. self, descendant, ancestor, same-page, other

First Contentful Paint (FCP) 

Attribute Description Example
fcp.id Unique identifier for the metric. v4-1724856546003-8941918093361
fcp.delta Change in value since the last measurement. 100
fcp.value Current value of FCP, in milliseconds. Measures the time between when the page starts loading and any part of the page’s content is rendered on the screen. 100
fcp.rating Metric rating. good, needs-improvement, poor
fcp.navigation_type Type of navigation that occurred. restore, navigate, reload, back-forward, prerender, back-forward-cache
fcp.time_to_first_byte Time between when the user initiates loading the page and the browser receives the first byte of the response (also known as TTFB). 200
fcp.time_since_first_byte Delta between the TTFB and the FCP. 500
fcp.load_state Current state of the page load process. loading, dom-content-loading, complete, dom-interactive

Time To First Byte (TTFB) Attributes 

Attribute Description Example
ttfb.id Unique identifier for the metric. v4-1724856546003-8941918093361
ttfb.delta Change in value since the last measurement. 100
ttfb.value Current value of TTFB, in milliseconds. Measures the time between when the user initiates loading the page and the browser receives the first byte of the response. 100
ttfb.rating Metric rating. good, needs-improvement, poor
ttfb.navigation_type Type of navigation that occurred. restore, navigate, reload, back-forward, prerender, back-forward-cache
ttfb.waiting_duration Time spent waiting for the server to respond to the request. 150
ttfb.dns_duration Time taken to resolve the DNS for the request. 50
ttfb.connection_duration Time taken to establish a connection to the server. 100
ttfb.request_duration Time taken for the server to process the request and send the first byte of the response. 200
ttfb.cache_duration Time taken to retrieve the response from the cache, if applicable. 20
ttfb.waiting_time Deprecated: Time spent waiting for the server to respond to the request. 150
ttfb.dns_time Deprecated: Time taken to resolve the DNS for the request. 50
ttfb.connection_time Deprecated: Time taken to establish a connection to the server. 100
ttfb.request_time Deprecated: Time taken for the server to process the request and send the first byte of the response. 200

Deprecated: First Input Delay (FID) 

This will be deprecated in a future version of web-vitals. Use INP instead.

Attribute Description Example
fid.id Unique identifier for the metric. v4-1724856546003-8941918093361
fid.delta Change in value since the last measurement. 100
fid.value Current value of FID, in milliseconds. Measures the time between when the user initiates loading the page and the browser receives the first byte of the response. 100
fid.rating Metric rating. good, needs-improvement, poor
fid.navigation_type Type of navigation that occurred. restore, navigate, reload, back-forward, prerender, back-forward-cache
fid.element Selector identifying the element interacted with by the user. The element will be the target of the event dispatched. button#submit
fid.event_type type of the event dispatched from the user interaction. click
fid.load_state State of the page load when the event occurred. loading, dom-content-loading, complete, dom-interactive