ErrTapDocs

Node.js SDK

Install @errtap/node to capture uncaught exceptions and rejections, report handled errors from Express or any handler, and send logs and user feedback from Node.js.

Install

npm i @errtap/node

Initialise

Call init as early as possible — before any other imports that might throw:

import { init } from '@errtap/node';

init({
  dsn: 'https://et_<key>@<host>/<project>',
  environment: 'production',
  release: process.env.APP_VERSION,
});

init registers uncaughtException and unhandledRejection handlers. Calling it again replaces them instead of stacking duplicates. On runtimes without process.on (Edge, some workers) the handlers are skipped and manual capture still works.

Options

OptionTypeDefaultDescription
dsnstringrequiredURL DSN, or a bare et_… key with endpoint
endpointstringderived from the DSNFull ingest URL override
environmentstring'production'Attached to every event and log
releasestringnoneUsed for release health and regression detection
tagsRecord<string, unknown>noneAttached to every event and log
exitOnFatalbooleantrueExit with code 1 after reporting an uncaught exception

Fatal errors

Listening for uncaughtException normally stops Node from crashing. With exitOnFatal: true the SDK keeps Node's behaviour: it prints the error, waits up to 2 seconds for the report, then exits with code 1. Unhandled rejections are treated the same way unless the process runs with --unhandled-rejections=warn or none. Set exitOnFatal: false when a framework or supervisor owns the process; @errtap/next/server does this for you.

Express integration

The package does not install Express middleware. Capture in your existing error middleware and pass request context on that call:

import express from 'express';
import { init, captureException } from '@errtap/node';

init({ dsn: 'https://et_<key>@<host>/<project>' });

const app = express();

// ... your routes here ...

app.use((err, req, res, next) => {
  void captureException(err, {
    url: req.originalUrl,
    tags: { method: req.method },
  });
  next(err);
});

app.listen(3000);

Capture helpers

import { captureException, captureMessage } from '@errtap/node';

try {
  await doSomethingRisky();
} catch (err) {
  captureException(err, {
    tags: { job: 'invoice-sync' },
    context: { invoiceId: inv.id },
  });
}

captureMessage('stripe webhook skipped', { level: 'warning' });

API

captureException(error, options?)

FieldTypeDescription
tagsRecord<string, unknown>Merged with init-level tags
contextRecord<string, unknown>Arbitrary extra data
levelstringerror (default), warning, info, fatal
fingerprintstringOverrides automatic grouping
releasestringOverrides init-level release
user{ id?, email?, name? }Attaches user to this event

Once init has run with a valid DSN, both functions return a promise that resolves when the send finishes and never rejects. Before init, or with a DSN that couldn't be parsed, they return undefined, so use await rather than .then(). await them in short-lived scripts so the event leaves before the process exits.

captureMessage(message, options?)

Same options shape as captureException.

captureFeedback(feedback)

Sends a user's note, linked to eventId or, by default, lastEventId() — the last error this process sent. See User feedback.

Use logger.debug, logger.info, logger.warn/warning, and logger.error for structured logs. Dynamic request/user context belongs on each call; the Node SDK deliberately does not keep global breadcrumbs or mutable request context.

Release & environment

init({
  dsn: 'https://et_<key>@<host>/<project>',
  release: process.env.APP_VERSION ?? 'dev',
  environment: process.env.NODE_ENV ?? 'development',
});

ErrTap uses release to detect regressions: if an issue was resolved while v1.4.2 was the latest registered release and reappears in v1.4.3, it is reopened, provided v1.4.3 is registered as a later deploy with POST /ingest/release or on the Releases page. See Regression behaviour.