@traceon/browser
v0.1.0
Published
Official browser SDK for TraceOn: errors, performance, distributed tracing and session replay
Maintainers
Readme
@traceon/browser
Official browser SDK for TraceOn: errors, page-load performance, distributed tracing and session replay over one batched transport.
Zero runtime dependencies.
Installing
The SDK is not published to the public npm registry yet, so
npm install @traceon/browser will not resolve. Until it is, install it from a
checkout — npm pack produces exactly the tarball that would be published:
# in the traceon checkout
npm run build --workspace @traceon/browser
cd packages/sdk-browser && npm pack # -> traceon-browser-0.1.0.tgz
# in your application
npm install /path/to/traceon-browser-0.1.0.tgzOr, for an app that lives alongside the checkout:
npm install file:../traceon/packages/sdk-browserGetting started
import * as TraceOn from '@traceon/browser';
TraceOn.init({
dsn: import.meta.env.VITE_TRACEON_DSN,
environment: 'production',
release: __APP_VERSION__,
});Call init() as early as possible — ideally the first import in your entry
module — so errors thrown during startup are captured.
With no DSN the SDK becomes a no-op. You can call init() unconditionally
and simply not set the variable in development, without your application code
branching on whether monitoring is on.
What is on by default
| Feature | Default | Why |
| --- | --- | --- |
| Error capture | on | The reason you installed it. |
| Breadcrumbs (console, clicks, navigation, fetch/XHR) | on | Cheap, and useless if switched on only after a bug appears. |
| Performance tracing | off | Far higher volume than errors. Opt in with tracesSampleRate. |
| Session replay | off | Records real user screens. Opt in per sample rate. |
Performance and tracing
TraceOn.init({
dsn: '…',
// Trace 20% of page loads. Requests made by the page hang off that
// transaction automatically.
tracesSampleRate: 0.2,
});fetch and XMLHttpRequest are wrapped, so each request becomes a span and
carries a W3C traceparent header to same-origin endpoints only. Sending
trace headers cross-origin turns a simple request into a preflighted one and
leaks internal ids to third parties, so other origins are opt-in:
TraceOn.init({
tracePropagationTargets: ['https://api.example.com', /\.internal\./],
});If your server renders <meta name="traceparent" content="…"> into the HTML,
the page-load transaction joins the trace of the request that produced the
page, so a slow render and the slow query behind it appear in one waterfall.
Single-page apps
Route changes are not page loads. Start a transaction per route so requests made by the new view hang off it:
router.afterEach((to) => {
TraceOn.startTransaction(to.matchedPath, { op: 'navigation' });
});Manual spans
await TraceOn.startSpan({ op: 'render', description: 'ProductGrid' }, async () => {
await renderGrid();
});The span is finished automatically, including on a throw — where it is also marked failed, since an operation that threw did not succeed.
Session replay
TraceOn.init({
dsn: '…',
replay: {
// Record 5% of sessions from page load.
sessionSampleRate: 0.05,
// Plus every session in which an error occurs: the recorder buffers in
// memory and uploads only if something goes wrong.
errorSampleRate: 1,
},
});errorSampleRate is the setting most people want. It records into a ring
buffer and uploads only when an error is captured, so you get the replay for
the sessions that matter without storing the ones that were fine.
Privacy
All text is masked by default. Characters are replaced with * while
whitespace and length are preserved, so the replay still shows where text was
and how much of it there was, without revealing a single character.
Masking happens at serialisation time, not at upload time — masked text never enters the buffer, so it cannot leak through a crash dump or a network trace.
replay: {
maskAllText: true, // the default
unmaskSelectors: ['.public'], // record these verbatim
blockSelectors: ['.pii'], // omit these subtrees entirely
blockAllMedia: true, // replace images and video with a placeholder
}Regardless of settings, the SDK never records:
<script>contents- the value of a
password,email,telorhiddeninput - an element inside
[data-traceon-mask]
Errors are automatically tagged with the replay session id, so an issue in the dashboard links straight to the recording that produced it.
Marking moments
TraceOn.replay.addEvent('checkout_step', { step: 3 });Metrics
Points are aggregated in-process and flushed on an interval, so these are safe on a hot path:
TraceOn.metrics.increment('cart.item_added', 1, { tags: { plan: 'pro' } });
TraceOn.metrics.gauge('cart.items', 4);
TraceOn.metrics.distribution('search.latency', 340, { unit: 'millisecond' });
TraceOn.metrics.set('active.users', userId);
await TraceOn.metrics.timing('checkout.submit', () => submit());timing records failures too, tagged outcome=failure, so a slow error path
cannot hide behind a fast success path.
Scope
TraceOn.setUser({ id: 'user-42', email: '[email protected]' });
TraceOn.setTag('plan', 'pro');
TraceOn.setContext('feature_flags', { newCheckout: true });
TraceOn.addBreadcrumb({ category: 'ui', message: 'opened the cart' });Options
| Option | Default | |
| --- | --- | --- |
| dsn | — | Empty disables the SDK. |
| environment | production | |
| release | — | Required for source-map symbolication. |
| sampleRate | 1 | Share of errors sent. |
| tracesSampleRate | 0 | Share of page loads traced. |
| tracePropagationTargets | [] | Extra origins that receive traceparent. Same-origin always does. |
| maxBreadcrumbs | 100 | |
| autoBreadcrumbs | true | Console, clicks, navigation, fetch/XHR. |
| captureUnhandled | true | error and unhandledrejection listeners. |
| beforeSend | — | Last chance to scrub or drop an event. |
| replay.sessionSampleRate | 0 | |
| replay.errorSampleRate | 0 | |
| replay.maskAllText | true | |
Delivery
Events are batched and sent with fetch. On pagehide and when the tab is
hidden, the SDK switches to sendBeacon, which the browser delivers even after
the document is gone — this is the only reliable way not to lose the last
errors of a session, which are usually the interesting ones.
Payloads over the 64KB keepalive budget are truncated to the newest items
rather than dropped whole.
Removing it
close() removes every global patch the SDK installed — event listeners, the
fetch wrapper, the history wrapper, the console wrapper — which is what
makes it safe to use in a test suite:
afterEach(() => TraceOn.close());Licence
MIT.
