Errors
Error-first calls, cause chains, own properties, and how each formatter renders them.
The error goes first#
try {
await charge(order);
} catch (err) {
log.error(err, 'Charge for {OrderId} failed', order.id);
}The first argument of any level method may be an Error. It is kept on the event as event.error — apart from the properties, as Serilog keeps Exception — so every formatter can render it in full and every sink can inspect it.
log.error(err) with no template logs the error's message as the message (braces escaped). log.warn(err, 'Retrying') logs a warning that carries an error; the level is yours to choose.
If what you caught is not an Error — a string, a rejected promise's plain object — wrap it: new Error(String(thing), { cause: thing }).
What is captured#
event.errorProperties is the error as data, computed once:
{
"$type": "Error",
"name": "PaymentError",
"message": "card declined",
"stack": "PaymentError: card declined\n at charge (…)",
"code": "card_declined",
"status": 402,
"cause": { "$type": "Error", "name": "GatewayError", "message": "402", "stack": "…" }
}name,message,stack- every own enumerable property — the
codeon a Node error, thestatuson an HTTP one, whatever yourclass PaymentError extends Errorassigns in its constructor cause, captured recursively (anErrorcause is itself a structure; anything else is captured by the normal rules)errorsof anAggregateError
Redaction applies inside errors too — redact: ['password'] masks a password property a validation library attached. Depth and string limits apply.
How each output renders it#
Pretty console — the stack under the line, indented, each cause under it:
08:12:03.140 ERR [shop.payments] Charge for 1042 failed
PaymentError: card declined
at charge (/app/payments.ts:42:11)
caused by: GatewayError: 402
at post (/app/gateway.ts:17:9)JSON — the structure above under error.
CLEF — the same text as the pretty block, in @x, which is what Seq shows as the exception.
Output templates — {Exception} renders the text block; the default template ends with {NewLine}{Exception} so it goes on the lines after the message.
OTLP — exception.type, exception.message, exception.stacktrace attributes, per the semantic conventions.
Errors as properties#
An Error passed to a hole (not first) is captured as the same structure, under the hole's name, and renders as Name: message inside the message — the stack is not spliced into a sentence:
log.warn('Fallback used after {@Primary}', primaryError);
// Fallback used after GatewayError: 402 … "Primary":{"$type":"Error","name":"GatewayError",…}Crashes#
closeOnExit(log) from @unhingged/logit/node logs an uncaught exception or an unhandled rejection at fatal — Uncaught exception — the process will exit — flushes every sink, then exits with code 1. Without it Node prints the stack and exits before an HTTP sink has sent anything. See Production.
Next.js#
createOnRequestError() logs every error Next catches, with the route, the router kind and the request. React may wrap an error thrown during server rendering; the digest that identifies the original is logged as Digest. See Next.js.
Not yet#
Per-library destructurers — the useful fields of a Prisma, Axios or fetch error, without the noise — are on the roadmap. Today a byTransforming policy does it for the types you care about:
byTransforming(AxiosError, (e) => ({ message: e.message, status: e.response?.status, url: e.config?.url }))A policy applies to errors captured as properties; the first-argument error is always captured in full.