This document describes the changes end users and integrators need to be aware of when upgrading from Scribbles 1.x to 2.0.0. It is published as a living document alongside the v2 work-in-progress branch and will be finalised ahead of the v2.0.0 release.
If you hit a concrete error while migrating,
docs/troubleshooting.mdindexes every common Scribbles v2 error by its exact message text and walks through the fix. The v2.0.1 patch release (Bun CJS-chain fix and trace-across-await fix) is covered there in detail.
If your code looks like this today:
const scribbles = require('scribbles');
scribbles.log('user', user.name); // works, unchanged
scribbles.config({ mode: 'dev' }); // works in CJS, works in v1
scribbles.trace(req, () => { ... }); // works in CJS, works in v1On v2, both forms continue to work in CommonJS, but the recommended v2 idiom is to destructure the non-log-level APIs:
const scribbles = require('scribbles');
const { config, trace, middleware, register } = require('scribbles');
scribbles.log('user', user.name); // unchanged — log levels stay on the object
config({ mode: 'dev' }); // v2 recommended
trace(req, callback); // v2 recommended
app.use(middleware.express); // v2 recommended (middleware is a namespace,
// not a callable — use .express for Express)In ESM (new in v2), the destructured form is the idiomatic way to reach the non-log APIs:
import scribbles from 'scribbles';
import { config, trace, middleware, register } from 'scribbles';
scribbles.log('user', user.name); // works (default export carries log levels)
config({ mode: 'dev' }); // works
app.use(middleware.express); // Express middlewareIn v1.7.0, automatic variable-name extraction was documented as working
whenever you require('scribbles'), but the hook that performs the
source transform was never actually installed on require — only test code
that explicitly required scribbles/src/parsing/loader activated it. The
bulk of downstream users therefore saw plain logs without the feature.
v2 fixes this: require('scribbles') (or import scribbles from 'scribbles'
once the ESM build ships) now installs the CJS source-transform hook
automatically. No user code changes required — if you were already doing
scribbles.log('user', user.name), you now get user userName:<value>
in your output automatically.
Fix reference: commit "feat: auto-install CJS transform hook on require; drop node-hook dependency" in the v2 history.
We inlined the ~25 lines of node-hook that Scribbles actually used into
src/register/hooks/cjs-extensions.js. No external package is required
at install or build time for the CJS loader hook. Bundle output is
unchanged since node-hook never shipped in the runtime bundle.
register joins config, trace, and middleware as a destructurable
named export on the module:
const { register } = require('scribbles');
// Idempotent. Automatically called by require('scribbles') — end users
// don't need to call it explicitly, it's exposed for power users and
// for upcoming introspection helpers.
register();
// Wired in a subsequent task:
// register.status() // { runtime, installed, transformActive, ... }
// register.assert() // throws ScribblesNotRegisteredError if !installedv2.0.0 expands the supported runtimes. The current branch has Node-CJS
and Bun-CJS feature-complete (including variable-name extraction out
of the box). Node-ESM and Bun-ESM land as further v2 tasks complete
and will require a one-line preload as described in
docs/runtime-setup.md (the document lands with the ESM task):
- Node-ESM:
node --import scribbles/register app.mjs - Bun (
bun runand default CLI): addpreload = ["scribbles/register"]tobunfig.toml bun test: add a separate[test]section tobunfig.tomlwith its ownpreload = ["scribbles/register"]. Bun does not share preload configuration betweenbun runandbun test. Seedocs/runtime-setup.mdfor the full snippet.
If the preload is missing in an ESM runtime, Scribbles continues to log with correct file/line/col (via stack traces) but variable-name extraction is unavailable. A one-line warning on every startup points you at the fix.
Bundler support (esbuild, webpack, rollup, bun build) is tracked in follow-up issues on the project's GitHub and is not part of v2.0.0.
- CJS path (Node's
require): Node 8.5.0 — unchanged from v1. - ESM path (
import): Node 20.6+ — the version that stabilisedmodule.register(). ESM users on older Node see a clear error pointing at either upgrading Node or using CJS. - Bun: Bun 1.0+.
Existing CJS users do not need to change their Node version.
scribbles.log,scribbles.info,scribbles.warn,scribbles.error,scribbles.fatal, and any custom level you configure — still properties on the main object, still called the same way.scribbles.status,scribbles.timer,scribbles.timerEnd,scribbles.group,scribbles.groupEnd,scribbles.groupCollapsed— unchanged.- The log-body shape (
body.info,body.context,body.input,body.trace,body.git, etc.) — unchanged. - The
dataOut/stdOutconfig contract — unchanged. - The webpack git-defines helper at
scribbles/gitStatus— unchanged.
-
ESM default export does not carry
config/trace/middleware/register/timer/timerEnd/group/statusas properties.// ESM — v2.0.0 hard cut import scribbles, { config, trace, register } from 'scribbles'; scribbles.log('hello'); // ✅ log levels live on the default export scribbles.config({ ... }); // ❌ undefined — throws TypeError on call config({ ... }); // ✅ use the named import instead
The ESM default export is a
Proxyover the full scribbles object that hides infrastructure APIs (config,trace,middleware,register,timer,timerEnd,group,status). Every one of those is reachable only as a named import.Object.keys(scribbles)in ESM returns only the active log levels, and'config' in scribblesisfalse— the hiding is complete, not just agetintercept.CJS users see no strict breaks:
scribbles.config(...)andconst { config } = require('scribbles')both continue to work.
- HTTP hijacker stale-wrapper leak: in v1.x, when Scribbles was loaded
multiple times in one process (common under Jest's per-file module
reset or in test harnesses that snapshot-restore the module registry),
the second load captured the first load's wrapper as "the original"
and cross-wired old config into new requests — producing malformed
traceparent: "00-undefined-undefined-01"headers on requests that should have passed through untouched. v2 stores the true nativehttp.request/https.requestfunctions on the core modules themselves viaSymbol.for(...), so subsequent module re-evaluations always rewrap the same native original. Seesrc/tracing/hijacker.jsfor the full explanation. scribbles/gitStatuswebpack helper require path: the standalone webpack DefinePlugin helper at the repo root (gitStatus.js) pointed at the pre-reorganisation path./src/getGitStatus. That path has been./src/system/getGitStatussince the source tree was reorganised; the helper has been updated to match, sorequire('scribbles/gitStatus')works again.registernot inresirvedFnNames: users runningscribbles.config({ levels: ['register'] })previously escaped the log-level reservation check and silently clobbered thescribbles.registerfunction. The reservation list now capturesregisterbefore the firstconfig()call runs, so the name is rejected with the same error thrown forconfig,status,timer,timerEnd, andgroup.