npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@all-wits/witslog

v0.6.4

Published

Framework-agnostic Node.js SDK for witslog structured error logging

Readme

🪵 witslog (Node.js SDK)

npm License: Apache 2.0 node CI

Framework-agnostic Node.js SDK for witslog structured error logging.


Thin wrapper over the native witslog-ffi C ABI via koffi — one dependency, prebuilt, no native build step. As of 0.4.0 it also bundles the real witslog CLI binary per platform, so witslog query/stats/export/serve-mcp/doctor etc. (the read/ops surface that has no FFI equivalent — see CONTRACT.md) work straight after install, no separate CLI install required. See CONTRACT.md for the full SDK↔native ABI, and CHANGELOG.md for this package's release history.

📦 Install

npm install @all-wits/witslog
pnpm add @all-wits/witslog
bun add @all-wits/witslog

Native libraries and the witslog CLI binary for Windows x64, Linux x64/arm64, and macOS (Apple Silicon) are bundled — npm install / pnpm add / bun add alone is enough on those platforms, for both the SDK and npx witslog <command> / a global-install witslog on your PATH. See Platform support below for the current gap.

🚀 Quick Start

const witslog = require('@all-wits/witslog');

witslog.init({ createProject: true }); // scaffolds .witslog/ if missing, then mounts
witslog.error('myapp', 'db timeout', { context: { request_id: 'r1' }, tags: ['db'] });

try {
  risky();
} catch (e) {
  witslog.exception('myapp', e);      // captures err.stack (and, if e.cause is set —
                                       // e.g. Node's own fetch() failures — the full
                                       // cause chain, folded into stacktrace + context.root_cause)
}

init() needs a .witslog/ project directory to write into — pass createProject: true (scaffolds one at process.cwd()) or createProject: '/path/to/project' the first time you mount in a fresh project; it's a no-op on later runs once .witslog/ already exists.

As of 0.4.0, npm install @all-wits/witslog also gives you the real witslog CLI — on the 4 bundled platforms (see Platform support), a plain npm install wires up npx witslog <command> (and a global install puts witslog on your PATH) with the same binary docs/install.md or Homebrew/Scoop/cargo install would give you — witslog init, witslog query, witslog stats, witslog serve-mcp, all of it:

npx witslog init .
npx witslog query "db timeout*"

On a real terminal, npx witslog init also asks (arrow keys/spacebar/enter, plain language, no jargon) whether you want to protect sensitive data you might log later — say yes and it generates a secret key, shows it to you once (never saved to disk — you store it yourself), and wires up .witslog/config.toml. Skip the prompt with npx witslog init --encrypt or --yes; piped/CI usage never sees a prompt either way. Revisit the choice anytime with npx witslog config. See CONTRACT.md's "Metadata encryption" section for what this does and doesn't cover.

For programmatic use createProject: true remains the way to scaffold .witslog/ from code without shelling out:

const witslog = require('@all-wits/witslog');
witslog.init({ createProject: true }); // scaffolds .witslog/, cross-platform

If you also separately install the witslog CLI (cargo install, Homebrew, Scoop, or a release binary), or the bundled binary isn't available for your platform, point WITSLOG_CLI=/path/to/witslog at it — the npm-bundled and separately-installed CLIs are interchangeable, pick whichever is already in your toolchain.

⚠️ For MCP (AI-assistant integration), install the CLI globally instead of relying on the npm-bundled binary — see the root README's MCP section for why: macOS Intel has no npm-bundled CLI at all (only curl/irm/ Homebrew/Scoop/cargo install cover it), and an MCP config generated from a path inside this project's node_modules/ breaks if node_modules is ever removed or reinstalled elsewhere. The npm-bundled npx witslog <command> is for ad-hoc/manual use from inside a project — a globally installed CLI is what serve-mcp --print-mcp-config should point an MCP client at.

ℹ️ MCP tools are self-teaching: initialize returns worked-example instructions, and every tool description carries an Example: {...} call — helps lightweight/under-informed models pick the right tool (e.g. search_errors vs. latest_errors) on the first try. See the root README's MCP section.

