npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pawells/logger-transport-file

v4.1.0

Published

File transport for @pawells/logger — persists structured log entries to rolling log files

Readme

@pawells/logger-transport-file

CI npm version Node License: MIT

File transport plugin for @pawells/logger. Writes structured log entries to disk with automatic size-based and daily rotation, configurable archive retention, and an injectable fs module for deterministic unit testing.


Description

@pawells/logger-transport-file extends the @pawells/logger event bus with a file-backed transport. It appends formatted log entries to a specified file, rotates the file when it exceeds a configurable byte threshold or on a daily schedule, retains a bounded number of archives, and optionally enforces a total disk budget for archived files.

Key characteristics:

  • Auto-registers on construction — unlike other built-in transports, FileTransport calls this.Register(...) inside its constructor. No separate Register() call is required or should be made.
  • Async initialization — directory creation and file-open happen in a background promise (initPromise). Construction is always synchronous and never throws from I/O.
  • Dependency-injectable fs — pass a mock FsModuleType to the constructor to avoid touching the real filesystem in unit tests.
  • Single-writer assumption — one FileTransport instance per file path. Multiple uncoordinated writers will race on rotation.
  • JSON output by default — uses JSONLogFormatter unless a different formatter is supplied.

Requirements

  • Node.js >= 22.0.0
  • @pawells/logger >=4.0.0 (peer dependency)

Installation

Install both the core package and this transport together:

npm install @pawells/logger @pawells/logger-transport-file
# or
yarn add @pawells/logger @pawells/logger-transport-file

Quick Start

import { Logger, ConsoleTransport, LogLevelFilter, LogLevels } from '@pawells/logger';
import { FileTransport } from '@pawells/logger-transport-file';

// ConsoleTransport requires an explicit Register() call
const consoleTransport = new ConsoleTransport({
  filters: [LogLevelFilter(LogLevels.INFO)],
});
consoleTransport.Register();

// FileTransport auto-registers in its constructor — do NOT call Register() again
const fileTransport = new FileTransport({
  filePath: '/var/log/my-app/app.log',   // must be absolute
  rotation: {
    enabled: true,
    maxFileSize: 10 * 1024 * 1024,       // 10 MB
    maxArchives: 5,
  },
});

const logger = new Logger('api');
logger.info('Server started', { port: 3000 });
logger.warn('High memory usage', { memoryPercent: 85 });
logger.error('Request failed', new Error('Connection refused'));

// Graceful shutdown — flush pending writes and close the file handle
process.on('SIGTERM', async () => {
  await fileTransport.close();
  process.exit(0);
});

process.on('SIGINT', async () => {
  await fileTransport.close();
  process.exit(0);
});

Note: Creating a FileTransport is sufficient to start receiving log entries. Registration is automatic. Other transports (ConsoleTransport, StreamTransport, MemoryTransport, and all custom transports) require an explicit Register() call.


API Reference

FileTransport

class FileTransport extends LogTransport<IFileTransportOptions>

Writes formatted log entries to a file on disk. Extends LogTransport from @pawells/logger.

Auto-registration

FileTransport calls this.Register('file-transport:<filePath>') inside its constructor, using the file path as part of the registration name. This allows multiple FileTransport instances to coexist, each writing to a different file. Do not call Register() on a FileTransport — it is already registered. Always call close() in teardown to unregister and flush.

Constructor

constructor(options: IFileTransportOptions, fsModule?: FsModuleType)

| Parameter | Type | Required | Description | | ---------- | ---------------------- | -------- | -------------------------------------------------------------------------------- | | options | IFileTransportOptions | Yes | Transport configuration. See IFileTransportOptions below. | | fsModule | FsModuleType | No | Optional fs/promises-compatible module for dependency injection in tests. |

Validates ALL options synchronously via AssertFileTransportOptions and throws before any I/O occurs:

  • TypeErrorfilePath is not absolute, or contains path traversal sequences (../ or ./); invalid formatter (does not implement LogFormatter); non-function onInitError or onRotationError.
  • RangeErrorrotation.maxFileSize is below 1024 or not an integer; rotation.maxArchives is outside 0–100 or not an integer; rotation.maxTotalArchiveSize is negative or not an integer.

