@pawells/logger-transport-file
v3.0.3
Published
[](https://www.npmjs.com/package/@pawells/logger-transport-file) [](https://github.com/PhillipAWells/logger/r
Readme
@pawells/logger-transport-file
File transport for @pawells/logger. Writes structured log entries to disk with automatic size-based rotation. ESM-only with no extra runtime dependencies beyond @pawells/logger.
Features
- Size-based rotation — rotates the active log file when it exceeds a configurable byte threshold
- Configurable archive count — retains a bounded number of archived log files and deletes the oldest when the limit is reached
- JSON output by default — uses
JSONLogFormatterout of the box; anyLogFormattercan be substituted - Automatic directory creation — creates the parent directory (with mode
0o700) if it does not exist - Graceful async error handling — initialization failures surface on first write via
process.stderr, not during construction - Dependency-injectable
fsmodule — pass a mockFsModuleTypeto the constructor for deterministic unit testing - Auto-registers with
LogManager— the transport subscribes to log events in its constructor; no separateRegister()call is required
Requirements
- Node.js >= 22.0.0
@pawells/logger>= 3.0.0 (peer dependency)
Installation
npm install @pawells/logger @pawells/logger-transport-file
# or
yarn add @pawells/logger @pawells/logger-transport-fileQuick Start
import { Logger, LogManager, ConsoleTransport, LogLevelFilter, LogLevels } from '@pawells/logger';
import { FileTransport } from '@pawells/logger-transport-file';
// Optional: set application-level context
LogManager.Context = 'my-app';
// Console transport — requires explicit Register()
const consoleTransport = new ConsoleTransport({
filters: [LogLevelFilter(LogLevels.INFO)],
});
consoleTransport.Register();
// File transport — auto-registers in its constructor; do NOT call Register() again
const fileTransport = new FileTransport({
filePath: '/var/log/my-app/app.log',
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);
});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') inside its constructor. This is the one built-in transport that auto-registers. Every other transport (ConsoleTransport, StreamTransport, MemoryTransport, and all custom transports) requires an explicit Register() call after construction. Do not call Register() on a FileTransport instance — it is already registered.
Constructor
constructor(options: IFileTransportOptions, fsModule?: FsModuleType)options— required configuration; seeIFileTransportOptionsbelowfsModule— optionalfs/promises-compatible module for dependency injection in tests
Throws TypeError synchronously if filePath is not an absolute path or contains path traversal sequences (../ or ./). All other initialization (directory creation, file open) is deferred to a background promise and surfaces errors on the first write.
Properties
readonly initPromise: Promise<void>Resolves when async initialization (directory creation and file open) completes. Await this in tests before asserting that writes have occurred. In production code you do not need to await this — errors surface automatically through process.stderr on the first write.
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.
OnPosted(entry: TLogEntry): Promise<void>Called automatically by LogManager for each log entry. Do not call this directly.
IFileTransportOptions
interface IFileTransportOptions extends ILogTransportOptions {
filePath: string;
formatter?: LogFormatter;
rotation?: IFileRotationOptions;
filters?: LogEntryPredicate[];
}| 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. |
| 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 (no filtering) | Inherited from ILogTransportOptions. All predicates must return true for an entry to be written. |
IFileRotationOptions
interface IFileRotationOptions {
enabled?: boolean;
maxFileSize?: number;
maxArchives?: number;
}| Field | Type | Default | Description |
|---|---|---|---|
| enabled | boolean | true | Whether automatic size-based rotation is active. Set to false to disable rotation entirely. |
| maxFileSize | number | 10_485_760 (10 MB) | File size in bytes at which 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. |
FileRotationError
class FileRotationError extends Error {
readonly code = 'FILE_ROTATION_ERROR';
constructor(message: string, cause: unknown)
}Error class used internally when log rotation fails. FileTransport catches this error and writes it to process.stderr — applications do not receive it via throw. The code property is always 'FILE_ROTATION_ERROR'.
Assertion and Validation Functions
These functions validate options objects and are useful for building wrappers or validating configuration at startup.
AssertFileTransportOptions
function AssertFileTransportOptions(options: unknown): asserts options is IFileTransportOptionsThrows TypeError if options is not a valid IFileTransportOptions object. Checks that filePath is a non-empty absolute string without traversal components, that formatter (if provided) implements LogFormatter, and that rotation (if provided) passes rotation validation.
ValidateFileTransportOptions
function ValidateFileTransportOptions(options: unknown): booleanReturns true if options passes AssertFileTransportOptions validation, false otherwise. Non-throwing alternative to AssertFileTransportOptions.
AssertFileRotationOptions
function AssertFileRotationOptions(options: unknown): asserts options is IFileRotationOptionsThrows TypeError or RangeError if options is not a valid IFileRotationOptions object. Validates enabled (boolean), maxFileSize (finite number >= 1024), and maxArchives (integer 0–100).
ValidateFileRotationOptions
function ValidateFileRotationOptions(options: unknown): booleanReturns true if options passes AssertFileRotationOptions validation, false otherwise.
Testing
FileTransport accepts an optional fsModule parameter for dependency injection. Pass a mock that implements FsModuleType to avoid touching the real filesystem in unit tests.
After constructing the transport, await transport.initPromise before making assertions to ensure the async initialization sequence has completed.
Always unregister the transport in afterEach — because FileTransport auto-registers, any test that constructs one must explicitly unregister it on teardown:
import { describe, it, expect, afterEach } from 'vitest';
import { LogManager, Logger } from '@pawells/logger';
import { FileTransport } from '@pawells/logger-transport-file';
describe('FileTransport', () => {
let transport: FileTransport;
afterEach(async () => {
// FileTransport auto-registers, so always unregister on teardown
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,
};
transport = new FileTransport(
{ filePath: '/tmp/test/app.log' },
mockFs as never,
);
// Await initialization before asserting
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');
});
});Development
yarn nx run-many -t build # Compile TypeScript → ./dist/
yarn nx run-many -t typecheck # Type check without emitting
yarn nx run-many -t lint # ESLint
yarn nx run-many -t test # Run tests with Vitest
yarn nx run-many -t test -- --coverage # Tests with coverage reportTypeScript Support
All public types are exported from the main entry point:
import {
FileTransport,
type FsModuleType,
type IFileTransportOptions,
type IFileRotationOptions,
AssertFileTransportOptions,
ValidateFileTransportOptions,
AssertFileRotationOptions,
ValidateFileRotationOptions,
FileRotationError,
} from '@pawells/logger-transport-file';License
MIT — See LICENSE for details.
