Skip to content

API Overview

SymbolPurposeExecution modeCommon gotcha
createLogger()Create an isolated Logger instanceSyncOmitting transports defaults to consoleTransport()
RunePre-created default logger singletonShared instance — use child() or withBindings() to scope
lazy(fn)Defer a binding value past the level checkSyncFactory runs on every emit, not once
pipe()Fan-out dispatcher to multiple transportsSyncErrors in one transport don't propagate to others
isLevelEnabled()Utility: test whether a level passes a thresholdSync'off' always returns false
resolveTheme()Merge a partial theme onto the defaultSyncReturns a fully-populated ResolvedTheme
consoleTransport()Styled console outputSyncTheme is resolved once at factory call, not per entry
remoteTransport()Async HTTP/webhook deliveryAsyncHandler errors are swallowed to console.warn
jsonTransport()NDJSON to stdout or a custom sinkSyncprocess.stdout is unavailable in browsers
batchTransport()Buffered batch delivery with flush intervalSync/IntervalMust call .dispose() on shutdown to flush remaining
sampleTransport()Probabilistic entry forwardingSyncrate: 1 forwards all entries; rate: 0 forwards none
redactTransport()Sensitive field stripping before forwardingSyncPlace this closest to the remote transport, not console

Package Entry Point

ImportPurpose
@vielzeug/runeAll exports — logger, transport factories, lazy, types

createLogger(initial?, options?)

Creates an isolated logger instance.

ts
createLogger(namespace: string, options?: Omit<RuneOptions, 'namespace'>): Logger
createLogger(options?: RuneOptions): Logger
  • string shorthand sets namespace: createLogger('api') or createLogger('api', { logLevel: 'warn' }).
  • Each call produces a fully independent instance — no shared mutable state.
  • Default transport is consoleTransport() when transports is omitted.

Note — disposed loggers: after dispose() is called, all log methods (debug, info, warn, error, fatal), time(), and group() / groupCollapsed() silently no-op. The fn callback in group() still runs — only the group header is suppressed.

Returns: Logger

Example:

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

const log = createLogger({ logLevel: 'warn', namespace: 'app' });

const serverLog = createLogger({
  namespace: 'server',
  transports: [
    consoleTransport(),
    remoteTransport({
      handler: async (type, data) => {
        await fetch('/api/logs', { body: JSON.stringify(data), method: 'POST' });
      },
      level: 'error',
    }),
  ],
});

Rune

Rune is the pre-created default logger (createLogger() called once at module load).

Use it as a quick-start singleton or create a child for module-level use:

ts
import { Rune } from '@vielzeug/rune';

const log = Rune.child({ namespace: 'app.worker' });

lazy(fn)

Defers evaluation of an expensive binding value until after the level check passes. The factory function is never called when the log level suppresses the entry.

ts
lazy(fn: () => unknown): LazyBinding
ts
import { lazy } from '@vielzeug/rune';

const reqLog = log.withBindings({
  diagnostics: lazy(() => buildExpensiveDiagnostics()),
});

reqLog.debug('trace'); // diagnostics() only called when debug is enabled

Returns: LazyBinding

Logger Methods

Logging

All five methods share the same signature:

ts
log.debug / info / warn / error / fatal(message: string): void
log.debug / info / warn / error / fatal(error: Error, context?: Bindings, message?: string): void
log.debug / info / warn / error / fatal(context: Bindings, message?: string): void

Argument rules:

  • String-only calls accept a single message argument.
  • Error-first form: pass an Error as the first argument — it is auto-serialized to { message, name, stack } under the err key. Optionally follow with a Bindings object and/or a message string.
  • Context object comes first when providing structured data without a top-level Error. Error values inside the context object are also auto-serialized to { message, name, stack }.
ts
log.error(err, 'request failed'); // err auto-serialized to data.err
log.error(err, { requestId }, 'request failed'); // err + context + message
log.error({ err: new Error('boom') }, 'failed'); // Error nested in context object

Composition

