@vantatrace/sdk · Node.js SDK Reference

SDK Documentation

npm install @vantatrace/sdkNode.js ≥ 16TypeScript support

Introduction

VantaTrace is a real-time error tracking, telemetry, and observability SDK for Node.js applications and microservices. It captures exceptions, runtime metrics, and request context, then streams them to your centralized VantaTrace dashboard.

⚡

Zero Blocking

All payloads are dispatched asynchronously via setImmediate — no request latency added.

🔁

Smart Batching

Events are batched and flushed every 500 ms, or immediately for critical severity.

🛡️

Fail-Safe

SDK errors are caught internally — it will never crash your host application.

🔍

Auto Deduplication

Duplicate error objects are filtered via WeakSet to prevent double-reporting.

Installation

Install the SDK from npm. It works with any Node.js framework, and nothing it pulls in runs on your request path — its single dependency is a Babel helper used only by the optional build-time plugin.

bash
npm install @vantatrace/sdk
Requires Node.js 16+. TypeScript types are bundled — no @types/ package needed.

Initialization

Create a single VantaTrace instance in your main entry file (e.g. app.js or server.ts) and export it for use across your service.

typescript
import { VantaTrace } from '@vantatrace/sdk';

const vantaTrace = new VantaTrace({
  apiKey: 'ep_live_xxxxxxxxxxxxxxxxxxxxxxxx', // Your project API key
  debug: false, // Enable verbose SDK logs in development

  // ── try/catch capture ────────────────────────────────────────────────
  // An error you handle yourself never reaches errorHandler() or the
  // global handlers, so it needs its own capture path. Details in the
  // "try/catch Capture" section below.
  autoCapture: {
    http5xx: true,           // ON by default — a 5xx answer with no error reported
    httpClientErrors: true,  // ON by default — same for 4xx (401 + 404 excluded)

    // Opt-in: recovers the REAL Error object from inside your catch blocks
    // using the V8 inspector. Auto-disabled when NODE_ENV=production —
    // use the Babel plugin there instead (zero runtime cost).
    caughtExceptions: process.env.NODE_ENV !== 'production',
  },
});

Configuration Options

OptionTypeRequiredDefaultDescription
apiKeystringYes—Your project API key from the VantaTrace dashboard. Prefix determines environment (ep_live_ → live, ep_test_ → test). With no key the SDK runs in dry-run mode and discards every event, logging a startup warning regardless of debug.
debugbooleanNofalseWhen true, the SDK prints verbose logs to stdout (trace IDs, payloads, transport status).
autoCaptureAutoCaptureOptionsNo—Capture of errors that never escape your code — http5xx and httpClientErrors (both on by default) plus the opt-in caughtExceptions inspector watcher. See try/catch Capture below.
apiUrlstringNohttps://api.vantatrace.com/api/eventsOverride the ingestion endpoint. Useful for on-premise or staging deployments.
maskingKeysstring[]No[]Extra field names (exact match, case-insensitive) to redact from log metadata — domain-specific PII the generic password/token/secret matching would not catch. Merged with the built-in list.
rateLimitRateLimitOptionsNo—Volume ceilings on captured events: maxPerMinute (default 1000), maxPerFingerprintPerMinute (default 150) and sampleRate (default 1). Protects your app and the pipeline during an event storm; getDropStats() reports what was dropped.

Environments & API Keys

VantaTrace derives the environment directly from your API key — no manual environment config needed:

ep_live_…live
ep_test_…test

Global Handlers

Call initGlobalHandlers() once after initialization to register a safety net that automatically captures:

  • Synchronous uncaught exceptions — process.on('uncaughtException') — runtime crashes, typos, undefined refs.
  • Unhandled promise rejections — process.on('unhandledRejection') — re-thrown errors inside async catch blocks.
  • console.error() calls — Auto-patched — any Error passed to console.error is forwarded.
  • Winston logger errors — Auto-patched at runtime if the winston package is installed.
  • Pino logger errors — Auto-patched at runtime if the pino package is installed.
typescript
// Call once, right after initialization
vantaTrace.initGlobalHandlers();

