@swarmmachina/swm-logs v0.1.1

swm-logs

Structured JSON logger

A zero-runtime-dependency structured JSON logger for Node.js. Every call writes one NDJSON line to stdout, stderr, a file descriptor, a writable destination, or a lifecycle-aware transport.

Choose swm-logs when you need a stable event format, pino-compatible severities, safe serialization and secret redaction, cheap child loggers, and explicit delivery control without bundled network clients.

ApplicationLoggerNDJSONDestination / transport
0
runtime dependencies
22 / 24
Node.js
NDJSON
output format
ESM
package surface

Installation and quick start

Shellinstall
npm install @swarmmachina/swm-logs
JavaScriptquick-start.js
import Logger from '@swarmmachina/swm-logs'

const logger = new Logger({
  bindings: { service: 'gateway' },
  redact: ['req.headers.authorization']
})

const requestLogger = logger.child({ requestId: 'r1' })

requestLogger.info({ port: 3000 }, 'listening')
requestLogger.error(new Error('request failed'))

await logger.close()
  • By default, the logger writes to stdout immediately and does not create its own queue.
  • Every record contains a numeric level and epoch-millisecond time and ends with exactly one newline.
  • Application fields named level and time are ignored; msg becomes the message only when no explicit message argument is present.
  • Native ESM is the only supported package surface; CommonJS require() is not supported.
  • The package is experimental. Pin the version and review release notes before upgrading.

What the logger provides

Stable NDJSON envelope
Numeric level, epoch-millisecond time, optional msg, pre-serialized bindings, and call fields.
Cheap child loggers
child() serializes bindings once and shares levels, output state, and delivery counters with the root logger.
Finite serialization
Circular references, BigInt, Error.cause, and depth/edge bounds are handled without unbounded traversal.
Compiled redaction
Exact, wildcard, dot, and bracket paths support censoring or removal without mutating input data.
Opt-in extensions
Keyed serializers, before/after hooks, a formatter, transports, and a reversible ConsoleBridge are enabled explicitly.
Explicit delivery
Immediate output by default, bounded opt-in buffering, flushSync for crash paths, and asynchronous close for transport owners.
Observable loss
Destination failures are contained while deliveryStats() and onDestinationError report errors and potentially lost data.

Runtime and delivery targets

EnvironmentStatusNote
Node.js 22 / 24SupportedOther major versions are rejected by the engine constraint.
Native ESMSupportedDefault and named Logger exports with TypeScript declarations.
CommonJS require()UnsupportedUse import from an ESM application.
Runtime dependencies0Network clients, rotation, and vendor exporters are not bundled.
stdout / stderr / fd / writableSupportedSelected through destination; immediate output is enabled by default.
Application transportsSupportedA transport owns its queue, retry, backpressure, persistence, and metrics.
Bundled network transportsNot includedHTTP, database, and vendor delivery stay application-owned.

API map

Log methods

The same call shapes are accepted by built-in and custom levels.

trace()debug()info()warn()error()fatal()log(level, ...args)isLevelEnabled(level)

Logger context

Threshold control, child bindings, and effective context snapshots.

levelchild(bindings, options)bindings()

Delivery lifecycle

Operations on the output state shared by a root logger and its children.

flush()flushSync()close()deliveryStats()

Configuration

Levels, serialization, redaction, extensions, and output ownership.

customLevelsserializersredacthooksformatterbufferingdestinationtransports

Exports

The logger class, levels, console bridge, and TypeScript contracts.

LoggerNamedLoggerConsoleBridgeLEVELSLoggerOptionsLogTransportDeliveryStats

Complete signatures, types, and examples are available in the source repository README and TypeScript declarations.

Delivery, backpressure, and shutdown

The logger separates event formatting from delivery ownership. The application must choose explicit memory bounds, durability, and the point at which the root logger closes.

Immediate mode does not bound the stream queue

When process.stdout.write() returns false, the Node.js stream owns the pending bytes and logging continues. Use a transport with a bounded queue when a strict limit is required.

The transport owns policy

write(line, level) must accept quickly. Queueing, batching, retries, overflow, persistence, timeouts, and asynchronous failure metrics belong to the transport implementation.

Close the root once

Child loggers share the root lifecycle. Stop accepting work, drain in-flight operations, and then call await rootLogger.close().

Keep crash paths synchronous

Call flushSync() for a fatal exception. Guaranteed durability requires a regular file or numeric descriptor; a generic writer must provide its own guarantee.

Buffering has an explicit bound

Buffering defaults to 64 KiB, 1000 ms, and warn as flushLevel. One oversized record can temporarily exceed maxBytes; the buffer resets after a write attempt.

Delivery failures stay contained

Destination and synchronous transport failures do not escape a log method. Export onDestinationError and deliveryStats() through an independent observability path.

Redact before delivery

Prefer exact redact paths for known schemas and use wildcards only for genuinely dynamic structure. Caller-owned data is not mutated.

Migrating from pino

Numeric severities are pino-compatible, but the surface is intentionally smaller: base becomes bindings, custom levels use log(), and workers, pretty printing, and vendor transports are not bundled.

JavaScriptlogger.js
// pino
const previous = pino({ level: 'info', base: { service: 'gateway' } })

// swm-logs
const logger = new Logger({
  level: 'info',
  bindings: { service: 'gateway' }
})
Need an HTTP/WebSocket server with explicit limits and graceful shutdown?Open swm-core