MethodReturnsWhat it does
child(overrides?)LoggerClones config, applies overrides, inherits bindings
withBindings(fields)LoggerPins fields to every subsequent call, returns a new child logger
use(middleware)LoggerAppends a middleware function to the pipeline, returns new logger

child() transport inheritance:

  • Omit transports → inherit parent transports (default).
  • Pass transports: [] → disable all transports on the child.
  • Pass transports: [...] → replace entirely with the given list.

child() namespace joining:

  • parent.child({ namespace: 'auth' }) on a logger with namespace 'api' produces 'api.auth'.
  • Omit namespace → inherits parent namespace unchanged.

Utilities

MethodReturnsDescription
enabled(level)booleanTrue if entries at this level pass the configured threshold
time(label, fn, level?)TMeasures sync/async execution; emits at level (default 'debug'), label as message, { duration_ms } in data. When fn throws or rejects, { err } is also included.
group(label, fn, level?)TWraps callback in console.group; closes even on throw/reject. Pass level to gate the group header on the configured threshold (e.g. 'debug' suppresses when logLevel is 'warn').
groupCollapsed(label, fn, level?)TSame as group, using console.groupCollapsed.
dispose()voidSilences all subsequent log calls on this logger instance. Does not auto-dispose batch transports — hold a reference and call batchTransport.dispose() on shutdown. Idempotent.

Properties

PropertyTypeDescription
logLevelLogLevelActive log level threshold
namespacestringEffective namespace string
middlewarereadonly LogMiddleware[]Middleware pipeline snapshot
transportsreadonly Transport[]Transport pipeline snapshot
bindingsReadonly<Bindings>Snapshot of currently pinned fields
disposalSignalAbortSignalAborted when dispose() is called. Use to tie external lifetimes.
disposedbooleantrue after dispose() has been called
[Symbol.dispose]() => voidDelegates to dispose(). Enables using declarations.

Transport Factories

consoleTransport(options?)

ts
consoleTransport(options?: ConsoleTransportOptions): Transport

Writes styled output to the browser console (CSS badges) or Node terminal (plain text). This is the default transport.

OptionTypeDefaultDescription
levelLogLevel'debug'Minimum level to output
timestampbooleantrueInclude HH:MM:SS.mmm
ansibooleanautoForce ANSI color codes on/off (Node only)
format'json' | 'raw''raw'Context serialization: 'json' uses JSON.stringify
inspectFn(v: unknown) => stringCustom object formatter (e.g. util.inspect)
themeConsoleThemeOverride default badge colours for this transport

Returns: Transport

Example:

ts
import { consoleTransport, createLogger } from '@vielzeug/rune';
import { inspect } from 'node:util';

const log = createLogger({
  transports: [consoleTransport({ level: 'info', timestamp: true, inspectFn: inspect })],
});

remoteTransport(options)

ts
remoteTransport(options: RemoteTransportOptions): Transport

Forwards entries asynchronously to a remote handler. Fire-and-forget — handler errors are swallowed to console.warn and never propagate to the caller.

OptionTypeDefaultDescription
handlerRemoteHandlerRequired. Receives each forwarded entry
levelLogLevel'debug'Minimum level to forward
env'production' | 'development'auto-detectedOverride the runtime environment marker
onError(error: unknown) => voidCalled when the handler throws or rejects. Default: a dev-only console.warn. Silent in production — provide an explicit handler for production observability.

Returns: Transport

Example:

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

const log = createLogger({
  transports: [
    remoteTransport({
      handler: async (type, data) => {
        await fetch('/api/logs', { body: JSON.stringify(data), method: 'POST' });
      },
      level: 'error',
    }),
  ],
});

jsonTransport(options?)

ts
jsonTransport(options?: JsonTransportOptions): Transport

Outputs newline-delimited JSON (NDJSON) to stdout or a custom function. Useful for server-side log aggregation pipelines (ELK, Datadog, etc.).

Each line is a flat JSON object with level, time (ISO), and optional ns, msg, plus all merged context fields.

OptionTypeDefaultDescription
levelLogLevel'debug'Minimum level
output(line: string) => voidprocess.stdoutCustom output sink
safebooleanfalseReplace circular references with '[Circular]' instead of throwing
fields{ level?, msg?, ns?, time? }Custom output field names for aggregator compatibility (e.g. 'severity' for Datadog)

Returns: Transport

Example:

ts
import { createLogger, jsonTransport } from '@vielzeug/rune';

const log = createLogger({
  namespace: 'api',
  transports: [jsonTransport({ level: 'info' })],
});

log.info({ path: '/users', status: 200 }, 'request');
// {"path":"/users","status":200,"level":"info","time":"2026-05-30T...","ns":"api","msg":"request"}

batchTransport(options)

ts
batchTransport(options: BatchTransportOptions): BatchHandle

Buffers entries and delivers them in batches. Flushes when the buffer reaches maxSize or after interval elapses.

OptionTypeDefaultDescription
onFlush(entries: LogEntry[]) => void | Promise<void>Required. Receives each batch (may be async)
onFlushError(entries: LogEntry[], error: unknown) => voidCalled when onFlush throws or rejects
levelLogLevel'debug'Minimum level to buffer
intervalnumber5000Flush interval in milliseconds
maxSizenumber50Max buffer size before an early flush
maxBuffernumberunboundedHard cap — oldest entries are dropped silently when exceeded. Does not trigger a flush.

Returns a BatchHandle with:

  • .transport — the Transport function to pass to createLogger({ transports: [handle.transport] }).
  • .flush() — immediately send buffered entries without stopping the timer.
  • .dispose() — stop the interval and flush remaining entries. Call on shutdown. Idempotent.
  • [Symbol.dispose]() — delegates to .dispose(). Enables using declarations.

After dispose(), the transport becomes inert: new entries are silently dropped.

Returns: BatchHandle

Example:

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

const batch = batchTransport({
  onFlush: (entries) => sendToCollector(entries),
  interval: 10_000,
  maxSize: 100,
});

// Pass batch.transport to the logger — batch holds flush/dispose
const log = createLogger({ transports: [batch.transport] });

process.on('exit', () => batch.dispose());

sampleTransport(options)

ts
sampleTransport(options: SampleTransportOptions): Transport

Probabilistically forwards entries to a downstream transport.

OptionTypeDefaultDescription
ratenumberRequired. Fraction of entries to forward (0–1)
transportTransportRequired. Downstream transport
levelLogLevel'debug'Minimum level to sample

Returns: Transport

Example:

ts
import { createLogger, remoteTransport, sampleTransport } from '@vielzeug/rune';

const log = createLogger({
  transports: [
    sampleTransport({
      rate: 0.1,
      transport: remoteTransport({ handler }),
    }),
  ],
});

redactTransport(options)

ts
redactTransport(options: RedactTransportOptions): Transport

Strips sensitive fields from bindings and context before forwarding. Redaction is applied recursively at any depth (up to 20 levels).

Key matching

keys matches exact field names at any nesting depth. Dot-path notation (e.g. 'user.password') is not supported — use 'password' to redact every field named password regardless of nesting.

OptionTypeDefaultDescription
keysstring[]Required. Field names to redact
replacementstring'[REDACTED]'Replacement value
transportTransportRequired. Downstream transport

Returns: Transport

Example:

ts
import { createLogger, redactTransport, remoteTransport } from '@vielzeug/rune';

const log = createLogger({
  transports: [
    redactTransport({
      keys: ['password', 'token', 'ssn'],
      transport: remoteTransport({ handler }),
    }),
  ],
});

pipe(...transports) / pipe(options, ...transports)

ts
pipe(...transports: Transport[]): Transport
pipe(options: PipeOptions, ...transports: Transport[]): Transport

Dispatches each LogEntry to every transport in the list independently. An error thrown by one transport does not stop the others. Use in place of separate array entries when you want fault isolation or a shared error observer.

pipe() with no arguments creates a valid no-op transport — useful for conditional pipeline construction: pipe(condition ? remoteTransport(opts) : undefined!) pattern, or simply as a placeholder during development.