🔒 Security: argv enrichment defaults on and captures the full command line. If your app may receive secrets as bare CLI args, call witslog.init({ enrich: { argv: false } }) — see CONTRACT.md.

🧩 Express

const { witslogErrorHandler } = require('@all-wits/witslog/frameworks/express');

witslog.init();
app.use(witslogErrorHandler('myapp'));   // last, after routes

🌐 Browser-side error capture

Pairs with the browser reporter — a zero-dep client that batches window.onerror / unhandled-rejection events (plus, opt-in, console.error/console.warn and resource-load failures) and ships them via navigator.sendBeacon to this ingest endpoint. Available two ways: as the npm subpath @all-wits/witslog/browser (0.6.1+, packaged copy) for bundler/import usage, or the standalone bindings/browser/witslog-browser.js for a plain <script src> — same API either way.

import WitslogBrowser from '@all-wits/witslog/browser';

const reporter = WitslogBrowser.init({
  endpoint: '/api/witslog-ingest',
  app: 'my-web-app',
  captureConsole: true, // also captures console.error/warn + resource-load failures
});
const { witslogBrowserIngest } = require('@all-wits/witslog/frameworks/express');

app.use(witslogBrowserIngest({
  allowedOrigins: ['https://your-app.example'], // required — fail-closed, default []
}));

🔒 Security: the request body is untrusted input that lands in events.message, which MCP serves verbatim to an AI assistant. This handler is armed fail-closed: empty origin allowlist by default, refuses to run under NODE_ENV=production unless { force: true }, rate-limited per client, and severity clamped to error/warn (never fatal/critical). tags: ['browser'] is advisory only, not a trust boundary. See CONTRACT.md for the Python/PHP ingest recipe and the full guardrail rationale.

🧵 Zero-boilerplate auto-instrumentation

Mount instrumentation once instead of hand-writing try/catch + witslog.exception/witslog.error at every route handler and outbound fetch call.

Instrumented fetch — witslogFetch

