@nowarajs/logger
Version:
Type-safe logging library for Bun with advanced TypeScript body intersection, modular sink pattern, transform streams, and immutable API design.
295 lines (225 loc) • 8.1 kB
Markdown
# 🎯 NowaraJS - Logger

## 📌 Table of Contents
- [🎯 NowaraJS - Logger](#-nowarajs---logger)
- [📌 Table of Contents](#-table-of-contents)
- [📝 Description](#-description)
- [✨ Features](#-features)
- [🔧 Installation](#-installation)
- [⚙️ Usage](#-usage)
- [Basic Setup](#basic-setup)
- [Multiple Sinks](#multiple-sinks)
- [Custom Sinks](#custom-sinks)
- [Type-Safe Logging](#type-safe-logging)
- [Error Handling](#error-handling)
- [Flushing and Closing](#flushing-and-closing)
- [Configuration](#configuration)
- [⚖️ License](#-license)
- [📧 Contact](#-contact)
## 📝 Description
> A TypeScript library that provides a modular, type-safe, and worker-based logging system designed specifically for Bun.
**@nowarajs/logger** is a high-performance, asynchronous logging system built on top of Bun's worker threads. It provides a simple sink-based architecture for routing logs to multiple destinations (console, file, custom) with automatic type safety and zero-blocking guarantees.
## ✨ Features
- ⚡ **Non-blocking**: All logging operations are processed in a worker thread
- 🔒 **Type-safe**: Full TypeScript support with type inference for logged objects
- 🎯 **Sink Pattern**: Route logs to multiple destinations (console, file, custom)
- 📦 **Built-in Sinks**: Console and file logger included out of the box
- 🔧 **Custom Sinks**: Easily create custom sinks for your specific needs
- 🔄 **Batched Processing**: Automatic batching for better performance
- 📊 **Log Levels**: ERROR, WARN, INFO, DEBUG, LOG
- 🎛️ **Configurable**: Control batch size, timeout, queue limits and more
- 🔔 **Event-driven**: Listen to lifecycle events (flush, close, errors)
## 🔧 Installation
```bash
bun add @nowarajs/logger
```
### Peer Dependencies
#### Required :
```bash
bun add @nowarajs/error @nowarajs/typed-event-emitter
```
## ⚙️ Usage
### Basic Setup
```typescript
import { Logger } from '@nowarajs/logger';
import { ConsoleLoggerSink } from '@nowarajs/logger/sinks';
// Create a logger and register a console sink
const logger = new Logger()
.registerSink('console', ConsoleLoggerSink);
// Log messages
logger.info('Application started');
logger.warn('This is a warning');
logger.error('An error occurred');
logger.debug('Debug info');
logger.log('Generic log');
// Close the logger when done
await logger.close();
```
### Multiple Sinks
```typescript
import { Logger } from '@nowarajs/logger';
import { ConsoleLoggerSink, FileLoggerSink } from '@nowarajs/logger/sinks';
// Register multiple sinks
const logger = new Logger()
.registerSink('console', ConsoleLoggerSink)
.registerSink('file', FileLoggerSink, './app.log');
// Log to all sinks
logger.info('This goes to console and file');
// Log to specific sinks only
logger.error('Only in file', ['file']);
logger.warn('Only in console', ['console']);
await logger.close();
```
### Custom Sinks
```typescript
import type { LoggerSink, LogLevels } from '@nowarajs/logger/types';
// Create a custom sink
class DatabaseSink implements LoggerSink {
public async log(level: LogLevels, timestamp: number, object: unknown): Promise<void> {
// Your custom logging logic
await saveToDatabase({ level, timestamp, object });
}
}
const logger = new Logger()
.registerSink('database', DatabaseSink);
logger.info('Logged to database');
await logger.close();
```
### Type-Safe Logging
One of the most powerful features is **automatic type safety**. When you create typed sinks, TypeScript automatically infers the correct object shape for logging. When using multiple sinks, it even creates an **intersection type** of all sink types.
#### Single Typed Sink
```typescript
import type { LoggerSink, LogLevels } from '@nowarajs/logger/types';
// Define your log object type
interface UserLog {
userId: number;
action: string;
timestamp?: Date;
}
// Create a typed sink
class UserLogSink implements LoggerSink<UserLog> {
public async log(level: LogLevels, timestamp: number, object: UserLog): Promise<void> {
console.log(`User ${object.userId} performed: ${object.action}`);
}
}
const logger = new Logger()
.registerSink('userLog', UserLogSink);
// ✅ TypeScript requires the correct shape
logger.info({
userId: 123,
action: 'login'
});
// ❌ TypeScript error: Missing required property 'action'
logger.info({
userId: 123
// Error: Property 'action' is missing
});
```
#### Multiple Typed Sinks with Intersection
When logging to multiple typed sinks at the same time, TypeScript automatically creates an **intersection** of all types:
```typescript
interface UserLog {
userId: number;
action: string;
}
interface ApiLog {
endpoint: string;
method: string;
statusCode: number;
}
class UserLogSink implements LoggerSink<UserLog> {
public async log(level: LogLevels, timestamp: number, object: UserLog): Promise<void> {
await saveUser(object);
}
}
class ApiLogSink implements LoggerSink<ApiLog> {
public async log(level: LogLevels, timestamp: number, object: ApiLog): Promise<void> {
await saveApi(object);
}
}
const logger = new Logger()
.registerSink('user', UserLogSink)
.registerSink('api', ApiLogSink);
// ✅ When using both sinks, you need BOTH types combined
logger.info({
userId: 123,
action: 'api_call',
endpoint: '/users',
method: 'POST',
statusCode: 201
}, ['user', 'api']); // Logs to both sinks
// ✅ When using only one sink, only that type is required
logger.warn({
userId: 456,
action: 'failed_attempt'
}, ['user']); // Only UserLog type required
// ❌ TypeScript error: Missing 'endpoint', 'method', 'statusCode'
logger.error({
userId: 789,
action: 'error',
// Error: Missing api properties
}, ['user', 'api']);
```
#### Mixed Typed and Untyped Sinks
When mixing typed and untyped sinks (like `ConsoleLoggerSink` which accepts `unknown`), the intersection includes `unknown`, allowing flexible logging:
```typescript
interface DatabaseLog {
query: string;
duration: number;
}
class DatabaseLogSink implements LoggerSink<DatabaseLog> {
public async log(level: LogLevels, timestamp: number, object: DatabaseLog): Promise<void> {
await logToDatabase(object);
}
}
const logger = new Logger()
.registerSink('database', DatabaseLogSink)
.registerSink('console', ConsoleLoggerSink); // Accepts unknown
// ✅ This works - intersection with unknown allows extra properties
logger.info({
query: 'SELECT * FROM users',
duration: 123,
customData: 'anything goes'
}, ['database', 'console']);
```
### Error Handling
```typescript
const logger = new Logger()
.registerSink('console', ConsoleLoggerSink);
// Listen for errors
logger.on('sinkError', (error) => {
console.error('Logger error:', error.message);
});
logger.on('registerSinkError', (error) => {
console.error('Failed to register sink:', error.message);
});
logger.info('Safe to log');
await logger.close();
```
### Flushing and Closing
```typescript
const logger = new Logger()
.registerSink('console', ConsoleLoggerSink);
logger.info('First message');
logger.info('Second message');
// Wait for all pending logs to be processed
await logger.flush();
// Close the logger and release resources (internally calls flush)
await logger.close();
```
### Configuration
```typescript
const logger = new Logger({
maxPendingLogs: 5000, // Max queued logs (default: 10,000)
batchSize: 50, // Logs per batch (default: 50)
batchTimeout: 100, // Ms before flushing batch (default: 0.1)
maxMessagesInFlight: 100, // Max batches being processed (default: 100)
autoEnd: true, // Auto-close on process exit (default: true)
flushOnBeforeExit: true // Flush before exit (default: true)
});
```
## ⚖️ License
Distributed under the MIT License. See [LICENSE](./LICENSE) for more information.
## 📧 Contact
- Mail: [nowarajs@pm.me](mailto:nowarajs@pm.me)
- GitHub: [NowaraJS](https://github.com/NowaraJS)