Skip to content
rune logoRuneLogging
Browser/Node logger with levels, namespaces, pluggable transports, lazy bindings, and timing helpers.
Version
v3.0.0
Size
3.6 KB gzip
Dependencies
Zero dependencies
BrowserNode ≥22SSRDeno
createLoggerconsoleTransportjsonTransportremoteTransportbatchTransport 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}`);

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

const api = createLogger({
  namespace: 'api',
  transports: [
    consoleTransport({ level: 'debug' }),
    jsonTransport({ level: 'error' }),
  ],
});

api.info('request', { method: 'GET', path });
FeatureRuneWinstonPinoconsole
Bundle size3.6 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 { consoleTransport, createLogger, jsonTransport, lazy } from '@vielzeug/rune';

const log = createLogger({
  logLevel: 'debug',
  namespace: 'server',
  transports: [
    consoleTransport({ timestamp: true }),
    jsonTransport({ level: 'error' }),
  ],
});

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

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

Features

  • Level filtering (debug to off) with enabled() checks, including fatal above error
  • Immutable config after construction — use child() or withBindings() to scope
  • Message-first, context-first, and Error-first log calls for application code and adapters
  • Error values are 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 })
  • Pluggable transport pipeline: console, JSON, remote, batching, sampling, and fail-closed redaction
  • Immutable middleware transforms and filters applied before every transport
  • 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.6 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