rotating-file-stream
Opens a Node.js stream.Writable to a log file that rotates by size or time interval, with gzip compression and retention limits.
Repository Health
Technical Analysis
rotating-file-stream provides a drop-in stream.Writable for Node.js applications that need log files to rotate automatically. Instead of writing to a single ever-growing file, applications write to a stream returned by createStream(), and the library takes care of splitting output into new files once a size threshold or time interval is reached, compressing old files, and pruning history once configured limits are exceeded.
Rotation naming is fully customizable: callers can pass either a static filename (rotated files get a generated suffix) or a generator function that receives the rotation time and index and returns the exact path to use, which makes it possible to reproduce classic UNIX logrotate numbering (file.log.1, file.log.2, …) or bespoke date-based schemes. Compression can use the bundled gzip pipeline or shell out to an arbitrary external command.
The library exposes an event-driven API (open, rotation, rotated, removed, history, warning, external) so applications can observe the rotation lifecycle rather than polling the filesystem, and ships its own TypeScript types with template-literal types that validate interval (1h, 30m, 1d) and size (10M, 1G) strings at compile time.
What You Get
- A
createStream()factory that returns astream.Writableyou write to exactly likefs.createWriteStream, with rotation handled transparently underneath - Rotation triggered by file size (
size: "10M"), calendar/clock interval (interval: "1d", with optional interval-boundary alignment and UTC handling), or both combined - Built-in gzip compression of rotated files, or a custom
Compressorfunction to shell out to any external compression command - Classic
logrotate-style numbered rotation via therotateoption, as an alternative to timestamped filenames - Automatic history tracking and pruning via
maxFilesandmaxSize, backed by a plain-text history file - An
immutablemode that never overwrites an existing rotated file, instead advancing to the next available index - A typed event interface (
open,rotation,rotated,removed,history,warning,external) for observing the rotation lifecycle - First-class TypeScript types, including template-literal types that validate
intervalandsizeoption strings at compile time
Common Use Cases
- Rotating application or access logs in a long-running Node.js server so a single log file never grows unbounded
- Replacing an external
logrotatecron job with in-process rotation that starts as soon as the app does, including in containers where cron isn’t available - Compressing and retaining only the last N days or last N megabytes of logs to bound disk usage automatically
- Feeding a custom filename generator to integrate with existing log-shipping or archival naming conventions
- Streaming to both stdout (for a process supervisor or container log collector) and a rotating file at once via
teeToStdout
Under The Hood
Architecture
RotatingFileStream extends Node’s stream.Writable as a single class in index.ts (790 lines) with no internal layering: the constructor computes the initial filename via the caller-supplied generator and kicks off an async init() inside an IIFE, and every Writable override (_write, _writev, _final, _destroy) delegates to a shared rewrite/refinal path that awaits an initPromise/timeoutPromise before touching the underlying FileHandle, serializing back-pressure and rotation through those two promise fields rather than a separate scheduler. Rotation branches three ways from rotate() into classical() (numbered logrotate-style naming), immutate() (never-overwrite mode), or move() (default timestamped rename/compress); interval-based rotation is driven by a self-rescheduling setTimeout capped at a maxTimeout constant to work around Node’s 32-bit timer limit. History pruning reads a plain-text history file, appends the new entry, sorts by mtime, and unlinks entries past maxFiles/maxSize, emitting a matching removed event per file. Options validation runs as a separate functional pipeline (checkOpts plus a checks map of per-field validators) before the class is ever instantiated, so createStream() rejects malformed options before any I/O happens; there is no dependency injection, so swapping any filesystem call for testing means monkey-patching the private fsX properties set in the constructor.
Tech Stack
The implementation relies entirely on Node core modules: child_process.exec for external compression commands, zlib.createGzip for the default compression path, stream.Writable as the base class, and fs/fs/promises for FileHandle-based file I/O. Zero runtime dependencies are declared in package.json; the whole library is index.ts (790 lines) plus a 12-line utils.ts build helper. TypeScript 5.9 compiles the source three separate ways (CJS, ESM, and a types-only pass) via a prepare script to ship dual CJS/ESM builds plus .d.ts files, Jest 30 with ts-jest runs the test suite, and ESLint 8 with @typescript-eslint and eslint-plugin-sort-keys enforces style.
Code Quality
The test suite is extensive relative to implementation size: ten spec files under test/ (numbered 01 through 09, plus a 99 teardown) total roughly 1,200 lines against 790 lines of implementation, covering constructor validation, writes, size rotation, error paths, options parsing, interval rotation, compression, classical rotation, and history pruning, run through a custom testSequencer.cjs to control ordering. Error handling is deliberately explicit — nearly every async filesystem call is wrapped in try/catch that re-throws unless the error code is the expected ENOENT, and user-facing option errors throw descriptive Error objects from the validators rather than failing silently. Naming is consistent camelCase throughout, and the codebase leans on TypeScript’s type system (template-literal Interval/FileSize types) for compile-time option validation rather than a runtime schema library; there is no separate typecheck script beyond what tsc already does during prepare.
What Makes It Unique What differentiates this library from a simple size-only rotator is combining interval- and size-based rotation in one option set, with configurable interval-boundary alignment and UTC handling, plus an immutable mode that guarantees a rotated file is never overwritten by scanning forward for a free index, and template-literal TypeScript types that reject malformed interval/size strings at compile time rather than only at runtime. None of these ideas are individually unprecedented — logrotate itself has offered size-and-time rotation for decades — but bundling them behind a plain Writable stream interface with zero runtime dependencies is a distinctive design point relative to comparable rotation packages in the npm ecosystem, most of which either pull in a heavier dependency chain or support only a much simpler size-only rotation model.
Used by 2 apps in this directory
Budibase
Low Code Platforms · No Code Platforms
Build AI agents, automations, and internal apps on a single open-source platform with full self-hosting control.
paseo
AI Agents · AI Code Assistants
One unified interface to orchestrate Claude Code, Codex, Copilot, OpenCode, and Pi agents in parallel from desktop, mobile, or the terminal.