# @wrnexus/tracking > Error tracking for WrNexus apps: capture exceptions manually or via middleware and fan them out to pluggable sinks. Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework. ## Overview `@wrnexus/tracking` is a small, server-side error-capture layer. You create a tracker with one or more **sinks**, then feed it errors — either manually with `tracker.capture(err, context)` or automatically by mounting `tracker.middleware()` in your request pipeline. A `consoleSink` is included; forwarding to Sentry, Datadog, or any other backend is just a matter of writing a tiny sink. Reach for it when you want a single, sink-agnostic place to route application errors. Sinks run best-effort — a throwing sink never breaks the request. ## Installation ```bash bun add @wrnexus/tracking ``` > Private package — the machine must be authenticated to the `wrnexus` npm org > (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported). ## API ### `createTracker(options?): Tracker` Creates a tracker. `TrackerOptions`: | Option | Type | Description | | ------------ | ------------------------------------------- | --------------------------------------------------------------------------- | | `sinks` | `ErrorSink[]` | Initial sinks to fan events out to. Defaults to `[]`. | | `now` | `() => number` | Clock used for `event.timestamp` (epoch ms). Defaults to `Date.now`. | | `beforeSend` | `(event: ErrorEvent) => ErrorEvent \| null` | Scrub/enrich an event before it reaches any sink. Return `null` to drop it. | The returned `Tracker`: | Member | Signature | Description | | ------------ | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `capture` | `(error: unknown, context?: Record) => Promise` | Normalizes any thrown value into an `Error`, builds an `ErrorEvent`, runs `beforeSend`, then dispatches to all sinks. Non-`Error` values are wrapped in an `Error` named `NonError`. | | `addSink` | `(sink: ErrorSink) => void` | Registers an additional sink at runtime. | | `middleware` | `() => Middleware` | Returns a WrNexus `Middleware` that captures any error thrown downstream, then re-throws it so the framework's error handler still produces the response. | The middleware attaches this context to captured events: ```ts { method: ctx.req.method, path: ctx.url.pathname, requestId: ctx.locals.requestId } ``` ### `consoleSink: ErrorSink` A built-in sink that logs a compact one-line message via `console.error`, e.g. `[error] TypeError: cannot read x {"userId":42}`. ### Types ```ts interface ErrorEvent { error: Error; context: Record; // request info, user id, tags… timestamp: number; // epoch ms } interface ErrorSink { name?: string; capture(event: ErrorEvent): void | Promise; } ``` ## Usage Manual capture: ```ts import { createTracker, consoleSink } from "@wrnexus/tracking"; const tracker = createTracker({ sinks: [consoleSink] }); try { await doWork(); } catch (err) { await tracker.capture(err, { userId: 42, op: "doWork" }); throw err; } ``` As request middleware: ```ts import { createTracker, consoleSink } from "@wrnexus/tracking"; const tracker = createTracker({ sinks: [consoleSink] }); app.use(tracker.middleware()); // captures + re-throws downstream errors ``` A custom sink with `beforeSend` scrubbing: ```ts import { createTracker, type ErrorSink } from "@wrnexus/tracking"; const sentrySink: ErrorSink = { name: "sentry", async capture(event) { await Sentry.captureException(event.error, { extra: event.context }); }, }; const tracker = createTracker({ sinks: [sentrySink], beforeSend(event) { delete event.context.password; // scrub secrets return event; // return null to drop the event entirely }, }); tracker.addSink(anotherSink); // add more sinks later ``` ## Requirements / Notes - Runs on **Bun** only (Node is not supported). - Peer package: [`@wrnexus/core`](../core) — the `Context` and `Middleware` types used by `tracker.middleware()` come from there. - Sink dispatch is fire-and-forget-safe: all sinks run via `Promise.all`, and a sink that throws is swallowed so it can never break the app.