SDK
Set up @openperf/sdk in the browser, Node.js and Bun, and report errors, users and spans.
npm install @openperf/sdk
| Entry point | For |
|---|---|
@openperf/sdk/browser |
Browser apps. ES modules only. |
@openperf/sdk/node |
Node.js 22.15 or later. |
@openperf/sdk/bun |
Bun. It throws on Node.js. |
@openperf/sdk/cli |
The openperf CLI for releases and source maps. |
The browser, Node.js and Bun entry points export the same API: init, captureException, setUser, setTag, addBreadcrumb, startSpan and flush.
Browser
import { init } from "@openperf/sdk/browser";
init({
dsn: "https://opf_…@ingest.example.com",
environment: "production",
release: "1.4.2",
tracePropagationTargets: ["https://api.example.com"],
});
Call init once, as early as possible. It collects:
- Errors: uncaught errors and unhandled promise rejections.
- Page loads and navigations: one trace per page load and per path change in the history.
- Web vitals: LCP, INP, CLS, FCP and TTFB, sent when the page is hidden.
- Requests: a span for every
fetchandXMLHttpRequest. - Breadcrumbs: clicks,
console.log,console.warn,console.errorand navigations, attached to errors.
Data is sent in batches and flushed when the page is hidden.
The browser reads no environment variables: pass dsn, environment and release in code, for example from your bundler’s env.
Requests get a traceparent header so backend spans join the browser trace. By default only same-origin requests get it. tracePropagationTargets replaces that with your own list: a string matches when the URL contains it, a RegExp when it matches. To keep same-origin requests, add your own origin to the list. A cross-origin backend must allow the traceparent header in its CORS settings.
To accept browser data only from your sites, list them under Allowed origins in the project settings. One origin per line, up to 20. Empty accepts any origin; requests from an unlisted origin get 403.
Node.js
Put init in its own file, instrument.mjs, so it runs before your app loads:
import { init } from "@openperf/sdk/node";
init({ serviceName: "checkout" });
For an ES module app, load the file with --import. init registers the loader hooks itself, so ES modules get instrumented:
node --import ./instrument.mjs app.mjs
For a CommonJS app, use require in instrument.cjs and start with node --require ./instrument.cjs app.js.
Bun
Create instrument.ts:
import { init } from "@openperf/sdk/bun";
init({ serviceName: "checkout" });
bun --preload ./instrument.ts app.ts
To preload it every time, add it to preload in bunfig.toml.
What servers collect
On Node.js and Bun, init sets up OpenTelemetry with the standard Node.js instrumentations and Fastify:
- Traces for incoming and outgoing HTTP, frameworks such as Express and Fastify, databases, caches and queues. Bun also traces
Bun.serveandfetch. - Errors: uncaught exceptions, and exceptions an instrumentation records on a span, such as an Express handler that throws.
- Logs from pino, winston and bunyan, unless
captureLogsisfalse.
On an uncaught exception the SDK reports it, prints it and exits with code 1 after up to 2 seconds of sending. If your app registers its own uncaughtException handler, the SDK only reports and leaves the rest to it.
Data is flushed when the process exits on its own. Call await flush() before process.exit() and at the end of a serverless handler.
OTEL_NODE_ENABLED_INSTRUMENTATIONS and other standard OpenTelemetry variables still apply.
Options
| Option | Runtime | Default | Description |
|---|---|---|---|
dsn |
All | OPENPERF_DSN |
The project DSN. Without it, init does nothing. |
environment |
All | OPENPERF_ENVIRONMENT |
Such as production or staging. Data without one goes to default. |
release |
All | OPENPERF_RELEASE |
The release version. Needed for source maps and release health. |
serviceName |
All | OTEL_SERVICE_NAME |
The service shown on traces, logs and errors. |
tracesSampleRate |
All | 1 |
Share of traces to send, from 0 to 1. |
tracePropagationTargets |
Browser | Same origin | URLs that get a traceparent header. |
captureLogs |
Node.js, Bun | true |
Send pino, winston and bunyan logs. |
instrumentations |
Node.js, Bun | [] |
More OpenTelemetry instrumentations to register. |
Environment variables are read only on Node.js and Bun. An option passed to init wins over its variable. A malformed DSN or a sample rate outside 0 to 1 throws. A second init is ignored.
Sampling
tracesSampleRate decides per trace, so a sampled trace keeps all its spans. Errors from captureException and uncaught errors are always sent. Exceptions that an instrumentation records on a span travel with that span: below 1, the ones in unsampled traces are lost. Report errors you always need with captureException.
API
import { captureException, setTag, setUser, startSpan } from "@openperf/sdk/node";
setUser({ id: "u_123", email: "ada@example.com" });
setTag("plan", "pro");
await startSpan({ name: "charge card", op: "payment" }, async (span) => {
span.setAttribute("amount", 4200);
await chargeCard();
});
captureException(error, { fingerprint: "payment-declined" });
| Function | Does |
|---|---|
captureException(error, context?) |
Reports an error. fingerprint groups all errors with the same value together. handled defaults to true. |
setUser(user) |
Adds id, email and name to later errors and spans. null clears it. |
setTag(key, value) |
Adds a string, number or boolean attribute to later errors and spans. |
addBreadcrumb(breadcrumb) |
Records a step with a category, message, level and data. Errors carry the last 100. |
startSpan(options, callback) |
Runs callback in a span named name, with an optional op and attributes. A thrown error or rejected promise fails the span. |
flush() |
Sends everything queued. |
On a server, setUser, setTag and addBreadcrumb inside a request apply to that request only. Outside a request, and in the browser, they apply to everything after the call.
Before init, the functions do nothing and startSpan only runs its callback, so libraries can call them safely.