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

@2ez4dan/zbun

v0.1.5

Published

The fastest structured logging library for Bun — zero dependencies, zero allocations, lock-free async I/O

Readme

ZBun

The fastest structured logging library for Bun.

ZBun merges the zero-allocation fluent API of Go's zerolog with the lock-free asynchronous I/O of C++'s spdlog — built from scratch for the Bun runtime. Zero dependencies.

Benchmarks

Structured logging (str + int + bool fields) writing to /dev/null, Bun 1.3.10, 100k iterations x 5 rounds (median):

| Logger | Ops/sec | vs ZBun | |--------|---------|---------| | zbun | 2,888,549 | — | | pino | 931,519 | 3.10x slower | | winston | 1,016,133 | 2.84x slower |

ZBun achieves 3.1x higher throughput than pino and 2.8x over winston with zero memory overhead (0KB heap delta).

Install

bun add @2ez4dan/zbun

Requires Bun >= 1.1. Recommended: Bun >= 1.3.x.

Quick Start

import { createLogger } from '@2ez4dan/zbun';

const log = createLogger({
  level: 'info',          // 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'
  target: 'stdout',       // or a file path like './app.log'
});

log.info()
   .str('module', 'auth')
   .int('userId', 12345)
   .bool('success', true)
   .msg('User login completed');

// Output: {"level":"info","time":1709568000000,"module":"auth","userId":12345,"success":true,"message":"User login completed"}

API

Log Levels

log.trace()  // Level 0
log.debug()  // Level 1
log.info()   // Level 2
log.warn()   // Level 3
log.error()  // Level 4
log.fatal()  // Level 5

If a level is disabled, the call returns a frozen no-op singleton — zero allocation, zero buffer writes.

Typed Field Methods

log.info()
   .str('name', 'alice')        // String field (JSON-escaped)
   .int('age', 30)              // Integer (no toString() allocation)
   .float('score', 9.5)         // Floating point
   .bool('active', true)        // Boolean
   .any('meta', { x: 1 })       // Any value (JSON.stringify fallback for objects)
   .msg('user created');

Type-specific methods (.str, .int, .bool) are faster than .any because they skip type checking and use optimized byte encoding paths.

Error Logging

try {
  await db.connect();
} catch (e) {
  log.error()
     .err(e)                    // Extracts message + stack trace
     .str('host', 'db-1')
     .msg('Connection failed');
}

Child Loggers

Attach persistent context fields that are included in every log entry:

const reqLog = log.with()
                  .str('reqId', 'req-55aa77')
                  .str('path', '/api/users')
                  .logger();

reqLog.info().msg('Request started');
// Output: {"level":"info","time":...,"reqId":"req-55aa77","path":"/api/users","message":"Request started"}

// Child loggers nest — grandchild inherits all parent context
const dbLog = reqLog.with().str('db', 'postgres').logger();

Context fields are pre-serialized as a byte array when .logger() is called. On each subsequent log call, they are copied directly into the buffer — O(1) cost regardless of how many context fields exist.

Async Mode

For maximum throughput, enable async mode to offload I/O to a background Worker thread:

const log = createLogger({
  level: 'info',
  target: 'stdout',
  async: true,                  // Enables SharedArrayBuffer + Worker
  bufferSize: 1024 * 1024,      // 1MB ring buffer (default)
});

// Logs are written to a lock-free ring buffer on the main thread,
// then flushed to I/O by a background Bun Worker.
log.info().str('fast', 'path').msg('Non-blocking');

// Flush before exit to ensure all buffered logs are written
await log.flush();

If the ring buffer fills up, ZBun transparently falls back to synchronous writing — no messages are ever dropped.

.send() — Log Without a Message

log.info().str('event', 'heartbeat').int('uptime', 3600).send();
// Output: {"level":"info","time":...,"event":"heartbeat","uptime":3600}

How It's Fast

  1. Manual byte encoding — JSON is built byte-by-byte into a pre-allocated Uint8Array. No JSON.stringify(), no intermediate string objects.

  2. Buffer pooling — Each logger holds a stack of reusable write buffers. In steady state, zero allocations per log call.

  3. No-op singleton — Disabled log levels return a frozen Object.freeze'd singleton. Every chained method is a no-op that returns itself.

  4. Type-specific formatters.int(k, 42) writes ASCII digits directly via % 10 extraction (no Number.toString() allocation). .bool(k, true) writes the 4 pre-encoded bytes true.

  5. Pre-computed contexts — Child logger context fields are serialized once as raw bytes, then memcpy'd on each log call.

  6. Lock-free async I/O — Async mode uses a SPSC ring buffer over SharedArrayBuffer with Atomics for zero-contention, zero-copy handoff to a background Worker.

Configuration

interface LoggerOptions {
  level?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'silent';
  target?: 'stdout' | string;   // 'stdout' or a file path
  async?: boolean;               // Enable background Worker transport
  bufferSize?: number;           // Ring buffer size in bytes (default: 1MB)
}

Running Tests

bun test

Running Benchmarks

bun add -d pino winston
bun run benchmarks/run.ts

License

MIT