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.
Honeycomb’s Web Instrumentation package gives you all of the capabilities provided by the generic OpenTelemetry Distribution for JavaScript and also allows you to:
Before you can set up instrumentation for your application, you will need to do a few things.
To send data to Honeycomb, you need to:
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
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
.
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.
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.
To accept POST
s 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.
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. |
*
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.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,
})],
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
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.
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.
]
}
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();
}
});
};
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
@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();
@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();
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.
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();
});
}
});
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.
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.
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();
});
}
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();
});
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.
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.
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:
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>
Select the link to view the trace in detail within the Honeycomb UI.
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.
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.
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.
To explore common issues when sending data, visit Common Issues with Sending Data in Honeycomb.