Explicit wrapper around fetch (no global monkeypatch — safe alongside Next.js's own fetch caching/instrumentation). Swap it in at your outbound-request choke points:

const { witslogFetch } = require('@all-wits/witslog/fetch');

const res = await witslogFetch(upstreamUrl, init, {
  application: 'my-proxy',
  tags: ['proxy'],
  context: { path: '/cards/123' },
});

Auto-captures a correlation id (x-request-id by default, propagated to the outbound request), context.timing.latency_ms, and on failure:

  • Thrown error (network unreachable/timeout) — logs via exception() (full .cause chain, error_code: 'UPSTREAM_UNREACHABLE'), then rethrows the original error unchanged.
  • Non-2xx response — peeks the body via .clone() (your code still gets the untouched Response), extracts error_code/message/details from a {error:{code,message,details}} body when present, and logs at warn for 4xx (expected client-caused conflicts) / error for 5xx.

Next.js adapter

// instrumentation.ts — Next.js's own server-boot hook. Captures every uncaught error in
// route handlers, Server Components, Server Actions, and middleware — zero per-route code.
import { register as registerWitslog, onRequestError as witslogOnRequestError } from '@all-wits/witslog/frameworks/next';

export function register() {
  registerWitslog('my-app', { createProject: true });
}
export const onRequestError = witslogOnRequestError;

withWitslog(handler, opts?) wraps a single route handler explicitly, for Next < 15 or when you want per-route timing/correlation without global instrumentation.

witslogNextIngest(options) is the Next.js Route Handler-shaped equivalent of witslogBrowserIngest above (Express's raw req/res and Next's Web Request/Response aren't interchangeable, so this is a separate export, not a re-export — same guardrails):

// app/api/witslog-ingest/route.ts — do NOT start the folder name with `_`;
// Next.js's App Router treats any path segment starting with `_` as a
// private folder excluded from routing, so app/api/__witslog/route.ts
// would silently never register a route (every POST 404s).
import { witslogNextIngest } from '@all-wits/witslog/frameworks/next';
export const POST = witslogNextIngest({ allowedOrigins: ['https://your-app.example'] });

React Query client capture

Subscribes to a TanStack QueryClient's MutationCache/QueryCache — the same event stream TanStack Query Devtools itself observes — so every failed query/mutation (key, variables, error) is captured with zero per-hook code. Browser-safe, no hard @tanstack/react-query dependency.

import { attachWitslog } from '@all-wits/witslog/frameworks/react-query';

// `report` is any {enqueue(event)} sink — typically WitslogBrowser.init(...)
// from bindings/browser/witslog-browser.js, which ships events to witslogNextIngest/witslogBrowserIngest
attachWitslog(queryClient, { report: myBrowserReporter, tags: ['my-app'] });

See CONTRACT.md for the full design (including the context.root_cause convention exception()/witslogFetch use, and how clampContext bounds what an ingest endpoint accepts).

Correlation id + network-tab-equivalent capture

Closes the "a React Query failure can't be correlated with the proxy log for the same request" gap, plus transport-layer failures React Query never sees (WebSocket disconnects, direct axios calls).

// client.ts — mints/reuses a correlation id per request (propagated as a header,
// default x-request-id), stamps correlationId/latencyMs onto the response/rejected
// error. Does NOT log every rejection itself (attachWitslog already captures every
// React-Query-managed failure) — opt a specific call into direct capture instead:
import { witslogAxiosInterceptor } from '@all-wits/witslog/frameworks/axios';

witslogAxiosInterceptor(apiClient, { report: myBrowserReporter, tags: ['my-app'] });

// a call that bypasses React Query entirely (e.g. an imperative token fetch)
apiClient.get('/collab/ticket', { witslogDirectCapture: true });

frameworks/react-query.js's buildEvent reads error.correlationId/error.latencyMs (when stamped by the interceptor above) into correlation_id/context.timing.latency_ms, and independently computes latency from TanStack Query v5's state.submittedAt/state.errorUpdatedAt when those aren't present — no extra wiring needed for either.

bindings/browser/witslog-websocket.js's witslogWebSocketWatch(opts) (vendored file, alongside witslog-browser.js — not an npm subpath) returns {onClose, onDisconnect} handlers shaped for HocuspocusProvider's constructor options, logging abnormal closes (code not 1000/1001) with error_code: WS_CLOSE_<code> and context.ws: {code, reason, wasClean}:

const { witslogWebSocketWatch } = require('./witslog-websocket');
const watch = witslogWebSocketWatch({ report: myBrowserReporter });
new HocuspocusProvider({ ..., onClose: watch.onClose, onDisconnect: watch.onDisconnect });

Hocuspocus/Yjs collab-provider adapter (Node-side)

frameworks/hocuspocus.jsattachWitslogHocuspocus(provider, opts) — is the Node-process counterpart to witslog-websocket.js above, purpose-built for a HocuspocusProvider (or any EventEmitter-shaped target exposing on(event, fn)/off(event, fn)) rather than a raw CloseEvent hook. Two improvements over the vendored watcher for this target specifically: isAbnormalClose(code, wasClean) treats any wasClean:true close as normal — the vendored watcher checks code alone, so a clean disconnect that synthesizes close code 1005 ("No Status Rcvd", which the browser does locally on a routine provider.destroy()/tab-nav/HMR reload) is misclassified as abnormal — and it additionally captures authenticationFailed (error_code: COLLAB_AUTH_FAILED). Like the vendored watcher, it flushes the reporter immediately after every emit (connection loss/auth failure is rare and urgent, unlike high-volume sources such as react-query/axios that rely on the reporter's batch window), and duck-types against the target's public API — no hard @hocuspocus/provider dependency.

const { HocuspocusProvider } = require('@hocuspocus/provider');
const { attachWitslogHocuspocus } = require('@all-wits/witslog/frameworks/hocuspocus');

const hp = new HocuspocusProvider({ url, name, document, token });
const detach = attachWitslogHocuspocus(hp, { report: myBrowserReporter, tags: ['my-app'] });
// later: detach(); hp.destroy();

Tests: test/hocuspocus.test.js.

🧱 Works with your Node.js stack

@all-wits/witslog is a plain npm/pnpm/bun package with no bundler-specific glue — it works in any Node.js process, which covers the server side of most modern frameworks:

  • Next.js — call it from Route Handlers / API Routes, Server Actions, or Server Components (witslog.init() once at module scope, witslog.error(...) in a catch). Next.js bundles server route code by default (webpack/turbopack), and that breaks resolution of koffi's native .node addon — you'll hit:
    Error: Cannot find the native Koffi module; did you bundle it correctly?
    Fix: tell Next to require() both packages natively instead of bundling them, in next.config.ts:
    const nextConfig: NextConfig = {
      serverExternalPackages: ["@all-wits/witslog", "koffi"],
    };
    (Next.js ≥15; pre-15 use experimental.serverComponentsExternalPackages instead.) This is the same fix any native-addon npm package needs under Next.js (e.g. Prisma, Sharp) — no witslog code change can make a .node binary bundler-safe, so this config is required, not optional.
  • Nuxt.js — same idea inside server routes / the Nitro server (server/api/*.ts, server/plugins/*.ts).
  • Vite — use it in a Vite SSR entry (vite-node, vite-plugin-ssr, a custom server.js) or in vite.config.js build hooks — anywhere Vite code actually executes in Node, not in code shipped to the browser.

⚠️ Not for browser bundles. The native witslog_ffi library is loaded via koffi, which needs a real Node.js process — it cannot run inside client-rendered Vue.js components, React components, or any code that ends up in a browser bundle. If you're using Vue.js/React purely client-side, log from your Node backend (API route, SSR middleware, server action) instead of the browser-rendered component itself.

📖 API

| Function | Description | |----------|--------------| | init(config?) | Mount the SDK; pass { createProject: true } to scaffold .witslog/ first, plus optional enrich/redact/buffer config (see CONTRACT.md). | | error/warn/info(app, message, opts?) | Log at the given severity. opts: context, tags, metadata, error_code, exception, stacktrace, correlation_id, parent_event_id, category, version, environment. | | log(app, message, opts?) | Same as error, explicit severity via opts.severity. | | exception(app, err, opts?) | Log a caught Error, capturing err.stack and, when set, err.cause's full chain (folded into stacktrace + context.root_cause). | | flush() / shutdown() | Drain buffered events before exit. |

Auto-instrumentation (see above)

| Import | Function | Description | |--------|----------|--------------| | @all-wits/witslog/fetch | witslogFetch(input, init, opts?) | Instrumented fetch wrapper. | | @all-wits/witslog/frameworks/next | register(app, config?) / onRequestError(err, req, ctx) / withWitslog(handler, opts?) | Next.js server-error capture. | | @all-wits/witslog/frameworks/next | witslogNextIngest(options) | Browser-ingest endpoint, Next.js Route Handler shape. | | @all-wits/witslog/frameworks/react-query | attachWitslog(queryClient, opts) | Global React Query mutation/query failure capture. | | @all-wits/witslog/frameworks/axios | witslogAxiosInterceptor(axiosInstance, opts?) | Correlation-id propagation + latency stamping on an axios instance. | | @all-wits/witslog/browser (0.6.1+) | WitslogBrowser.init(config) | Browser-side client reporter — window.onerror/unhandled-rejection by default, plus console.error/console.warn + resource-load failures with captureConsole: true. | | bindings/browser/witslog-websocket.js (vendored, not an npm subpath) | witslogWebSocketWatch(opts) | Abnormal WebSocket close/disconnect capture (browser-side). | | @all-wits/witslog/frameworks/hocuspocus | attachWitslogHocuspocus(provider, opts) | Node-side Hocuspocus/Yjs collab-provider adapter — close/disconnect + authenticationFailed capture, wasClean-aware. |

🌍 Platform support

| Platform | Status | |----------|--------| | Windows x64 | ✅ | | Linux x64 | ✅ | | Linux arm64 | ✅ | | macOS arm64 (Apple Silicon) | ✅ | | macOS x64 (Intel) | ⬜ not yet built by CI — see CHANGELOG |

If your platform isn't bundled, point at a local build via WITSLOG_LIB=/path/to/witslog_ffi.* (native lib) and WITSLOG_CLI=/path/to/witslog[.exe] (CLI binary).

🧪 Test

pnpm install && pnpm test

📄 License

Apache License 2.0 — see LICENSE.