129 lines
5.0 KiB
Markdown
129 lines
5.0 KiB
Markdown
# @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<string, unknown>) => Promise<void>` | 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<string, unknown>; // request info, user id, tags…
|
|
timestamp: number; // epoch ms
|
|
}
|
|
|
|
interface ErrorSink {
|
|
name?: string;
|
|
capture(event: ErrorEvent): void | Promise<void>;
|
|
}
|
|
```
|
|
|
|
## 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.
|