Directory creation and file open are deferred to a background promise (initPromise) and do not throw from the constructor.

Properties

| Property | Type | Description | | ------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | initPromise | Promise<void> | Resolves when async initialization (directory creation and file open) completes. Await this in tests before asserting writes. In production, errors surface automatically. |

Methods

async close(): Promise<void>

Flushes all pending writes, closes the file handle, and unregisters the transport from LogManager. Safe to call multiple times (idempotent). Does not throw — stream-close errors are written to process.stderr. Call this during application shutdown to ensure all buffered entries are written to disk.

async Flush(): Promise<void>

Waits for initialization and all pending writes to complete without closing the file or unregistering the transport. Useful when you need to guarantee log entries are persisted without tearing down the transport.

OnPosted(entry: TLogEntry): Promise<void>

Called automatically by LogManager for each log entry that passes the transport's filter chain. Do not call this directly.


IFileTransportOptions

interface IFileTransportOptions extends ILogTransportOptions {
  filePath: string;
  formatter?: LogFormatter;
  rotation?: IFileRotationOptions;
  filters?: LogEntryPredicate[];
  onInitError?: (error: FileRotationError) => void | Promise<void>;
  onRotationError?: (error: FileRotationError) => void | Promise<void>;
}

| Field | Type | Required | Default | Description | | --------------- | ------------------------------------------------- | -------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | filePath | string | Yes | — | Absolute path to the log file. Relative paths throw TypeError at construction. Parent directory is created automatically with mode 0o700. | | formatter | LogFormatter | No | JSONLogFormatter | Formatter used to convert each TLogEntry to a string before writing. | | rotation | IFileRotationOptions | No | See below | File rotation configuration. Omit to use defaults (10 MB threshold, 5 archives, enabled). | | filters | LogEntryPredicate[] | No | undefined | Inherited from ILogTransportOptions. All predicates must return true for an entry to be written. | | onInitError | (error: FileRotationError) => void \| Promise<void> | No | undefined | Callback invoked when directory creation or file open fails. If omitted, errors are written to process.stderr only. | | onRotationError | (error: FileRotationError) => void \| Promise<void> | No | undefined | Callback invoked when file rotation or archive cleanup fails. If omitted, errors are written to process.stderr only. |


IFileRotationOptions

interface IFileRotationOptions {
  enabled?: boolean;
  maxFileSize?: number;
  maxArchives?: number;
  gzipArchives?: boolean;
  dailyRotation?: boolean;
  maxTotalArchiveSize?: number;
}

| Field | Type | Default | Description | | -------------------- | --------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | enabled | boolean | true | Whether automatic rotation is active. Set to false to disable all rotation. | | maxFileSize | number | 10_485_760 (10 MB) | File size in bytes at which size-based rotation is triggered. Must be at least 1024 bytes. | | maxArchives | number | 5 | Maximum number of archived files to retain. When exceeded, the oldest archive is deleted. Set to 0 to truncate instead of archiving. Maximum value is 100. | | gzipArchives | boolean | false | When true, rotated archive files are compressed with gzip and saved with a .gz extension. | | dailyRotation | boolean | false | When true, the log file is rotated at midnight local time regardless of size, organizing logs by calendar date. | | maxTotalArchiveSize| number | 0 (unlimited) | Maximum total disk space in bytes that all archived files may consume. When exceeded, the oldest archives are deleted until the total is within budget. 0 disables the limit. |


FileRotationError

class FileRotationError extends BaseError<{ code: string; cause?: Error }>

Error class used when log rotation fails. FileTransport catches this error internally and routes it to process.stderr and/or the onRotationError callback — applications do not receive it as an uncaught throw. The error code is always 'FILE_ROTATION_ERROR'.

To observe rotation errors programmatically, provide an onRotationError callback in IFileTransportOptions:

const transport = new FileTransport({
  filePath: '/var/log/app.log',
  onRotationError: (error) => {
    metrics.increment('log.rotation.error');
  },
});

Assertion and Validation Functions

These functions validate options objects. They are useful for validating configuration at application startup or in custom transport wrappers.

AssertFileTransportOptions

function AssertFileTransportOptions(options: unknown): asserts options is IFileTransportOptions

Throws TypeError if options is not a valid IFileTransportOptions object. Validates that filePath is a non-empty absolute string without traversal components, that formatter (if provided) implements the LogFormatter interface, that rotation (if provided) passes rotation validation, and that the error callbacks (if provided) are functions.

ValidateFileTransportOptions

function ValidateFileTransportOptions(options: unknown): boolean

Returns true if options passes AssertFileTransportOptions validation, false otherwise. Non-throwing alternative.

AssertFileRotationOptions

function AssertFileRotationOptions(options: unknown): asserts options is IFileRotationOptions

Throws TypeError or RangeError if options is not a valid IFileRotationOptions object. Validates enabled (boolean), maxFileSize (finite integer >= 1024), maxArchives (integer 0–100), gzipArchives (boolean), dailyRotation (boolean), and maxTotalArchiveSize (non-negative integer).

ValidateFileRotationOptions

function ValidateFileRotationOptions(options: unknown): boolean

Returns true if options passes AssertFileRotationOptions validation, false otherwise.


FsModuleType

type FsModuleType = {
  mkdir: typeof fsPromises.mkdir;
  open: typeof fsPromises.open;
  stat: typeof fsPromises.stat;
  rename: typeof fsPromises.rename;
  realpath: typeof fsPromises.realpath;
  readdir: typeof fsPromises.readdir;
  unlink: typeof fsPromises.unlink;
}

Shape of the fs/promises-compatible object accepted by the FileTransport constructor. Provide an object implementing this interface to avoid real filesystem access in unit tests. Marked @internal in source — the type is exported to support testing only and may change in minor releases.


Testing

FileTransport is designed for deterministic unit testing via the fsModule constructor parameter and initPromise property.

Three rules for testing FileTransport:

  1. Inject a mock fsModule — avoids real filesystem access and makes tests portable.
  2. Await transport.initPromise — initialization is async; asserting before it resolves produces flaky tests.
  3. Call close() in afterEach — because FileTransport auto-registers, every test that constructs one must explicitly close it on teardown to prevent cross-test leakage.
import { describe, it, expect, afterEach } from 'vitest';
import { Logger } from '@pawells/logger';
import { FileTransport } from '@pawells/logger-transport-file';

describe('FileTransport', () => {
  let transport: FileTransport;

  afterEach(async () => {
    // FileTransport auto-registers — always close in afterEach to unregister
    await transport.close();
  });

  it('writes a log entry to the mock file system', async () => {
    const written: string[] = [];

    const mockFs = {
      mkdir: async () => undefined,
      open: async () => ({
        write: async (data: string) => { written.push(data); },
        close: async () => undefined,
      }),
      stat: async () => ({ size: 0 }),
      rename: async () => undefined,
      realpath: async (p: string) => p,
      readdir: async () => [],
      unlink: async () => undefined,
    };

    transport = new FileTransport(
      { filePath: '/tmp/test/app.log' },
      mockFs as never,
    );

    // Await initialization before making assertions
    await transport.initPromise;

    const logger = new Logger('test');
    logger.info('hello from test');

    // Allow the write promise to settle
    await new Promise<void>((resolve) => setTimeout(resolve, 10));

    expect(written.length).toBeGreaterThan(0);
    expect(written[0]).toContain('hello from test');
  });
});

NX commands for this package (run from the repository root):

yarn nx run @pawells/logger-transport-file:typecheck
yarn nx run @pawells/logger-transport-file:lint -- --fix
yarn nx run @pawells/logger-transport-file:test -- --coverage
yarn nx run @pawells/logger-transport-file:build

License

MIT — see LICENSE for details.