// This registers:
//  1. process.on('uncaughtException', ...)   – synchronous crashes
//  2. process.on('unhandledRejection', ...)  – async/await rejections
//  3. console.error patch                     – errors via console.error
//  4. Winston auto-patch (if installed)
//  5. Pino auto-patch (if installed)
For uncaughtException, the process exits after 1.5 seconds to allow the batched transport to complete flushing before shutdown.

Express Middleware

VantaTrace provides two Express middlewares that work together to capture full HTTP request context alongside every error.

requestHandler()

Mount this at the very top of your middleware stack, before any routes or body parsers. It seeds an AsyncLocalStorage context with request metadata (route, method, IP, sanitized headers, sanitized body).

Request bodies, query params, and headers are recursively scrubbed for passwords, tokens, cookies, and other PII before being stored or transmitted — see Security & Redaction below for the full default rule set and how to add your own.

errorHandler()

Mount this at the very bottom of your middleware stack, after all routes and controllers. It intercepts any error passed to next(err) and forwards it to VantaTrace while calling next(err) so your own error handler still runs.

typescript
import express from 'express';
import { VantaTrace } from '@vantatrace/sdk';

const app = express();

const vantaTrace = new VantaTrace({
  apiKey: process.env.VANTATRACE_API_KEY!,
});

// ① Mount requestHandler FIRST — before any routes or body parsers
app.use(vantaTrace.requestHandler());

app.use(express.json());

// ... your routes ...
app.get('/api/orders', async (req, res) => {
  const orders = await fetchOrders(); // any thrown error is auto-captured
  res.json(orders);
});

// ② Mount errorHandler LAST — after all routes and controllers
app.use(vantaTrace.errorHandler());

// Your own error response handler (still called via next(err))
app.use((err, req, res, next) => {
  res.status(500).json({ message: 'Internal Server Error' });
});

app.listen(3000);

try/catch Capture

errorHandler() and the global handlers only see errors that escape your code. An error you catch yourself is, as far as Express and process are concerned, handled — so it reaches neither:

typescript
app.get('/api/checkout', async (req, res) => {
  try {
    await chargeCard(req.body);
  } catch (error) {
    // Nothing escapes this block, so errorHandler() and the global
    // handlers never see it. Without try/catch capture, this failure is
    // invisible everywhere except your own logs.
    res.status(500).json({ error: 'Payment failed' });
  }
});

JavaScript has no language-level hook for a caught exception — try/catch is resolved entirely inside the V8 VM, which is why most error trackers require a manual capture call in every catch block. VantaTrace covers it in three layers instead, and the one you want depends on where the code runs:

typescript
const isProd = process.env.NODE_ENV === 'production';

const vantaTrace = new VantaTrace({
  apiKey: process.env.VANTATRACE_API_KEY,
  autoCapture: {
    // ① Failed-request safety net — already ON, no config needed.
    //    The route above is reported as a synthetic HttpServerError with
    //    the method, route, status and the response body your catch block
    //    sent back. Pass false to turn it off.
    http5xx: true,

    // ② The same for 4xx answers your code returns on purpose
    //    (res.status(422).json(...)), at 'warning' severity.
    //    401 and 404 are excluded by default as routine traffic.
    httpClientErrors: true,

    // ③ Deep capture — the real Error object, not a synthetic one.
    //    Auto-disabled under NODE_ENV=production; use the Babel plugin there.
    caughtExceptions: !isProd,
  },
});

// Optional: detach the inspector session on graceful shutdown.
process.on('SIGTERM', () => vantaTrace.shutdown());

Failed-Request Safety Net

Enabled by default — nothing to configure. When a request finishes 5xx and no error was reported for it, the SDK emits a synthetic HttpServerError (severity critical) carrying the method, route, status code and full request context. The response body your catch block actually sent is attached as metadata.responseBody — usually the most concrete clue available when the Error object itself was swallowed.

4xx answers are covered too, as HttpClientError at warning severity — a res.status(422).json(...) with no throw is exactly the kind of handled-and-invisible failure this exists for. 401 and 404 are excluded by default; pass { exclude: [404] } to change the list, or false to disable 4xx capture entirely.

Deep Capture via the V8 Inspector (opt-in)

With pause-on-exceptions enabled, V8 notifies an in-process node:inspector session at every throw site, before the catch block runs. VantaTrace uses that to recover the real error object — including engine-generated ReferenceError and TypeError — with its full stack and the active request context, without touching a single catch block.

