npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@traceon/monitor

v0.1.0

Published

Official Node.js / JavaScript SDK for TraceOn

Downloads

95

Readme

@traceon/monitor

Official Node.js / JavaScript SDK for TraceOn.

Zero runtime dependencies. Batches events, retries with backoff, and reports crashes that would otherwise vanish.

Installing

The SDK is not published to the public npm registry yet, so npm install @traceon/monitor will not resolve. Until it is, install it directly from a checkout - npm pack produces exactly the tarball that would be published, so what you install is what you would get from the registry:

# in the traceon checkout
npm run build --workspace @traceon/monitor
cd packages/sdk-node && npm pack     # -> traceon-monitor-0.1.0.tgz

# in your application
npm install /path/to/traceon-monitor-0.1.0.tgz

Or, for a workspace that lives alongside the checkout:

npm install file:../traceon/packages/sdk-node

Once the package is published, this becomes:

npm install @traceon/monitor

Getting started

import * as Monitor from '@traceon/monitor';

Monitor.init({
  dsn: process.env.TRACEON_DSN!,
  environment: process.env.NODE_ENV ?? 'production',
  release: process.env.APP_VERSION,
});

Call init() before the rest of your application starts, so unhandled exceptions and rejections are captured from the first tick.

With no DSN the SDK becomes a no-op. That is deliberate: you can call init() unconditionally and simply not set the variable in development or in tests, without your application code branching on whether monitoring is on.

Capturing

// An exception, with its stack trace and any `cause` chain.
Monitor.captureException(error);

// A log-style message.
Monitor.captureMessage('Payment queue backing up', 'warning');

// Anything thrown, not just Errors. Strings, objects and numbers all produce
// a usable issue rather than being dropped.
Monitor.captureException('database unreachable');

Both return the event id, or undefined when the SDK is disabled or the event was sampled out.

Context

Everything set here is attached to subsequent events.

Monitor.setUser({ id: '42', email: '[email protected]' });
Monitor.setUser(null);                       // clear on logout

Monitor.setTag('region', 'eu-west-1');       // indexed, filterable in Discover
Monitor.setTags({ tier: 'pro', shard: 3 });

Monitor.setExtra('cartId', cart.id);         // arbitrary, not indexed
Monitor.setContext('deployment', { cluster: 'prod-a', pod: 'checkout-7d9f' });

Monitor.setTransaction('POST /checkout');    // names the unit of work
Monitor.setLevel('warning');

Breadcrumbs

What the application did before it broke. Buffered in memory and attached to the next event - never a request of their own, so recording them liberally costs nothing.

Monitor.addBreadcrumb({ type: 'http', category: 'fetch', message: 'GET /api/cart 200' });
Monitor.addBreadcrumb({ type: 'query', category: 'db', message: 'SELECT * FROM carts' });
Monitor.addBreadcrumb({ type: 'user', category: 'ui.click', message: 'Clicked Pay now' });

The buffer is a ring of maxBreadcrumbs (default 100); the oldest fall off.

Per-request context

captureExceptionWithScope applies context to one event only, which is what makes concurrent request handling safe:

app.use((req, res, next) => {
  res.on('finish', () => {});
  next();
});

try {
  await handle(req);
} catch (error) {
  Monitor.captureExceptionWithScope(error, (scope) => {
    scope.setUser({ id: req.user?.id });
    scope.setTransaction(`${req.method} ${req.route?.path}`);
    scope.setTag('request_id', req.id);
  });
  throw error;
}

Automatic capture

init() installs handlers for uncaughtException and unhandledRejection.

For an uncaught exception the process is already in an undefined state, so the SDK flushes with a short deadline and then re-raises Node's own behaviour (print the stack, exit 1) rather than swallowing the crash and leaving a zombie process running. Set exitOnUncaught: false if you have your own supervisor.

Wrapping handlers

export const handler = Monitor.wrap(async (event) => {
  return process(event);
});

Reports any throw or rejection and rethrows it, so behaviour is unchanged.

Shutting down

Events are batched, so a process that exits immediately can lose the last batch:

await Monitor.flush(5_000);   // wait for delivery, keep reporting
await Monitor.close(5_000);   // flush and shut down

Both return false if the deadline passed with events still queued.

Options

| Option | Default | Notes | | --- | --- | --- | | dsn | - | Empty disables the SDK. | | environment | production | | | release | - | Enables release tracking and regression detection. | | sampleRate | 1 | 0..1, applied per event before queueing. | | maxBreadcrumbs | 100 | | | maxBatchSize | 30 | Events per outbound request. | | flushIntervalMs | 2000 | How long a partial batch waits. | | maxQueueSize | 500 | Cap while the server is unreachable. Oldest are dropped. | | maxRetries | 4 | Attempts per batch, including the first. | | timeoutMs | 10000 | Per request. | | captureUnhandled | true | Install the global handlers. | | exitOnUncaught | true | Exit after reporting an uncaught exception. | | beforeSend | - | Last chance to scrub or drop an event. | | tags | {} | Applied to every event. | | inAppInclude / inAppExclude | [] | Override which stack frames count as your code. | | debug | false | SDK diagnostics on stderr. |

Scrubbing

beforeSend runs on every event. Return null to drop it.

Monitor.init({
  dsn: process.env.TRACEON_DSN!,
  beforeSend: (event) => {
    if (event.exception?.values[0]?.type === 'AbortError') return null;
    if (event.request?.headers) delete event.request.headers.authorization;
    return event;
  },
});

The server also redacts obviously sensitive header and cookie names when displaying an event, but scrubbing at the source is what keeps a secret from leaving your process at all.

In-app frames

Frames under node_modules, node:internal and similar are marked as library code: dimmed in the UI and excluded from grouping. In a monorepo where your own packages are symlinked into node_modules, tell the SDK:

Monitor.init({
  dsn: process.env.TRACEON_DSN!,
  inAppInclude: ['/node_modules/@acme/'],
});

How delivery works

Events accumulate in memory and are sent as one envelope, either when the batch fills or after flushIntervalMs. A burst of 500 errors becomes a handful of requests. Payloads over 1 KiB are gzipped.

Failures retry with exponential backoff and jitter. A 4xx other than 429 is not retried - the payload is wrong and retrying it forever would be a bug. A 429 is honoured for the duration of its Retry-After.

The flush timer is unref'd, so the SDK never keeps a process alive on its own.

Multiple clients

The module-level helpers operate on one implicit client, which is what almost every application wants. To report into two projects from one process:

import { Client } from '@traceon/monitor';

const billing = new Client({ dsn: process.env.BILLING_DSN! });
billing.captureException(error);
await billing.close();

Demo

TRACEON_DSN="http://<key>@localhost:8080/<projectId>" \
  node example/demo.mjs

Sends a handled exception with a real stack trace, a message, an error with a cause chain, a scoped capture and an unhandled rejection, then flushes.