@pawells/logger
v1.2.2
Published
Structured logging library for TypeScript/Node.js with support for multiple transports and log aggregation
Downloads
1,359
Maintainers
Readme
@pawells/logger
Structured logging library for TypeScript/Node.js with ESM, no runtime dependencies, and support for multiple transports and formatters for structured logging and log aggregation.
Features
- Structured logging with configurable log levels (DEBUG, INFO, WARN, ERROR, FATAL, SILENT)
- Multiple built-in transports: Console to stdout and stderr (with ANSI colors)
- Pluggable transport system for custom integrations
- JSON formatter for log aggregation platforms
- Nanosecond-precision timestamps
- Support for metadata and tracing fields (traceId, spanId, correlationId)
- Full TypeScript support with strict typing
- ESM-only, no runtime dependencies
- Targets ES2022, runs on Node.js >= 22.0.0
Installation
npm install @pawells/logger
# or
yarn add @pawells/loggerQuick Start
import { Logger, ConsoleTransport, LogLevel } from '@pawells/logger';
// Create a logger instance
const logger = new Logger({
service: 'my-app',
level: LogLevel.DEBUG,
transport: new ConsoleTransport(),
});
// Log messages at different levels
await logger.debug('Debug information', { userId: 123 });
await logger.info('Application started');
await logger.warn('Memory usage is high', { usage: 85 });
await logger.error('Request failed', { statusCode: 500 });
await logger.fatal('System critical error', { errno: 'EACCES' });API Reference
Logger Class
Main logging interface with methods for each log level.
Constructor
constructor(config: ILoggerConfig)ILoggerConfig:
service: string— Required service name for the loggerlevel?: LogLevel— Minimum log level to output (defaults to INFO)format?: 'json' | 'text'— Output format (defaults to 'text')transport?: ITransport— Transport to send logs to (defaults to ConsoleTransport)
Methods
async debug(message: string, metadata?: Record<string, unknown>): Promise<void>
async info(message: string, metadata?: Record<string, unknown>): Promise<void>
async warn(message: string, metadata?: Record<string, unknown>): Promise<void>
async error(message: string, metadata?: Record<string, unknown>): Promise<void>
async fatal(message: string, metadata?: Record<string, unknown>): Promise<void>LogLevel Enum
enum LogLevel {
DEBUG = 'debug',
INFO = 'info',
WARN = 'warn',
ERROR = 'error',
FATAL = 'fatal',
SILENT = 'silent', // suppresses all output when set as the logger level
}ConsoleTransport
Outputs log entries to stdout with ANSI color formatting. Colors are only applied when the stream is an interactive TTY.
import { Logger, ConsoleTransport, LogLevel } from '@pawells/logger';
const logger = new Logger({
service: 'my-app',
level: LogLevel.INFO,
transport: new ConsoleTransport({ service: 'my-app', level: LogLevel.INFO }),
});StderrTransport
Identical to ConsoleTransport but writes to stderr instead of stdout. Useful for servers that reserve stdout for structured protocol output (MCP, JSON-RPC, LSP, etc.).
import { Logger, StderrTransport, LogLevel } from '@pawells/logger';
const logger = new Logger({
service: 'my-app',
level: LogLevel.INFO,
transport: new StderrTransport({ service: 'my-app', level: LogLevel.INFO }),
});formatForJson()
Formats log entries as structured JSON compatible with log aggregation platforms.
import { formatForJson, ILogEntry, LogLevel } from '@pawells/logger';
const logEntry: ILogEntry = {
timestamp: Date.now().toString(),
level: LogLevel.INFO,
service: 'my-app',
message: 'User login successful',
metadata: { userId: '42' },
};
const jsonOutput = formatForJson(logEntry);
// Output: {"timestamp":"1705257983000000000","level":"info","service":"my-app","message":"User login successful","metadata":{"userId":"42"}}ITransport Interface
Implement this interface to create custom transports.
interface ITransport {
write(entry: ILogEntry): void | Promise<void>;
}ILogEntry:
timestamp: string— Nanosecond-precision timestamp as stringlevel: LogLevel— Log levelservice: string— Service namemessage: string— Log messagemetadata?: Record<string, unknown>— Contextual metadatatraceId?: string— Distributed trace IDspanId?: string— Distributed span IDcorrelationId?: string— Correlation ID for request tracking
Custom Transport Example
Create a custom transport to send logs to an external service:
import { Logger, ITransport, ILogEntry, LogLevel } from '@pawells/logger';
class FileTransport implements ITransport {
constructor(private filePath: string) {}
async write(entry: ILogEntry): Promise<void> {
const line = `[${entry.level}] ${entry.message}\n`;
// Write to file (implementation varies by use case)
await appendToFile(this.filePath, line);
}
}
const logger = new Logger({
service: 'my-app',
level: LogLevel.INFO,
transport: new FileTransport('./logs/app.log'),
});Log Aggregation Integration
For log aggregation with external platforms:
import { Logger, ITransport, ILogEntry, formatForJson, LogLevel } from '@pawells/logger';
class AggregationTransport implements ITransport {
constructor(private endpoint: string) {}
async write(entry: ILogEntry): Promise<void> {
const jsonOutput = formatForJson(entry);
await fetch(this.endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: jsonOutput,
});
}
}
const logger = new Logger({
service: 'my-app',
level: LogLevel.DEBUG,
transport: new AggregationTransport('https://aggregation.example.com/api/v1/push'),
});Testing
The package ships mock transport factories for use in unit tests. Import from the subpath that matches your test framework. These subpath exports require the matching testing framework as a peer dependency — vitest for @pawells/logger/testing/vitest, @jest/globals for @pawells/logger/testing/jest. Both are optional; only install the one you use.
Vitest
import { createMockTransport } from '@pawells/logger/testing/vitest';
import { Logger, LogLevel } from '@pawells/logger';
const transport = createMockTransport();
const logger = new Logger({ service: 'my-app', level: LogLevel.DEBUG, transport });
await logger.info('hello');
expect(transport.write).toHaveBeenCalledOnce();
expect(transport.write.mock.calls[0][0].message).toBe('hello');Jest
import { createMockTransport } from '@pawells/logger/testing/jest';
import { Logger, LogLevel } from '@pawells/logger';
const transport = createMockTransport();
const logger = new Logger({ service: 'my-app', level: LogLevel.DEBUG, transport });
await logger.info('hello');
expect(transport.write).toHaveBeenCalledTimes(1);
expect(transport.write.mock.calls[0][0].message).toBe('hello');transport.write is a typed spy (vi.fn() / jest.fn()) whose argument is ILogEntry, so all standard mock assertion APIs are available.
TypeScript Support
Full TypeScript support with strict typing. All types are exported for custom implementations:
import {
Logger,
LogLevel,
ILogEntry,
ILoggerConfig,
ITransport,
IWritableStream,
ConsoleTransport,
StderrTransport,
formatForJson,
normalizeMetadata,
NS_PER_MS,
} from '@pawells/logger';normalizeMetadata(metadata: unknown) applies the same metadata normalisation rules the Logger uses internally: null/undefined/empty objects → undefined; Error → { error, name, stack }; arrays and primitives → { value }; plain objects passed through. Useful when building custom transports or formatters that need consistent metadata handling.
NS_PER_MS (1_000_000n) is the BigInt conversion factor used to convert a millisecond Date.now() value to the nanosecond string stored in ILogEntry.timestamp.
Development
yarn install # Install dependencies
yarn build # Compile TypeScript (tsconfig.build.json) → ./build/
yarn dev # Build + run
yarn watch # Watch mode
yarn typecheck # Type check without building
yarn lint # ESLint
yarn lint:fix # ESLint with auto-fix
yarn test # Run tests
yarn test:ui # Interactive Vitest UI
yarn test:coverage # Tests with coverage reportRequirements
- Node.js >= 22.0.0
License
MIT — See LICENSE for details.