typescript
autoCapture: {
  caughtExceptions: {
    // 'request-failure' (default): buffer caught exceptions and report them
    // only if the request ends >= 500 — zero noise from errors your code
    // genuinely recovered from.
    // 'always': report every caught exception immediately, severity 'warning'.
    // Use this outside HTTP (queue consumers, cron jobs).
    report: 'request-failure',

    // Libraries throw and catch internally as control flow, which is noise.
    includeNodeModules: false,

    // Recording ceiling, protects throw-heavy hot loops.
    maxPerMinute: 120,

    // Required to run the inspector watcher under NODE_ENV=production.
    // Without it the watcher is skipped and a warning is logged.
    allowInProduction: false,
  },
}

Duplicates are suppressed: an error that is caught, rethrown and then reaches errorHandler() (or a manual captureException) is reported exactly once.

Auto-disabled in production. The watcher adds roughly 0.3–0.5 ms per thrown exception while attached, which can mean latency spikes on a throw-heavy path — not a trade-off to opt into implicitly. When NODE_ENV=production the SDK skips it and logs a warning pointing at the Babel plugin. Set allowInProduction: true to run it there anyway.

Babel Plugin — the production path

The plugin injects the capture call into every catch block at build time, so production gets the same real-error capture with no inspector attached and no runtime cost at all.

json
// .babelrc — or babel.config.js
{
  "presets": ["@babel/preset-env"],
  "plugins": ["@vantatrace/sdk/babel-plugin"]
}
typescript
// What you write:
try {
  doSomething();
} catch (error) {
  res.status(500).json({ error: 'Failed' });
}

// What the plugin compiles it to — no inspector, no runtime cost:
import { captureExceptionGlobal } from '@vantatrace/sdk/runtime';

try {
  doSomething();
} catch (error) {
  captureExceptionGlobal(error);
  res.status(500).json({ error: 'Failed' });
}

// Opt a block out with a comment:
try {
  parseOptionalConfig();
} catch (error) { // vantatrace-ignore
  // expected control flow — not a defect worth reporting
}

It skips a block when the catch has no binding (catch { }), when the binding is destructured, when you already call captureException there yourself, or when a // vantatrace-ignore comment marks it.

Keep a module transform in the pipeline (@babel/preset-env, or sourceType: "unambiguous"). Without one, a plain CommonJS file gets an ESM import injected and fails at runtime with Cannot use import statement outside a module. Next.js picks up a .babelrc in the project root automatically and switches that project off SWC.

Security & Redaction

PII and secrets are redacted recursively, everywhere the SDK captures data — not just top-level fields or standard headers.

Default Redaction

  • Headers — authorization, cookie, set-cookie, x-api-key, proxy-authorization.
  • Request bodies & query params — Any nested field named like password, passwd, pwd, secret, token, apiKey, accessKey, privateKey, authorization, credential, cookie, session, ssn, creditCard/cardNumber, cvv/cvc or otp — matched anywhere in the name, case-insensitive, at any depth, including inside arrays.
  • Short names, matched as whole words — pin, mpin, pincode, pass, auth, key, mfa and 2fa are matched per key segment (split on dashes, underscores and camelCase humps) so X-MPIN and shippingPin are redacted while shipping, passenger and author are left intact.
  • Secrets inside free text — A credential interpolated into an error message, a stack frame or a raw URL is matched by shape — Bearer tokens, JWTs, key=value pairs, SSNs, card-shaped digit runs, email addresses — by the ingestion worker rather than in your process, so it is redacted before storage but does reach the ingestion endpoint. Avoid putting credentials in error messages you throw.
Key-name redaction runs client-side, in requestHandler(), before anything leaves your process. The ingestion worker then re-scrubs every event independently — key names and secret-shaped strings — before persisting it, so data stays sanitized even for events sent by an older SDK version or posted directly to the ingest API.

Capturing Errors

captureException(error, context?)

The primary method for manual error capture. Use inside try/catch blocks to send errors with rich business context.

typescript
try {
  await chargeCard(userId, amount);
} catch (error) {
  vantaTrace.captureException(error, {
    userId: 'user_482',
    route: '/api/checkout',
    method: 'POST',
    severity: 'critical',
    metadata: {
      amount: 29.99,
      currency: 'USD',
    },
  });
  throw error; // re-throw if needed
}

