Skip to content

Add error serialisation, source maps, transports, tracing and level control - #1

Merged
developerEhsan merged 2 commits into
mainfrom
feat/observability-suite
Jul 27, 2026
Merged

Add error serialisation, source maps, transports, tracing and level control#1
developerEhsan merged 2 commits into
mainfrom
feat/observability-suite

Conversation

@developerEhsan

Copy link
Copy Markdown
Owner

Errors were the headline bug: log.error(err) printed {} because every interesting field on an Error is non-enumerable or on the prototype, and that is the single most common call a logger receives. serializeError() now covers cause chains, AggregateError, own extras (code, statusCode, Next's digest), non-Error throws, throwing getters and cycles, and dispatch() normalises the four call shapes people actually write — including hoisting data.error and errors nested anywhere inside data. Stacks are stored as frame arrays, not strings, so the formatter can own the line prefix (a relayed frame is attacker-controlled text) and so frames can be source-mapped individually.

Source-map resolution turns minified relayed browser stacks and the Turbopack chunk path in caller back into real filenames. VLQ decoding is hand-rolled rather than pulling in a WASM trace-mapping library for a best-effort dev-time feature. Filesystem access is deliberately bundler-invisible: process.getBuiltinModule where available, otherwise a dynamic import whose specifier is assembled with a join, because esbuild constant-folds string concatenation back into a resolvable literal and Next's Edge scanner then flags it in every consumer chunk.

Transports gain batching, jittered retry, bounded buffers with counted drops and per-sink isolation, plus file/HTTP/OTLP/Pino/Winston adapters and Datadog, Axiom and Better Stack presets. Throwing from write() means "retry me", so a permanent 4xx returns and reports instead of hammering a vendor forever with a payload it will never accept. The old (entry) => void form still runs inline, unchanged.

Also: uncaught browser errors and unhandled rejections reach the terminal with no call site, additively and without preventDefault; onRequestError and process handlers for instrumentation.ts; time/timer/withLogging; W3C and OpenTelemetry trace correlation; LOG_LEVEL per-namespace rules with last-match-wins precedence; a Redis-backed relay cap that fails open; and optional Standard Schema validation of data that never suppresses a log line.

Two new public entry points (./transports, ./security/rate-limit-redis) plus ./instrumentation, all wired through tsup and the exports map. check-edge-safety now rejects resolvable node: specifiers.

303 tests, typecheck and both build gates pass.

…ontrol

Errors were the headline bug: log.error(err) printed {} because every
interesting field on an Error is non-enumerable or on the prototype, and
that is the single most common call a logger receives. serializeError()
now covers cause chains, AggregateError, own extras (code, statusCode,
Next's digest), non-Error throws, throwing getters and cycles, and
dispatch() normalises the four call shapes people actually write —
including hoisting data.error and errors nested anywhere inside data.
Stacks are stored as frame arrays, not strings, so the formatter can own
the line prefix (a relayed frame is attacker-controlled text) and so
frames can be source-mapped individually.

Source-map resolution turns minified relayed browser stacks and the
Turbopack chunk path in `caller` back into real filenames. VLQ decoding
is hand-rolled rather than pulling in a WASM trace-mapping library for a
best-effort dev-time feature. Filesystem access is deliberately
bundler-invisible: process.getBuiltinModule where available, otherwise a
dynamic import whose specifier is assembled with a join, because esbuild
constant-folds string concatenation back into a resolvable literal and
Next's Edge scanner then flags it in every consumer chunk.

Transports gain batching, jittered retry, bounded buffers with counted
drops and per-sink isolation, plus file/HTTP/OTLP/Pino/Winston adapters
and Datadog, Axiom and Better Stack presets. Throwing from write() means
"retry me", so a permanent 4xx returns and reports instead of hammering
a vendor forever with a payload it will never accept. The old
(entry) => void form still runs inline, unchanged.

Also: uncaught browser errors and unhandled rejections reach the terminal
with no call site, additively and without preventDefault; onRequestError
and process handlers for instrumentation.ts; time/timer/withLogging;
W3C and OpenTelemetry trace correlation; LOG_LEVEL per-namespace rules
with last-match-wins precedence; a Redis-backed relay cap that fails open;
and optional Standard Schema validation of data that never suppresses a
log line.

Two new public entry points (./transports, ./security/rate-limit-redis)
plus ./instrumentation, all wired through tsup and the exports map.
check-edge-safety now rejects resolvable node: specifiers.

303 tests, typecheck and both build gates pass.
@developerEhsan
developerEhsan merged commit 55b0df8 into main Jul 27, 2026
1 check passed
@developerEhsan
developerEhsan deleted the feat/observability-suite branch July 27, 2026 13:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant