Skip to content

API Overview

SymbolPurposeExecution modeCommon gotcha
createLogger()Create an isolated Logger instanceSyncOmitting transports defaults to consoleTransport()
defaultLoggerPre-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
PRIORITYNumeric priority table backing isLevelEnabled()Lower number = more verbose
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 intervalAsyncAwait .dispose() and handle rejected delivery
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.

Note — transport/middleware fault isolation: if a transport or middleware function throws, the logger catches it, reports it via a dev-only warning, and continues — a single misbehaving transport can never crash the caller of log.info()/etc., and sibling transports still receive the entry. A throwing middleware drops just that one entry.

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',
    }),
  ],
});

defaultLogger

defaultLogger 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 { defaultLogger } from '@vielzeug/rune';

const log = defaultLogger.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
handler(type: LogType, data: RemoteLogData) => voidRequired. Receives each forwarded entry
levelLogLevel'debug'Minimum level to forward
env'production' | 'development'auto-detectedOverride the runtime environment marker
onError(error: unknown, data: RemoteLogData) => 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 order. Flushes when the buffer reaches maxSize or after interval elapses; flush() and dispose() wait for accepted batch delivery.

OptionTypeDefaultDescription
onFlush(entries: LogEntry[]) => void | Promise<void>Required. Receives each batch; implement retry here when successful retry must fulfill drain
onFlushError(entries: LogEntry[], error: unknown) => voidObserves delivery failure; matching flush() or later dispose() rejects
levelLogLevel'debug'Minimum level to buffer
intervalnumber5000Finite interval in milliseconds greater than zero
maxSizenumber50Finite positive integer batch size before early flush
maxBuffernumberunboundedFinite non-negative integer hard cap; oldest entries drop when exceeded

Returns a BatchHandle with:

  • .transport — the Transport function to pass to createLogger({ transports: [handle.transport] }).
  • .flush() — immediately send buffered entries and resolve after delivery; rejects when delivery fails.
  • .dispose() — stop the interval, reject new entries, and settle after every accepted batch completes. Rejects if any automatic or final delivery fails. Idempotent.
  • .disposedtrue when disposal starts.
  • [Symbol.asyncDispose]() — delegates to .dispose(). Enables await 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({
  interval: 10_000,
  maxSize: 100,
  onFlush: (entries) => console.debug('batch', entries),
});

const log = createLogger({ transports: [batch.transport] });

async function shutdown() {
  await batch.dispose();
}

sampleTransport(options)

ts
sampleTransport(options: SampleTransportOptions): Transport

Probabilistically forwards entries to a downstream transport.

OptionTypeDefaultDescription
ratenumberRequired finite 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: (_type, data) => console.debug('sampled log', data) }),
    }),
  ],
});

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: (_type, data) => console.debug('redacted log', data) }),
    }),
  ],
});

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: (error) => console.warn('transport error', error) },
      consoleTransport(),
      remoteTransport({
        handler: (_type, data) => console.debug('remote log', data),
        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.

PRIORITY

ts
PRIORITY: Record<LogLevel, number>

Numeric priority for each level (debug: 0, info: 1, warn: 2, error: 3, fatal: 4, off: 5) — lower is more verbose. Exported for transport/middleware authors building custom level-comparison logic; isLevelEnabled() is built directly on top of it.

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 RemoteTransportOptions.handler:

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

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.asyncDispose]: () => Promise<void>;
  dispose: () => Promise<void>;
  readonly disposed: boolean;
  flush: () => Promise<void>;
  transport: Transport;
};

Returned by batchTransport(). Pass handle.transport to createLogger({ transports }); await handle.dispose() during graceful shutdown. disposed is true when disposal starts.

Logger

The full interface returned by createLogger() and defaultLogger:

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
handler(type: LogType, data: RemoteLogData) => voidRequired. Receives each forwarded entry
levelLogLevel'debug'Minimum level to forward
env'production' | 'development'auto-detectedOverride the runtime environment marker
onError(error: unknown, data: RemoteLogData) => 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) => voidObserves delivery failure; matching flush() or later dispose() rejects
levelLogLevel'debug'Minimum level to buffer
intervalnumber5000Finite interval in milliseconds greater than zero
maxSizenumber50Finite positive integer batch size before early flush
maxBuffernumberunboundedFinite non-negative integer cap; drops oldest entries when exceeded

SampleTransportOptions

FieldTypeDefaultDescription
ratenumberRequired finite 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
maxDepthnumber20Finite non-negative integer nesting depth. 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