OptionTypeDescription
onError(error: unknown, entry: LogEntry) => voidCalled with the error and entry when any transport throws

Returns: Transport

Example:

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

const log = createLogger({
  transports: [
    pipe(
      { onError: (err) => metrics.increment('log.transport.error') },
      consoleTransport(),
      remoteTransport({ handler, level: 'error' }),
    ),
  ],
});

## Utilities

### isLevelEnabled(threshold, level)

```ts
isLevelEnabled(threshold: LogLevel, level: LogLevel): boolean

Returns true when level is at or above threshold. Always returns false when level is 'off'. Useful for building custom transports that respect level filtering.

ts
import { isLevelEnabled } from '@vielzeug/rune';

isLevelEnabled('warn', 'error'); // true
isLevelEnabled('warn', 'info'); // false
isLevelEnabled('debug', 'off'); // false

resolveTheme(override?)

ts
resolveTheme(override: ConsoleTheme | undefined): ResolvedTheme

Deep-merges a partial ConsoleTheme override onto DEFAULT_THEME. Returns a fully-populated ResolvedTheme where every level and every field is present. Used internally by consoleTransport() — call directly when building a custom transport that needs to honour theme overrides.

ts
import { resolveTheme } from '@vielzeug/rune';

const theme = resolveTheme({ warn: { badge: '⚡' } });
// theme.warn.badge === '⚡', theme.warn.bg === DEFAULT_THEME.warn.bg (unchanged)

DEFAULT_THEME

The built-in badge and namespace colour definitions used by consoleTransport(). Override per-transport via ConsoleTransportOptions.theme.

Types

LogType

'debug' | 'error' | 'fatal' | 'info' | 'warn'

LogLevel

LogType | 'off' — threshold order: debug < info < warn < error < fatal < off

Bindings

Record<string, unknown> — Key-value context pinned via withBindings() or passed per-call.

LogEntry

The structured record produced by every log call and dispatched to all transports.

FieldTypeDescription
dataReadonly<Bindings>Merged result of pinned bindings and per-call context — already resolved
levelLogTypeLog level
messagestring?Log message
namespacestringEffective namespace at time of call
timestampDateExact moment of the call, shared across transports

Transport

ts
type Transport = (entry: LogEntry) => void;

Receives every LogEntry that passes the logger's level threshold. Responsible for its own formatting, delivery, and per-transport level filtering.

RemoteLogData

Payload shape delivered to RemoteHandler:

FieldTypeDescription
dataBindings?Merged structured data (omitted if empty)
env'production' | 'development'Runtime env marker
levelLogTypeLog level
messagestring?Log message
namespacestring?Effective namespace
timestampstringFull ISO timestamp

RemoteHandler

(type: LogType, data: RemoteLogData) => void

PipeOptions

FieldTypeDescription
onError(error: unknown, entry: LogEntry) => voidCalled when a transport in the pipe throws or rejects

ResolvedTheme

Record<LogType | 'group' | 'ns', ConsoleThemeEntry> — fully resolved theme with all fields populated.

RuneOptions

FieldTypeDefaultDescription
logLevelLogLevel?'debug'Logger level threshold
namespacestring?''Namespace prefix
transportsTransport[]?[consoleTransport()]Transport pipeline
bindingsBindings?{}Initial pinned bindings
middlewareLogMiddleware[]?[]Entry transform/filter chain

LogMethod

ts
type LogMethod = {
  (message: string): void;
  (error: Error, context?: Bindings, message?: string): void;
  (context: Bindings, message?: string): void;
};

Every log-level method uses this signature. Three call forms are supported:

  • String-only: log.info('message')
  • Error-first: log.error(err, { requestId }, 'failed')Error is auto-serialized to { message, name, stack } under data.err. Optionally follow with a Bindings object and/or a message string.
  • Context-first: log.info({ key: 'value' }, 'message') — structured context object, optional message. Error values nested inside the context are also auto-serialized.

LogMiddleware

ts
type LogMiddleware = (entry: LogEntry) => LogEntry | null;

Middleware functions intercept entries before they reach transports. Return the (optionally mutated) entry to continue, or return null to drop the entry. Added via use(fn) or RuneOptions.middleware.

LazyBinding

Opaque type returned by lazy(). Pass as a value inside withBindings(). The factory is only called when the entry is actually emitted (after the level check passes).

BatchHandle

ts
type BatchHandle = {
  [Symbol.dispose]: () => void;
  dispose: () => void;
  flush: () => void;
  transport: Transport;
};

Returned by batchTransport(). Pass handle.transport to createLogger({ transports }); call handle.dispose() on shutdown.

Logger

The full interface returned by createLogger() and Rune:

ts
type Logger = {
  [Symbol.dispose]: () => void;
  readonly bindings: Readonly<Bindings>;
  child: (overrides?: RuneOptions) => Logger;
  debug: LogMethod;
  readonly disposalSignal: AbortSignal;
  dispose: () => void;
  readonly disposed: boolean;
  enabled: (type: LogLevel) => boolean;
  error: LogMethod;
  fatal: LogMethod;
  group: <T>(label: string, fn: () => T, level?: LogType) => T;
  groupCollapsed: <T>(label: string, fn: () => T, level?: LogType) => T;
  info: LogMethod;
  readonly logLevel: LogLevel;
  readonly middleware: readonly LogMiddleware[];
  readonly namespace: string;
  time: <T>(label: string, fn: () => T, level?: LogType) => T;
  readonly transports: readonly Transport[];
  use: (middleware: LogMiddleware) => Logger;
  warn: LogMethod;
  /** Returns a new child logger with additional pinned bindings. The returned logger is fully independent — disposing it does not affect the parent, and vice versa. */
  withBindings: (bindings: Bindings) => Logger;
};

ConsoleTransportOptions

FieldTypeDefaultDescription
levelLogLevel'debug'Minimum level to output
timestampbooleantrueInclude HH:MM:SS.mmm
ansibooleanautoForce ANSI color codes on/off (Node only)
format'json' | 'raw''raw'Context serialization: 'json' uses JSON.stringify
inspectFn(v: unknown) => stringCustom object formatter (e.g. util.inspect)
themeConsoleThemeOverride default badge colours for this transport

RemoteTransportOptions

FieldTypeDefaultDescription
handlerRemoteHandlerRequired. Receives each forwarded entry
levelLogLevel'debug'Minimum level to forward
env'production' | 'development'auto-detectedOverride the runtime environment marker
onError(error: unknown) => voidCalled when the handler throws

JsonTransportOptions

FieldTypeDefaultDescription
levelLogLevel'debug'Minimum level
output(line: string) => voidprocess.stdoutCustom output sink
safebooleanfalseReplace circular references with '[Circular]' instead of throwing
fields{ level?, msg?, ns?, time? }Custom output field names (e.g. level: 'severity' for Datadog)

BatchTransportOptions

FieldTypeDefaultDescription
onFlush(entries: LogEntry[]) => void | Promise<void>Required. Receives each batch (may be async)
onFlushError(entries: LogEntry[], error: unknown) => voidCalled when onFlush throws or rejects
levelLogLevel'debug'Minimum level to buffer
intervalnumber5000Flush interval in milliseconds
maxSizenumber50Max buffer size before an early flush
maxBuffernumberunboundedHard cap — drops oldest when exceeded, no flush triggered

SampleTransportOptions

FieldTypeDefaultDescription
ratenumberRequired. Fraction of entries to forward (0–1)
transportTransportRequired. Downstream transport
levelLogLevel'debug'Minimum level to sample

RedactTransportOptions

FieldTypeDefaultDescription
keysstring[]Required. Field names to redact at any depth
maxDepthnumber20Maximum object nesting depth to traverse. Fields deeper than this are not redacted — a dev-only warning is emitted when hit. Security: the warning is suppressed in production; ensure sensitive fields are not nested beyond this limit.
replacementstring'[REDACTED]'Replacement value
transportTransportRequired. Downstream transport