Skip to content
rune logoRuneLogging
Browser/Node logger with levels, namespaces, pluggable transports, lazy bindings, and timing helpers.
Version
v2.0.0
Size
3.5 KB gzip
Dependencies
Zero dependencies
BrowserNode ≥22SSRDeno
createLoggerdefaultLoggerconsoleTransportremoteTransportjsonTransport View all 14 exports

Why Rune?

Plain console.log lacks structure: no log levels, no namespacing, no remote delivery, no way to silence logs in production.

ts
// Before — manual approach
const path = '/users';
console.log(`[api] GET ${path}`);
fetch('/api/logs', { body: JSON.stringify({ level: 'error', path }), method: 'POST' });

// After — Rune
import { consoleTransport, createLogger, remoteTransport } from '@vielzeug/rune';

const api = createLogger({
  namespace: 'api',
  transports: [
    consoleTransport({ level: 'debug' }),
    remoteTransport({
      handler: (_type, data) => console.debug('remote log', data),
      level: 'error',
    }),
  ],
});

api.info({ method: 'GET', path }, 'request');
FeatureRuneWinstonPinoconsole
Bundle size3.5 KB~44 kB~4 kB0 kB
Browser support
Scoped loggersManualChild
Pluggable transports Built-in factories Transports Streams
Structured log entry LogEntry typePartial
Lazy bindings lazy(fn)
Styled output CSS badgesText onlyText onlyManual
Zero dependencies (15+) (5+)N/A

Use Rune when you need isomorphic logging (browser + Node.js), namespaced module loggers, or remote error delivery without a heavy dependency chain.

Consider alternatives when you need high-throughput file-based logging (Pino), file rotation (Winston), or your team already uses a logging framework.

Installation

sh
pnpm add @vielzeug/rune
sh
npm install @vielzeug/rune
sh
yarn add @vielzeug/rune

Quick Start

ts
import { batchTransport, consoleTransport, createLogger, lazy, remoteTransport } from '@vielzeug/rune';

const log = createLogger({
  logLevel: 'debug',
  namespace: 'server',
  transports: [
    consoleTransport({ timestamp: true }),
    remoteTransport({
      handler: (_type, data) => console.debug('remote log', data),
      level: 'error',
    }),
  ],
});

const requestLog = log.withBindings({
  diagnostics: lazy(() => ({ queueDepth: 0 })),
  requestId: 'abc-123',
});

requestLog.info({ method: 'GET', path: '/users' }, 'request');
const users = await requestLog.time('load users', () => Promise.resolve(['user-1']));
console.log(users);

const batch = batchTransport({ onFlush: (entries) => console.debug('batch', entries) });
const bufferedLog = createLogger({ transports: [batch.transport] });

bufferedLog.info('queued for delivery');
await batch.dispose();

Features

  • Level filtering (debug to off) with enabled() checks, including fatal above error
  • Immutable config after construction — use child() or withBindings() to scope
  • Three call forms: log.info('msg'), log.error(err, { id }, 'msg') (Error-first), or log.info({ key: 'val' }, 'msg') — Error-first form auto-serializes to data.err
  • Error values in context fields are also auto-serialized to { message, name, stack } — survives JSON.stringify
  • Pinned context bindings via withBindings({ requestId }) — fields on every line
  • Lazy bindings via lazy(fn) — expensive computations gated behind the level check
  • Namespaced child loggers via createLogger('name') or logger.child({ namespace })
  • Middleware pipeline via use(fn) — transform or filter entries before transport dispatch
  • Pluggable transport pipeline: consoleTransport, remoteTransport, jsonTransport, batchTransport, sampleTransport, redactTransport
  • Fan-out via pipe() — dispatch to multiple transports independently, fault-tolerant
  • Structured time() wrapper: emits the label as message with { duration_ms } in context
  • group() and groupCollapsed() wrappers that auto-close on throw/reject
  • LogEntry.data — single merged flat object for transports; no manual merging needed
  • Zero dependencies — 3.5 KB gzipped

Documentation

See Also

  • Courier — HTTP client with built-in request/response interception; pipe Rune as a transport to log every API call with structured context
  • Herald — typed event bus; emit log-level change or flush events across modules without coupling loggers directly
  • Familiar — Web Worker pool; use Rune inside task functions to surface structured worker-side logs back to the main thread