Add error serialisation, source maps, transports, tracing and level control - #1
Merged
Merged
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
callerback 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.