Severity Helpers

Convenience wrappers that set the severity field automatically:

typescript
// Equivalent to captureException with severity: 'critical'
vantaTrace.captureCritical(error, { userId: 'user_42' });

// Equivalent to captureException with severity: 'warning'
vantaTrace.captureWarning(error, { route: '/api/webhook' });

// Equivalent to captureException with severity: 'info'
vantaTrace.captureInfo(error, { metadata: { source: 'scheduler' } });
OptionTypeRequiredDefaultDescription
captureCritical()methodNo—Forwards error with severity: "critical". Dashboard shows a red badge.
captureWarning()methodNo—Forwards error with severity: "warning". Dashboard shows an amber badge.
captureInfo()methodNo—Forwards error with severity: "info". Dashboard shows a blue badge.

Context Object

The optional second argument to all capture methods accepts a VantaTraceContext object:

typescript
// The full VantaTraceContext object you can pass to captureException:
{
  userId?: string;       // Authenticated user ID
  route?: string;        // API route (e.g. '/api/checkout')
  method?: string;       // HTTP method (e.g. 'POST')
  ip?: string;           // Client IP address
  headers?: Record<string, any>;  // Sanitized HTTP headers
  severity?: 'critical' | 'warning' | 'info';
  metadata?: Record<string, any>; // Any custom key-value payload
}
When using Express middlewares, userId, route, method, ip, and headers are auto-populated from the request via AsyncLocalStorage — you only need to pass additional metadata.

Logger Integrations

When initGlobalHandlers() is called, VantaTrace automatically detects and patches popular loggers. No additional configuration is required.

Winston

A custom winston-transport is injected at runtime. Any Error object logged via logger.error() is forwarded to VantaTrace.

typescript
import winston from 'winston';
import { VantaTrace } from '@vantatrace/sdk';

const vantaTrace = new VantaTrace({
  apiKey: process.env.VANTATRACE_API_KEY!,
});

// Auto-patches Winston when initGlobalHandlers() is called.
// Any logger.error(error) call where the argument is an Error
// will be automatically forwarded to VantaTrace.
vantaTrace.initGlobalHandlers();

const logger = winston.createLogger({
  transports: [new winston.transports.Console()],
});

// This error will appear in your VantaTrace dashboard:
logger.error(new Error('Database connection refused'));

Pino

Pino's write() prototype method is patched. Errors passed directly or inside { err } / { error } objects are captured.

typescript
import pino from 'pino';
import { VantaTrace } from '@vantatrace/sdk';

const vantaTrace = new VantaTrace({
  apiKey: process.env.VANTATRACE_API_KEY!,
});

// Auto-patches Pino when initGlobalHandlers() is called.
vantaTrace.initGlobalHandlers();

const logger = pino();

// Any of these patterns will forward errors to VantaTrace:
logger.error(new Error('Connection timeout'));
logger.error({ err: new Error('DB query failed') }, 'Database error');
logger.error({ error: new Error('Payment failed') }, 'Payment error');

console.error Patch

console.error is monkey-patched to intercept any Error argument. The original console.error is still called — VantaTrace only adds its own capture on top.

All three interceptors include a reentrance guard — if the SDK itself triggers a log during capture, it won't cause an infinite loop.

Trace ID Correlation

Every captured error is tagged with a unique 32-character hex Trace ID. You can retrieve the ID bound to the current async request context using the static method VantaTrace.getActiveTraceId().

This allows you to correlate your own log lines (Winston, Pino, or custom) with the exact error event visible in the VantaTrace dashboard.

typescript
import { VantaTrace } from '@vantatrace/sdk';

// Get the active trace ID bound to the current async request context.
// Use this to correlate your own log entries with the VantaTrace dashboard event.
const traceId = VantaTrace.getActiveTraceId();

// Example: attach traceId to your response headers for distributed tracing
app.use(vantaTrace.requestHandler());

app.get('/api/orders', (req, res) => {
  const traceId = VantaTrace.getActiveTraceId();
  res.setHeader('X-Trace-Id', traceId ?? '');
  // ...
});
The Trace ID is scoped to the AsyncLocalStorage context of each HTTP request. It persists across any async/await hops within the same request lifecycle.

Ready to get started?

Create a free account