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:

anything.ts
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 passThe record holds
a stringthe note text, nothing else
an Errorname, message, stack, and the full cause chain
an object with a message or titlethat field as the text, the whole object as what
a Map or Settagged with @type, entries preserved
a class instanceplain object tagged with the class name
a circular structure[Circular → path] at the loop
something hugetruncated, 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.

FieldMeaning
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 })