A TypeScript library that provides a modular, type-safe, and sink-based logging system designed specifically for Bun.
@nowarajs/logger provides a flexible and high-performance logging system with advanced TypeScript support. It uses a sink pattern architecture that allows multiple logging destinations (console, file, custom) to be used individually or combined, with automatic type inference and body intersection for type-safe logging operations.
bun add @nowarajs/logger
bun add @nowarajs/error @nowarajs/typed-event-emitter
import { Logger } from '@nowarajs/logger';
import { ConsoleLoggerSink, FileLoggerSink } from '@nowarajs/logger/sinks';
// Create a logger with console strategy
const logger = new Logger()
.registerStrategy('console', new ConsoleLoggerSink(true)); // with colors
// Log messages
logger.info('Application started successfully');
logger.error('An error occurred');
logger.debug('Debug information', ['console']); // specific strategy
// Combine multiple sinks
const logger = new Logger()
.registerStrategy('console', new ConsoleLoggerSink(true))
.registerStrategy('file', new FileLoggerSink('./app.log'));
// Logs to both console and file
logger.info('This goes to both sinks');
// Log to specific sinks only
logger.error('Critical error', ['file']); // only to file
logger.warn('Warning message', ['console']); // only to console
The most powerful feature of @nowarajs/logger is its advanced type safety system. You can create custom logging sinks with typed objects, and TypeScript will automatically infer and enforce the correct types based on your selected sinks through body intersection.
When you implement LoggerSink<TLogObject>
, you specify the exact type of object that sink expects:
import { Logger } from '@nowarajs/logger';
import type { LoggerSink, LogLevels } from '@nowarajs/logger/types';
// Define specific interfaces for different logging contexts
interface DatabaseLog {
userId: number;
action: string;
metadata?: Record<string, unknown>;
}
interface ApiLog {
endpoint: string;
method: string;
statusCode: number;
responseTime: number;
}
// Create typed sinks
class DatabaseLoggerStrategy implements LoggerSink<DatabaseLog> {
public async log(level: LogLevels, date: Date, object: DatabaseLog): Promise<void> {
// object is strictly typed as DatabaseLog
await saveToDatabase({
level,
date,
userId: object.userId,
action: object.action,
metadata: object.metadata
});
}
}
// You can just put the type directly in the log method and it will be automatically inferred
class ApiLoggerStrategy implements LoggerSink {
public async log(level: LogLevels, date: Date, object: ApiLog): Promise<void> {
// object is strictly typed as ApiLog
await sendToMonitoring(`${object.method} ${object.endpoint} - ${object.statusCode} (${object.responseTime}ms)`);
}
}
// Register typed sinks
const logger = new Logger()
.registerStrategy('database', new DatabaseLoggerStrategy())
.registerStrategy('api', new ApiLoggerStrategy())
.registerStrategy('console', new ConsoleLoggerSink()); // ConsoleLoggerSink<unknown>
// ✅ TypeScript enforces the correct types based on selected sinks
logger.info({
userId: 123,
action: 'login',
metadata: { ip: '192.168.1.1' }
}, ['database']); // Only DatabaseLog type required
logger.error({
endpoint: '/api/users',
method: 'POST',
statusCode: 500,
responseTime: 1250
}, ['api']); // Only ApiLog type required
// ❌ TypeScript error: Missing required properties
logger.info({
userId: 123,
action: 'login'
// Error: object doesn't match ApiLog interface
}, ['api']);
When using multiple sinks simultaneously, @nowarajs/logger creates a type intersection of all selected sink types using the BodiesIntersection
utility type:
// ✅ TypeScript requires intersection of both types when using multiple sinks
logger.warn({
userId: 123,
action: 'failed_request',
endpoint: '/api/users',
method: 'POST',
statusCode: 400,
responseTime: 200
}, ['database', 'api']); // Both DatabaseLog & ApiLog types required
// ❌ TypeScript error: Missing ApiLog properties
logger.error({
userId: 123,
action: 'error'
// Error: Missing endpoint, method, statusCode, responseTime
}, ['database', 'api']);
// ✅ When no sinks specified, uses all sinks (intersection of all types)
logger.log({
userId: 123,
action: 'system_event',
endpoint: '/health',
method: 'GET',
statusCode: 200,
responseTime: 50
}); // DatabaseLog & ApiLog & unknown (console) intersection required
You can mix typed and untyped sinks. The intersection will include unknown
for untyped sinks:
// Using typed + untyped sinks
logger.info({
userId: 123,
action: 'mixed_log',
additionalData: 'any value' // ✅ Allowed due to intersection with unknown
}, ['database', 'console']); // Type: DatabaseLog & unknown
// TypeScript allows additional properties when unknown is in the intersection
logger.debug({
userId: 123,
action: 'debug_info',
debugLevel: 3,
stackTrace: ['frame1', 'frame2'],
customField: { nested: 'data' }
}, ['database', 'console']); // ✅ Extra properties allowed due to unknown intersection
const logger = new Logger()
.registerStrategy('console', new ConsoleLoggerSink());
// Listen for errors
logger.on('error', (error) => {
console.error('Logger error:', error);
});
// Listen for completion
logger.on('end', () => {
console.log('All pending logs processed');
});
let logger = new Logger();
// Add sinks
logger = logger
.registerStrategy('console', new ConsoleLoggerSink())
.registerStrategy('file', new FileLoggerSink('./app.log'));
// Add multiple sinks at once
logger = logger.registerStrategies([
['database', new DatabaseLoggerStrategy()],
['remote', new RemoteLoggerStrategy()]
]);
// Remove sinks
logger = logger.unregisterStrategy('database');
logger = logger.unregisterStrategies(['file', 'remote']);
// Clear all sinks
logger = logger.clearStrategies();
logger.error('Error message'); // ERROR level
logger.warn('Warning message'); // WARN level
logger.info('Info message'); // INFO level
logger.debug('Debug message'); // DEBUG level
logger.log('Generic message'); // LOG level
// Custom queue size (default: 10,000)
const logger = new Logger({}, 5000);
// With initial sinks
const logger = new Logger({
console: new ConsoleLoggerSink(true),
file: new FileLoggerSink('./app.log')
});
You can find the complete API reference documentation for @nowarajs/logger
at:
Distributed under the MIT License. See LICENSE for more information.