JavaScript & TypeScript
The reference BugWatch SDK: one package for Node services, browsers, edge runtimes and every major JS framework, with first-class TypeScript types.
Requirementslink
- Node 20.19+ for server use; evergreen browsers; ships dual ESM and CommonJS builds
- Subpath types resolve on every TypeScript setting including legacy
"moduleResolution": "node"(the package shipstypesVersions); never alias@newinstance/bugwatch/*incompilerOptions.paths - Edge runtimes (Cloudflare Workers, Vercel Edge) use the core client and must not import
./node(no AsyncLocalStorage there); Bun and Deno get core plus the Hono adapter - All framework and logger peers are optional. Minimums: express 4, fastify 4, koa 2, hono 4, hapi 20, nest 9, react 16.8, vue 3, pino 8, winston 3, bunyan 1.8, log4js 6, @opentelemetry/api 1.4
1 - Installlink
npm install @newinstance/bugwatchThe credential is the project DSN key (sk_test_… or sk_live_…, one per environment) from the dashboard project's Settings under DSN keys.
2 - Initialiselink
Server (Node):
import { BugWatch } from "@newinstance/bugwatch";
import { installNodeErrorHandlers } from "@newinstance/bugwatch/node";
const client = BugWatch.init({
projectKey: process.env.BUGWATCH_KEY!,
release: process.env.APP_VERSION,
});
installNodeErrorHandlers(client);init is idempotent (a singleton on globalThis, safe under hot reload) and returns the existing client on repeat calls; captures before init are no-ops that still return a generated id. installNodeErrorHandlers is idempotent and returns an uninstall function; installAsyncScope + runWithContext give per-unit isolation to non-framework code (queues, workers).
Browser (never ship the key) - one import, one call since v0.2.0:
import { initBrowser } from "@newinstance/bugwatch";
export const client = initBrowser({ sessionUrl: "/bugwatch/session" });initBrowser is SSR-safe and idempotent, installs the window error and unhandled-rejection handlers, resolves a relative sessionUrl against the page origin, and never throws: bad config or an unreachable mint endpoint degrades to a working no-op client plus a single console warning. getBrowserClient() returns the singleton anywhere else. The pre-0.2.0 createClient + installBrowserErrorHandlers pair still works but is legacy.
Next.js gets a dedicated browser-safe entry, @newinstance/bugwatch/next/client (no Node built-ins, unlike the server-side ./next): initBrowser for _app.tsx or a client component in layout.tsx, BugWatchErrorBoundary, and captureRenderError(error, { digest }) for App Router error.tsx.
Your backend exposes the mint endpoint: Express has a ready-made bugWatchBrowserSessionHandler; anything else calls mintBrowserSession which returns { token, expiresAt }. The transport refreshes the token 30 seconds before expiry and on a 401.
3 - Configurationlink
| Option | Default | What it does |
|---|---|---|
projectKey / sessionUrl | - | Server credential, or the browser mint endpoint |
release / environment | - / from key | Labels on every event |
enabled / debug | true / false | Master switch; debug prints delivery diagnostics including the server's reason string after the HTTP status |
sampleRate | 1 | Applied before beforeSend; keep-all-errors sampling belongs in beforeSend |
captureUnhandledErrors / captureUnhandledRejections | true | Global handlers, chained not replaced |
sensitiveFields | built-in list | Extra redaction keys |
beforeSend | - | May be async; return null to drop, or mutate and return the event |
console | on | Local echo of the native logger: false or `{ enabled, level, format: "auto" |
batchSize / flushInterval | 50 / 5000 ms | Delivery batching; flushInterval: 0 disables the timer (manual flush only) |
maxQueueSize | 1000 | Overflow drops the oldest queued events |
requestTimeout | 15000 ms | Per-request timeout |
retry | 3 attempts, 200 ms to 5 s | Exponential backoff |
traceContextProvider | - | Auto-attach traceId and spanId (OTel below) |
4 - Capture APIlink
const id = BugWatch.captureException(err, { tags: { route: "/checkout" } });
BugWatch.captureMessage("Payment settled", 30);
BugWatch.captureLog({ level: "warn", message: "Slow query", tags: { db: "orders" } });
BugWatch.setUser({ id: "u_123", email: "ada@example.com" });
BugWatch.setTag("tenant", "acme");
BugWatch.setContext("payment", { provider: "paystack" });
BugWatch.addBreadcrumb({ category: "cart", message: "cart validated", data: { items: 3 } });
BugWatch.setRelease("checkout@2.4.1");
await BugWatch.flush();
await BugWatch.close();- Levels are Pino-compatible numerics 10 to 60 (the
LEVELSmap is exported);captureLogtakes one object. - The
captureExceptionhint acceptslevel,tags,user,traceId,spanId; oncaptureMessagethe level is the positional second argument and the hint (third argument) carriestags,userand trace ids. - Cause chains are automatic: errors built with
new Error(msg, { cause })(andAggregateError) ship a structuredcauses[]array, each cause with its own type, message and stacktrace (depth 3, cycle-safe), rendered as the Caused-by chain on the issue page. addBreadcrumb({ category, type, level, message, data })keeps a bounded trail (last 50, per-request inside an adapter scope) attached to every event.setContext(name, data)groups arbitrary structured data on the event (contextson the wire), redacted like everything else.- Call
await BugWatch.close()on SIGTERM so the queue drains before exit.
5 - Framework adapterslink
Every framework below has its own dedicated page (in this section's sidebar) with install, wiring order, identity, tracing, a complete runnable example and troubleshooting: Express, Fastify, Koa, Hono, Hapi, NestJS, Next.js, React, Vue, Browser, and Node Workers. The table here is the quick summary.
Every adapter accepts a getUser option, resolved lazily at capture time, which replaces the auth-middleware pattern entirely; an explicit setRequestUser still wins. Adapters never swallow errors: they capture, then rethrow or call next(err).
Every server adapter also reads the inbound W3C traceparent header into the request scope, so captures and logs join the caller's trace automatically; when the header is absent the scope synthesizes a fresh trace id, so every request is traceable end to end either way (see Distributed tracing below).
| Stack | Subpath | Entry points and notes |
|---|---|---|
| Express | ./express | Request handler first, error handler last; bugWatchBrowserSessionHandler for the mint route |
| Fastify / Koa / Hono / Hapi | ./fastify etc. | bugWatchFastify, bugWatchKoa, bugWatchHono (pass opts.context to read Hono's c), createBugWatchHapiPlugin (opens a per-request scope at onRequest, so setRequestUser/setRequestTag/setRequestContext work inside handlers) |
| NestJS | ./nest | BugWatchExceptionFilter, bugWatchNestMiddleware |
| Next.js and serverless | ./next (server) · ./next/client (browser) | withBugWatchRouteHandler opens a per-request scope and flushes before return (same wrapper pattern for Lambda, Vercel, Netlify); the client entry ships initBrowser, BugWatchErrorBoundary and captureRenderError with no Node built-ins |
| React / Vue | ./react, ./vue | BugWatchErrorBoundary (captures render errors with component stack; renders nothing without a fallback), createBugWatchVuePlugin (preserves an existing errorHandler) |
6 - Logger integrationslink
Each logger has its own dedicated page in this section's sidebar (Pino, Winston, Bunyan, log4js, Console, Native Logger) covering wiring, level and tag mapping, error handling and a complete example. Quick summary:
| Logger | Factory |
|---|---|
| Native | createLogger() with child(bindings), zero deps, echoed locally per the console option |
| Pino | createBugWatchPinoDestination (only scalar fields become tags; objects and arrays are dropped) |
| Winston | createBugWatchWinstonTransport (error 50, warn 40, info 30, verbose and debug 20, silly 10) |
| Bunyan / log4js | createBugWatchBunyanStream, bugWatchLog4jsAppender |
| console | captureConsole(client, { levels }), returns a restore() |
7 - Testinglink
import { InMemoryTransport } from "@newinstance/bugwatch/testing";
const transport = new InMemoryTransport();
const client = new BugWatchClient(opts, { transport });
transport.events; transport.find(pred); transport.reset();InMemoryTransport intercepts events only; spans export separately over fetch. In tests either init with enabled: false, avoid flushing spans, or inject a fake fetch through the SpanExporter constructor.
8 - Distributed tracinglink
The SDK ties errors and logs to end-to-end traces and creates spans of its own, so BugWatch reconstructs a whole request across services (JS, PHP, and anything speaking W3C traceparent). Set serviceName at init so spans land on the right node of the service map.
Propagation is automatic. Server adapters read the inbound traceparent header; requests without one get a synthesized trace id so nothing is untraceable. For outbound calls, wrapFetch(client) returns a traced fetch that opens a client-kind span, injects traceparent (never overwriting an existing one), tags the response status and marks 5xx or network failures as error spans. Where fetch is not in play, BugWatch.traceHeaders() returns { traceparent } for any HTTP client or message payload, and parseTraceparent(header) + BugWatch.setTraceContext(traceId, spanId) join a trace on the receiving side.
Spans. withSpan(name, fn, opts) times a callback, links every capture and log inside it to the span, records a thrown exception on the span (with stacktrace and error status) and rethrows. startSpan(name, opts) gives manual control with setAttr, recordException, end(status) and span.traceparent() for outbound headers. Options: kind (1 internal, 2 server, 3 client, 4 producer, 5 consumer), attrs, traceId, parentSpanId, and links (point a queue consumer's span at the producer's { traceId, spanId } and the service map draws the async edge). Attach code.filepath, code.lineno and code.function attributes and the span detail shows the source location.
const result = await BugWatch.withSpan("db.query load-cart", async (span) => {
span?.setAttr("db.system", "postgresql");
return loadCart(cartId);
}, { kind: 3 });Limits and delivery. 50 attributes (200-char keys and values, scalars only), 20 events, 10 links, 200-char names, 8000-char exception stacktraces, 200-span buffer with oldest dropped. Spans post as OTLP JSON to POST /v1/traces with the same DSN key and flush together with events on flush()/close(). Spans need a projectKey: browser sessionUrl mode captures errors and logs but does not create spans. If your app already runs OpenTelemetry, skip BugWatch spans and pass traceContextProvider: otelTraceContextProvider() from ./otel so events join your OTel traces instead.
9 - Readable stack traceslink
Node stacks are readable as-is. React Native bundles resolve through the source map you upload in CI (see Mobile → React Native). For minified web bundles, server-side source-map resolution is not available yet: keep source maps deployed next to your bundles so browser devtools resolve frames.
Production checklistlink
await BugWatch.close()on SIGTERM;installNodeErrorHandlersat boot.- Keep the DSN key out of the repo and rotate it from the dashboard on exposure.
- Browsers get
sessionUrlthroughinitBrowser, neverprojectKey.
Troubleshootinglink
- 401 Invalid or inactive API key or 400 This API key is not a BugWatch project key: you are using an org key; use the project DSN key from Settings under DSN keys.
- Events missing from a short-lived script: the batch timer never fired; call
await BugWatch.flush(). - Pino fields missing as tags: only string, number, boolean and bigint values map to tags.
BugWatchResolutionErrorin the console: a bundler resolved@newinstance/bugwatch/browserto a type-declaration stub, almost always atsconfigpathsalias rewriting runtime imports. Delete the alias; types resolve without it since v0.2.0. The app keeps running on a no-op client meanwhile.- Spans missing but errors arriving: browser
sessionUrlmode creates no spans (server-sideprojectKeyonly); withflushInterval: 0spans leave only onflush()/close(). - A trace does not join the caller's: the inbound
traceparentwas malformed, so the request minted a fresh trace id; validate the header withparseTraceparenton the sending side.
Wire endpoints used: POST /api/v1/bugwatch/ingest (NDJSON, up to 5000 events per request), POST /api/v1/bugwatch/browser-session, POST /api/v1/bugwatch/ingest/browser, and POST /v1/traces (OTLP JSON, spans).