DEV Community

Cover image for NPM: komi-logger - A logging library like no other (DX & Strategies)
Komiriko
Komiriko

Posted on

NPM: komi-logger - A logging library like no other (DX & Strategies)

Discover komi-logger: My First Logging Library


Hello! I'm Komiriko and this is my very first post here on dev.to! πŸ‘‹

I'm going to present my komi-logger package that I recently developed. πŸš€

I'd really love to get some feedback on it ☺️, it's a project that's close to my heart and will hopefully help me progress and learn, so without further ado, let me present the project!

πŸ“‹ Table of Contents

🎯 What is komi-logger?

komi-logger is a modular, type-safe logging library based on the Strategy pattern.

It provides a flexible and non-blocking logging system with advanced type safety. Indeed, I put a strong emphasis on Developer Experience (DX) and type safety.

I really loved Elysia (a framework I'm particularly fond of) and that's why I wanted to be inspired by its DX to create this library.

Works with Bun and Node.js!

bun add komi-logger
Enter fullscreen mode Exit fullscreen mode

✨ Why I created this library

To be honest, I created komi-logger simply because I wanted to succeed in creating a logger with good DX and the ability to create custom logging strategies with type safety, that I could use in my projects.

πŸ”₯ Key features

πŸ”„ Non-blocking architecture

Uses transform streams and asynchronous processing.

🎯 Strategy pattern

Multiple logging strategies (console, file, custom) usable individually or combined.

πŸ›‘οΈ Advanced type safety

Complete TypeScript support with strict typing and automatic type intersection based on selected strategies.

⚑ High performance

Queue-based system with configurable buffer (default: 10,000 logs).
The logger uses a transform stream to process log entries asynchronously. Each log is queued and processed through the configured strategies. The system automatically handles backpressure and provides error isolation between strategies.

πŸ”— Event-driven

Emits typed events for error handling (You can throw in your strategy and it will be caught and transmitted with the error event) and lifecycle management.

πŸš€ Usage examples

Let's see some usage examples of komi-logger to illustrate its features!

But before we start, you should know that komi-logger only exports the logger at the root. If you want to use types for example, you need to import them from komi-logger/types.

Here's a list of exports that will be useful to you:

  • komi-logger : Logger
  • komi-logger/types : exports types and interfaces
  • komi-logger/strategies : exports already implemented strategies (console and file)

Strategy management

import { Logger } from 'komi-logger';
import { ConsoleLoggerStrategy, FileLoggerStrategy } from 'komi-logger/strategies';

// You can register strategies individually
const logger = new Logger()
    .registerStrategy('console', new ConsoleLoggerStrategy())
    .registerStrategy('file', new FileLoggerStrategy('./logs/app.log'));

// Or register multiple strategies at once
const logger = new Logger()
    .registerStrategies([
        ['console', new ConsoleLoggerStrategy()],
        ['file', new FileLoggerStrategy('./logs/app.log')]
    ]);

// Or just use the constructor
const logger = new Logger({
    console: new ConsoleLoggerStrategy(),
    file: new FileLoggerStrategy('./logs/app.log')
});

// Remove strategies
logger = logger.unregisterStrategies(['file', 'remote']);

// Clear all strategies
logger = logger.clearStrategies();

Enter fullscreen mode Exit fullscreen mode

Typed strategies with automatic intersection

import type { LoggerStrategy, LogLevels } from 'komi-logger/types';
import { Logger } from 'komi-logger';

// Define specific interfaces
interface DatabaseLog {
    userId: number;
    action: string;
    metadata?: Record<string, unknown>;
}

interface ApiLog {
    endpoint: string;
    method: string;
    statusCode: number;
    responseTime: number;
}

// Create typed strategies
class DatabaseLoggerStrategy implements LoggerStrategy<DatabaseLog> {
    public async log(level: LogLevels, date: Date, object: DatabaseLog): Promise<void> {
        // object is strictly typed as DatabaseLog
        await saveToDatabase(...);
    }
}

// You can omit the LoggerStrategy type if you want, it will be automatically inferred from the log method
class ApiLoggerStrategy implements LoggerStrategy {
    public async log(level: LogLevels, date: Date, object: ApiLog): Promise<void> {
        // object is strictly typed as ApiLog  
        await sendToMonitoring(...);
    }
}

// TypeScript automatically enforces the correct types
const logger = new Logger({
    database: new DatabaseLoggerStrategy(),
    api: new ApiLoggerStrategy()
});

/**
 * Tip: for example with VSCode, you can use Ctrl + Space to see available strategies
 * 
 * logger.log(..., [*here*]); // You'll see available strategies
 * Based on selected strategies, the first parameter of log will be automatically typed. (by default, all strategies are selected)
 */


// βœ… Correct type for one strategy
logger.info({ 
    userId: 123, 
    action: 'login',
    metadata: { ip: '192.168.1.1' } 
}, ['database']); // Only DatabaseLog is required

// βœ… Automatic intersection for multiple strategies  
logger.warn({
    userId: 123,
    action: 'failed_request',
    endpoint: '/api/users',
    method: 'POST', 
    statusCode: 400,
    responseTime: 200
}, ['database', 'api']); // DatabaseLog & ApiLog are required!

// ❌ TypeScript error: missing properties
logger.error({
    userId: 123,
    action: 'error'
    // Error: endpoint, method, statusCode, responseTime missing
}, ['database', 'api']);
Enter fullscreen mode Exit fullscreen mode

TypeScript calculates the intersection of required types based on the strategies you use!

Error handling and events

import type { LoggerStrategy, LogLevels } from 'komi-logger/types';
import { Logger } from 'komi-logger';

class DatabaseLoggerStrategy implements LoggerStrategy {
    public log(): void {
        throw new Error('Database connection failed'); // Simulate an error
    }
}

const logger = new Logger({
    database: new DatabaseLoggerStrategy()
});

logger.log('database');

// Listen for errors
logger.on('error', (error) => {
    console.error(error.uuid); // Unique error UUID
    console.error(error.date); // Error date
    console.error(error.message); // Error message
    console.error(error.key); // Error key (If you want to use i18n)
    console.error(error.cause?.strategyName); // Name of the strategy that caused the error
    console.error(error.cause?.object); // Object that caused the error
    console.error(error.cause?.error); // The error that was thrown
});

// Listen for processing end
logger.on('end', () => {
    console.log('All pending logs have been processed');
});
Enter fullscreen mode Exit fullscreen mode

🀝 Your feedback matters!

This is my first npm package that I'm sharing and I'd really love to get your feedback!

What I'm particularly interested in:

  • πŸ€” What do you think about the API and DX (Developer Experience)?
  • πŸ”§ Are there any features you're missing?
  • πŸ› Have you found any bugs or strange behaviors?
  • πŸ“š Is the documentation clear and complete?
  • ⚑ How do you find the performance in your use cases?
  • πŸ’‘ Do you have any ideas for improvements or new features?

Useful links:

Feel free to:

  • ⭐ Star the repo if you like it
  • πŸ› Open issues for bugs
  • πŸ’‘ Suggest improvements
  • πŸ”„ Contribute to the code

Thanks for reading this far! πŸ™

I'm really excited to see what you think and how you might use it in your projects.

PS: feel free to contact me: X or even by email komiriko@pm.me

Top comments (0)