Reporting & context
report() is the verb you'll call most. It takes anything, structures it safely, and attaches whatever context you give it.
Hand it anything#
No pre-flattening. No defensive JSON.stringify. Pass the value you have:
story.report(await response.json()); // any API payload story.report(caughtError); // cause chain preserved story.report(new Map([["region", "us-east"]])); story.report(circularObject); // marked, never thrown story.report({ deployToken: "dt-9f2c-abc" }); // → "[redacted]"
| You pass | The record holds |
|---|---|
| a string | the note text, nothing else |
an Error | name, message, stack, and the full cause chain |
an object with a message or title | that field as the text, the whole object as what |
a Map or Set | tagged with @type, entries preserved |
| a class instance | plain object tagged with the class name |
| a circular structure | [Circular → path] at the loop |
| something huge | truncated, with an explicit @truncated marker so you can tell |
{ apiKey, password, token, … } | [redacted] |
The normalizer never throws. A hostile object — a getter that explodes, a Proxy that traps everything — cannot break the pipeline.
Redaction matches key names, not values. It's defense in depth, not a guarantee. Don't rely on it as your only line.
Context#
Any beat can carry context. Use whichever fields make sense; the rest are simply absent from the record.
Field Meaning whoWho did it — a user id, a service, a role whatWhat was involved — an amount, a payload, an id whereWhere it happened — a component, a route, an upstream errorAny thrown value, normalized levelinfo · warn · oops, for this beat alone liveEmit this beat now, even in collected narration toDeliver this beat only to the named audiences
context.ts story.report("Card charged", {
who: { id: "user:413", role: "member" },
what: { amount: "$49.99", method: "visa" },
where: "stripe-api",
});
story.report("Write failed", {
where: "primary-db",
error: new Error("connection timeout"),
});
Levels#
Three levels, on stories and on individual beats. level accepts the short forms shown here, or the stored labels Information, Warning, Error.
info
Everything went well. The default.
story.finish("Checkout completed") warn
Something was off, but it's handled.
story.finish("Retried", { level: "warn" }) oops
Something broke. Pass the error.
story.finish("Failed", { level: "oops", error })