Runtime recorder setup
Turn on automatic instrumentation, scope each request or job, then optionally tune limits and redaction.
Enable automatic instrumentation
Rasputin wraps your functions so it can show what ran when an error happened. Recorded state is kept only while a request or job is running, and is attached when that work throws.
Compiled JavaScript
Use this in production. Build as you already do, then run rasputin-instrument on the output. Start the
process normally — no extra flags.
{
"scripts": {
"build": "tsc && rasputin-instrument dist",
"start:node": "node dist/app.js",
"start:bun": "bun dist/app.js"
}
}Have TypeScript write .js.map files next to the
output so recorded functions still point at your .ts sources.
Running TypeScript directly
rasputin-instrument rewrites compiled .js files on disk. It does not run when you start
TypeScript with Bun. Use --preload / --import so files are wrapped as they load.
{
"scripts": {
"start:bun": "bun --preload @rasputin-ai/elysia/instrument/bun src/app.ts",
"start:node": "node --import @rasputin-ai/elysia/instrument/node dist/app.js"
}
}Use those flags while you run TypeScript locally. In production, instrument at build time instead — wrapping files at startup is slower and uses more memory.
Scope each unit of work
The Elysia plugin already creates one execution per HTTP request. For background jobs, scheduled tasks, or worker iterations, wrap the work yourself:
const scope = client.execution.createScope({
kind: 'job',
name: 'sync-invoices',
});
try {
return await scope.run(() => syncInvoices());
} catch (error) {
const observation = scope.observeError(error);
client.captureException(error, { observation });
} finally {
scope.finish();
}scope.run() executes the callback under this
recording's async context and returns exactly what the callback returned. It does not wrap,
replace, or observe Promises. You own await, observeError, captureException, and finish().
If automatic instrumentation is not available — for example because the app is bundled into a single file — mark the functions you care about:
const syncInvoices = client.execution.trace(
'src/jobs/sync-invoices.ts:syncInvoices',
async () => {
// ...
},
);Configuration
Recording is on by default once instrumentation and executions are in place. Change these only to exclude noisy code, redact sensitive fields, or keep less state.
| Field | Default | What it does |
|---|---|---|
enabled | true | Turns runtime-state capture on or off. |
maxEventsPerExecution | 500 | Maximum events kept for one execution. |
maxCapturedCallsPerFunction | 3 | Detailed successful calls kept before similar calls are summarized. |
maxActiveMemoryBytes | 64 MiB | Maximum recorder memory shared across active executions. |
maxDepth | 3 | Maximum captured value depth. |
maxObjectKeys | 30 | Maximum properties kept from one object. |
maxArrayElements | 20 | Maximum items kept from one array, map, or set. |
maxStringLength | 500 | Maximum characters kept from one string. |
maxSerializedValueBytes | 16 KiB | Maximum retained size of one captured value. |
runtimeStateTargetBytes | 256 KiB | Soft size goal for the snapshot sent with an error; extra reconstruction data is dropped first. |
maxRuntimeStateBytes | 512 KiB | Hard size limit for that snapshot; values are dropped before call history. |
redactKeys | [] | Extra case-insensitive property names to redact. |
excludeSources | [] | Source globs or function-name patterns to exclude. |
const { client, plugin } = RasputinInit({
// ...required options
executionRecorder: {
excludeSources: ['src/logger/**', 'packages/shared-logger/**'],
redactKeys: ['customerEmail'],
maxEventsPerExecution: 200,
},
});redactKeys.Diagnostics
These counters are local to the current process and reset when it restarts.
const { recorder, transport } = client.getStats();