API Overview
| Symbol | Purpose | Execution mode | Common gotcha |
|---|---|---|---|
createLogger() | Create an isolated Logger instance | Sync | Omitting transports defaults to consoleTransport() |
defaultLogger | Pre-created default logger singleton | — | Shared instance — use child() or withBindings() to scope |
lazy(fn) | Defer a binding value past the level check | Sync | Factory runs on every emit, not once |
pipe() | Fan-out dispatcher to multiple transports | Sync | Errors in one transport don't propagate to others |
isLevelEnabled() | Utility: test whether a level passes a threshold | Sync | 'off' always returns false |
PRIORITY | Numeric priority table backing isLevelEnabled() | — | Lower number = more verbose |
resolveTheme() | Merge a partial theme onto the default | Sync | Returns a fully-populated ResolvedTheme |
consoleTransport() | Styled console output | Sync | Theme is resolved once at factory call, not per entry |
remoteTransport() | Async HTTP/webhook delivery | Async | Handler errors are swallowed to console.warn |
jsonTransport() | NDJSON to stdout or a custom sink | Sync | process.stdout is unavailable in browsers |
batchTransport() | Buffered batch delivery with flush interval | Async | Await .dispose() and handle rejected delivery |
sampleTransport() | Probabilistic entry forwarding | Sync | rate: 1 forwards all entries; rate: 0 forwards none |
redactTransport() | Sensitive field stripping before forwarding | Sync | Place this closest to the remote transport, not console |
Package Entry Point
| Import | Purpose |
|---|---|
@vielzeug/rune | All exports — logger, transport factories, lazy, types |
createLogger(initial?, options?)
Creates an isolated logger instance.
createLogger(namespace: string, options?: Omit<RuneOptions, 'namespace'>): Logger
createLogger(options?: RuneOptions): Loggerstringshorthand sets namespace:createLogger('api')orcreateLogger('api', { logLevel: 'warn' }).- Each call produces a fully independent instance — no shared mutable state.
- Default transport is
consoleTransport()whentransportsis omitted.
Note — disposed loggers: after
dispose()is called, all log methods (debug,info,warn,error,fatal),time(), andgroup()/groupCollapsed()silently no-op. Thefncallback ingroup()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:
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:
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.
lazy(fn: () => unknown): LazyBindingimport { lazy } from '@vielzeug/rune';
const reqLog = log.withBindings({
diagnostics: lazy(() => buildExpensiveDiagnostics()),
});
reqLog.debug('trace'); // diagnostics() only called when debug is enabledReturns: LazyBinding
Logger Methods
Logging
All five methods share the same signature:
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): voidArgument rules:
- String-only calls accept a single message argument.
- Error-first form: pass an
Erroras the first argument — it is auto-serialized to{ message, name, stack }under theerrkey. Optionally follow with aBindingsobject and/or a message string. - Context object comes first when providing structured data without a top-level Error.
Errorvalues inside the context object are also auto-serialized to{ message, name, stack }.
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 objectComposition
| Method | Returns | What it does |
|---|---|---|
child(overrides?) | Logger | Clones config, applies overrides, inherits bindings |
withBindings(fields) | Logger | Pins fields to every subsequent call, returns a new child logger |
use(middleware) | Logger | Appends 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
| Method | Returns | Description |
|---|---|---|
enabled(level) | boolean | True if entries at this level pass the configured threshold |
time(label, fn, level?) | T | Measures 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?) | T | Wraps 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?) | T | Same as group, using console.groupCollapsed. |
dispose() | void | Silences 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
| Property | Type | Description |
|---|---|---|
logLevel | LogLevel | Active log level threshold |
namespace | string | Effective namespace string |
middleware | readonly LogMiddleware[] | Middleware pipeline snapshot |
transports | readonly Transport[] | Transport pipeline snapshot |
bindings | Readonly<Bindings> | Snapshot of currently pinned fields |
disposalSignal | AbortSignal | Aborted when dispose() is called. Use to tie external lifetimes. |
disposed | boolean | true after dispose() has been called |
[Symbol.dispose] | () => void | Delegates to dispose(). Enables using declarations. |
Transport Factories
consoleTransport(options?)
consoleTransport(options?: ConsoleTransportOptions): TransportWrites styled output to the browser console (CSS badges) or Node terminal (plain text). This is the default transport.
| Option | Type | Default | Description |
|---|---|---|---|
level | LogLevel | 'debug' | Minimum level to output |
timestamp | boolean | true | Include HH:MM:SS.mmm |
ansi | boolean | auto | Force ANSI color codes on/off (Node only) |
format | 'json' | 'raw' | 'raw' | Context serialization: 'json' uses JSON.stringify |
inspectFn | (v: unknown) => string | — | Custom object formatter (e.g. util.inspect) |
theme | ConsoleTheme | — | Override default badge colours for this transport |
Returns: Transport
Example:
import { consoleTransport, createLogger } from '@vielzeug/rune';
import { inspect } from 'node:util';
const log = createLogger({
transports: [consoleTransport({ level: 'info', timestamp: true, inspectFn: inspect })],
});remoteTransport(options)
remoteTransport(options: RemoteTransportOptions): TransportForwards entries asynchronously to a remote handler. Fire-and-forget — handler errors are swallowed to console.warn and never propagate to the caller.
| Option | Type | Default | Description |
|---|---|---|---|
handler | (type: LogType, data: RemoteLogData) => void | — | Required. Receives each forwarded entry |
level | LogLevel | 'debug' | Minimum level to forward |
env | 'production' | 'development' | auto-detected | Override the runtime environment marker |
onError | (error: unknown, data: RemoteLogData) => void | — | Called 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:
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?)
jsonTransport(options?: JsonTransportOptions): TransportOutputs 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.
| Option | Type | Default | Description |
|---|---|---|---|
level | LogLevel | 'debug' | Minimum level |
output | (line: string) => void | process.stdout | Custom output sink |
safe | boolean | false | Replace 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:
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)
batchTransport(options: BatchTransportOptions): BatchHandleBuffers entries and delivers them in order. Flushes when the buffer reaches maxSize or after interval elapses; flush() and dispose() wait for accepted batch delivery.
| Option | Type | Default | Description |
|---|---|---|---|
onFlush | (entries: LogEntry[]) => void | Promise<void> | — | Required. Receives each batch; implement retry here when successful retry must fulfill drain |
onFlushError | (entries: LogEntry[], error: unknown) => void | — | Observes delivery failure; matching flush() or later dispose() rejects |
level | LogLevel | 'debug' | Minimum level to buffer |
interval | number | 5000 | Finite interval in milliseconds greater than zero |
maxSize | number | 50 | Finite positive integer batch size before early flush |
maxBuffer | number | unbounded | Finite non-negative integer hard cap; oldest entries drop when exceeded |
Returns a BatchHandle with:
.transport— theTransportfunction to pass tocreateLogger({ 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..disposed—truewhen disposal starts.[Symbol.asyncDispose]()— delegates to.dispose(). Enablesawait usingdeclarations.
After dispose(), the transport becomes inert: new entries are silently dropped.
Returns: BatchHandle
Example:
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)
sampleTransport(options: SampleTransportOptions): TransportProbabilistically forwards entries to a downstream transport.
| Option | Type | Default | Description |
|---|---|---|---|
rate | number | — | Required finite fraction of entries to forward (0–1) |
transport | Transport | — | Required. Downstream transport |
level | LogLevel | 'debug' | Minimum level to sample |
Returns: Transport
Example:
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)
redactTransport(options: RedactTransportOptions): TransportStrips 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.
| Option | Type | Default | Description |
|---|---|---|---|
keys | string[] | — | Required. Field names to redact |
replacement | string | '[REDACTED]' | Replacement value |
transport | Transport | — | Required. Downstream transport |
Returns: Transport
Example:
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)
pipe(...transports: Transport[]): Transport
pipe(options: PipeOptions, ...transports: Transport[]): TransportDispatches 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.
| Option | Type | Description |
|---|---|---|
onError | (error: unknown, entry: LogEntry) => void | Called with the error and entry when any transport throws |
Returns: Transport
Example:
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): booleanReturns true when level is at or above threshold. Always returns false when level is 'off'. Useful for building custom transports that respect level filtering.
import { isLevelEnabled } from '@vielzeug/rune';
isLevelEnabled('warn', 'error'); // true
isLevelEnabled('warn', 'info'); // false
isLevelEnabled('debug', 'off'); // falseresolveTheme(override?)
resolveTheme(override: ConsoleTheme | undefined): ResolvedThemeDeep-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.
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
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.
| Field | Type | Description |
|---|---|---|
data | Readonly<Bindings> | Merged result of pinned bindings and per-call context — already resolved |
level | LogType | Log level |
message | string? | Log message |
namespace | string | Effective namespace at time of call |
timestamp | Date | Exact moment of the call, shared across transports |
Transport
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:
| Field | Type | Description |
|---|---|---|
data | Bindings? | Merged structured data (omitted if empty) |
env | 'production' | 'development' | Runtime env marker |
level | LogType | Log level |
message | string? | Log message |
namespace | string? | Effective namespace |
timestamp | string | Full ISO timestamp |
PipeOptions
| Field | Type | Description |
|---|---|---|
onError | (error: unknown, entry: LogEntry) => void | Called when a transport in the pipe throws or rejects |
ResolvedTheme
Record<LogType | 'group' | 'ns', ConsoleThemeEntry> — fully resolved theme with all fields populated.
RuneOptions
| Field | Type | Default | Description |
|---|---|---|---|
logLevel | LogLevel? | 'debug' | Logger level threshold |
namespace | string? | '' | Namespace prefix |
transports | Transport[]? | [consoleTransport()] | Transport pipeline |
bindings | Bindings? | {} | Initial pinned bindings |
middleware | LogMiddleware[]? | [] | Entry transform/filter chain |
LogMethod
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')—Erroris auto-serialized to{ message, name, stack }underdata.err. Optionally follow with aBindingsobject and/or a message string. - Context-first:
log.info({ key: 'value' }, 'message')— structured context object, optional message.Errorvalues nested inside the context are also auto-serialized.
LogMiddleware
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
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:
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
| Field | Type | Default | Description |
|---|---|---|---|
level | LogLevel | 'debug' | Minimum level to output |
timestamp | boolean | true | Include HH:MM:SS.mmm |
ansi | boolean | auto | Force ANSI color codes on/off (Node only) |
format | 'json' | 'raw' | 'raw' | Context serialization: 'json' uses JSON.stringify |
inspectFn | (v: unknown) => string | — | Custom object formatter (e.g. util.inspect) |
theme | ConsoleTheme | — | Override default badge colours for this transport |
RemoteTransportOptions
| Field | Type | Default | Description |
|---|---|---|---|
handler | (type: LogType, data: RemoteLogData) => void | — | Required. Receives each forwarded entry |
level | LogLevel | 'debug' | Minimum level to forward |
env | 'production' | 'development' | auto-detected | Override the runtime environment marker |
onError | (error: unknown, data: RemoteLogData) => void | — | Called when the handler throws |
JsonTransportOptions
| Field | Type | Default | Description |
|---|---|---|---|
level | LogLevel | 'debug' | Minimum level |
output | (line: string) => void | process.stdout | Custom output sink |
safe | boolean | false | Replace circular references with '[Circular]' instead of throwing |
fields | { level?, msg?, ns?, time? } | — | Custom output field names (e.g. level: 'severity' for Datadog) |
BatchTransportOptions
| Field | Type | Default | Description |
|---|---|---|---|
onFlush | (entries: LogEntry[]) => void | Promise<void> | — | Required. Receives each batch (may be async) |
onFlushError | (entries: LogEntry[], error: unknown) => void | — | Observes delivery failure; matching flush() or later dispose() rejects |
level | LogLevel | 'debug' | Minimum level to buffer |
interval | number | 5000 | Finite interval in milliseconds greater than zero |
maxSize | number | 50 | Finite positive integer batch size before early flush |
maxBuffer | number | unbounded | Finite non-negative integer cap; drops oldest entries when exceeded |
SampleTransportOptions
| Field | Type | Default | Description |
|---|---|---|---|
rate | number | — | Required finite fraction of entries to forward (0–1) |
transport | Transport | — | Required. Downstream transport |
level | LogLevel | 'debug' | Minimum level to sample |
RedactTransportOptions
| Field | Type | Default | Description |
|---|---|---|---|
keys | string[] | — | Required. Field names to redact at any depth |
maxDepth | number | 20 | Finite 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. |
replacement | string | '[REDACTED]' | Replacement value |
transport | Transport | — | Required. Downstream transport |