mercy-logger
v2.1.4
Published
A fast, flexible and feature-rich logger for Node.js with worker threads support
Maintainers
Readme
Mercy Logger
Un logger rápido, flexible y con muchas características para Node.js con soporte para worker threads, rotación de archivos, transporte HTTP y más.
Características
- 🚀 Alto rendimiento con soporte opcional para worker threads
- 📁 Rotación de archivos por tamaño o fecha (on-demand, sin timers)
- 🌐 Transporte HTTP con batch, reintentos y autenticación
- 🎨 Múltiples formatos (texto y JSON)
- 🔧 Altamente configurable
- 🔇 Modo silencioso para desactivar completamente el logger
- 📦 Dependencias mínimas usando solo APIs nativas de Node.js
- 💾 Compresión automática de archivos rotados
- 🔄 Recuperación automática de workers caídos
- 🎯 Filtrado por nivel en cada transport
- 🌍 Multiplataforma (Windows, Linux, macOS)
Instalación
npm install mercy-loggerUso Básico
import Mercy from 'mercy-logger';
const logger = new Mercy({
level: 'info',
transports: [
{ type: 'console', format: 'text', colors: true },
{ type: 'file', filePath: './logs/app.log', format: 'json' }
]
});
logger.info('Application started');
logger.error('An error occurred', { userId: 123, action: 'login' });Arquitectura
Componentes Principales
- Mercy (
src/mercy.ts) - Clase principal del logger - Worker Logger (
src/workers/logger.worker.ts) - Worker thread para procesamiento asíncrono - Transports (
src/transports/index.ts) - Manejadores de destinos de logs - Utils (
src/utils/index.ts) - Utilidades para formateo, rotación, etc. - Interfaces (
src/interfaces/logger.interface.ts) - Definiciones de tipos
Modos de Operación
1. Modo Worker (por defecto)
- Los logs se envían a un Worker Thread separado
- El worker procesa y escribe los logs sin bloquear el hilo principal
- Health checks cada 60 segundos (
ping/pong) - Reconexión automática (hasta 3 intentos)
- Si falla, hace fallback a modo directo
2. Modo Directo
- Los logs se procesan en el mismo hilo
- Se activa si
useWorker: falseo si el worker falla
Configuración
Configuración Principal
interface MercyConfig {
level?: LogLevel; // Nivel mínimo de log
format?: LogFormat; // Formato por defecto
colors?: boolean; // Colores en consola
silent?: boolean; // Desactivar completamente
useWorker?: boolean; // Usar worker threads (default: true)
transports?: Transport[]; // Array de transportes
metadata?: { // Metadatos globales
service?: string;
version?: string;
environment?: string;
};
}Niveles de Log
trace (0) → debug (1) → info (2) → warn (3) → error (4) → fatal (5)Estructura de LogEntry
{
timestamp: Date,
level: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal',
message: string,
meta?: any, // Metadata adicional (puedes incluir tags aquí)
pid?: number, // Process ID
hostname?: string // Hostname del servidor
}Transports
1. Console Transport
Escribe en console.log o console.error con soporte para colores.
{
type: 'console',
format?: 'text' | 'json',
colors?: boolean,
level?: LogLevel
}Colores disponibles:
trace: grisdebug: azulinfo: verdewarn: amarilloerror: rojo boldfatal: blanco sobre fondo rojo bold
Formato Text:
2025-10-04T20:49:15.588Z [INFO ] Usuario logueado {"userId":123}Formato JSON:
{"timestamp":"2025-10-04T20:49:15.588Z","level":"info","message":"Usuario logueado","meta":{"userId":123}}2. File Transport
Escribe en archivos locales con rotación automática on-demand (sin timers).
{
type: 'file',
filePath: string,
format?: 'text' | 'json',
level?: LogLevel,
rotation?: {
type: 'size' | 'date',
maxSize?: string, // '10MB', '1GB', etc.
datePattern?: string, // 'YYYY-MM-DD', 'YYYY-MM-DD-HH', etc.
maxFiles?: number, // Número máximo de archivos a mantener
compress?: boolean // Comprimir archivos rotados (.gz)
}
}Rotación por Tamaño
La rotación se verifica antes de escribir cada log. Si el archivo excede maxSize, se rota automáticamente.
{
type: 'file',
filePath: './logs/app.log',
rotation: {
type: 'size',
maxSize: '10MB',
compress: true,
maxFiles: 5
}
}Formatos de tamaño soportados:
B: BytesKB: KilobytesMB: MegabytesGB: Gigabytes
Ejemplos: '10MB', '1.5GB', '500KB'
Proceso:
- Antes de escribir un log, verifica el tamaño del archivo actual
- Si ≥
maxSize, cierra el stream - Renombra el archivo con timestamp (ej:
app.2025-10-04T20-49-15.log) - Si
compress: true, comprime a.gzy borra el original - Si
maxFilesestá configurado, elimina archivos antiguos - Crea un nuevo archivo y escribe el log
Rotación por Fecha
La rotación se verifica antes de escribir cada log comparando el patrón de fecha actual vs. la última rotación.
{
type: 'file',
filePath: './logs/daily.log',
rotation: {
type: 'date',
datePattern: 'YYYY-MM-DD',
compress: true,
maxFiles: 30 // Mantener 30 días
}
}Patrones de fecha soportados:
| Patrón | Frecuencia | Ejemplo |
|--------|------------|---------|
| YYYY-MM-DD-HH-mm | Cada minuto | 2025-10-04-20-49 |
| YYYY-MM-DD-HH | Cada hora | 2025-10-04-20 |
| YYYY-MM-DD | Cada día | 2025-10-04 |
| YYYY-MM | Cada mes | 2025-10 |
Cómo funciona:
- El
datePatterndetermina cuándo rotar (comparando strings formateados) - También determina el nombre del archivo rotado
- Ejemplo: con
YYYY-MM-DD-HH-mm, rota cada vez que cambia el minuto
Proceso:
- Antes de escribir un log, formatea la fecha actual y la última rotación con el patrón
- Si los strings son diferentes, el patrón cambió (ej: de
2025-10-04-14a2025-10-04-15) - Cierra el stream actual
- Renombra el archivo con la fecha (ej:
app.2025-10-04-14.log) - Comprime y limpia archivos antiguos si está configurado
- Crea un nuevo archivo y escribe el log
3. HTTP Transport
Envía logs a un endpoint HTTP con batching, reintentos automáticos y autenticación.
{
type: 'http',
url: string,
method?: 'POST' | 'PUT',
level?: LogLevel,
headers?: Record<string, string>,
auth?: {
type: 'bearer' | 'basic' | 'header',
token?: string, // Para bearer
username?: string, // Para basic
password?: string, // Para basic
headerName?: string, // Para header custom
headerValue?: string // Para header custom
},
timeout?: number, // Timeout en ms (default: 10000)
retries?: number, // Número de reintentos (default: 3)
batchSize?: number, // Logs por batch (default: 1)
flushInterval?: number // Intervalo de flush en ms (default: 5000)
}Sistema de Batching
Los logs se acumulan y envían en lotes para optimizar el rendimiento:
- Flush automático: cada
flushIntervalms (default: 5000ms) - Flush inmediato: cuando el batch alcanza
batchSizelogs - Cola de reintentos: Si falla el envío, los logs van a
retryQueue - Límite de cola: Máximo 1000 logs para evitar memory leaks
Reintentos con Backoff Exponencial
- Hasta
retriesintentos (default: 3) - Delay entre reintentos: 2^intento * 1000ms (1s, 2s, 4s...)
- Si todos los reintentos fallan, los logs quedan en
retryQueue
Tipos de Autenticación
Bearer Token:
auth: {
type: 'bearer',
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
}
// Header generado: Authorization: Bearer eyJhbGc...Basic Auth:
auth: {
type: 'basic',
username: 'user',
password: 'pass'
}
// Header generado: Authorization: Basic dXNlcjpwYXNz (base64)Custom Header:
auth: {
type: 'header',
headerName: 'X-API-Key',
headerValue: 'secret-key'
}
// Header generado: X-API-Key: secret-keyFormato de Datos Enviados
Los logs se transforman a JSON antes de enviarse:
Single log:
{
"timestamp": "2025-10-04T20:49:15.588Z",
"level": "info",
"message": "Usuario logueado",
"meta": { "userId": 123 },
"pid": 12345,
"hostname": "server-01"
}Batch (múltiples logs):
[
{
"timestamp": "2025-10-04T20:49:15.588Z",
"level": "info",
"message": "Usuario logueado",
"meta": { "userId": 123 },
"pid": 12345,
"hostname": "server-01"
},
{
"timestamp": "2025-10-04T20:49:16.123Z",
"level": "error",
"message": "Error al procesar pago",
"meta": { "errorCode": "PAYMENT_FAILED" },
"pid": 12345,
"hostname": "server-01"
}
]Headers enviados:
Content-Type: application/json
Authorization: Bearer <token> (si hay auth)
<custom headers> (si los configuraste)Cómo Recibir los Logs en el Servidor
Tu endpoint debe:
- Aceptar POST o PUT (según configuración)
- Parsear JSON del body
- Manejar ambos casos: objeto único o array
- Responder con status 2xx para confirmar éxito
- Cualquier otro status se considera error y se reintenta
Ejemplo de endpoint Express:
app.post('/logs', (req, res) => {
const logs = Array.isArray(req.body) ? req.body : [req.body];
// Validar autenticación
const token = req.headers.authorization?.replace('Bearer ', '');
if (token !== 'mi-token-secreto') {
return res.status(401).json({ error: 'Unauthorized' });
}
// Procesar logs
logs.forEach(log => {
console.log(`[${log.level}] ${log.message}`, log.meta);
// Guardar en DB, enviar a otro servicio, etc.
});
res.status(200).json({ received: logs.length });
});Flujo de Envío HTTP
Log creado → Batch acumulado → Alcanza batchSize o flushInterval
↓
Transformar LogEntry[] a formato JSON
↓
Agregar headers (Content-Type, Auth, Custom)
↓
Enviar POST/PUT con fetch
↓
¿Éxito (2xx)? → ✓ Listo
↓
¿Error? → Agregar a retryQueue → Reintentar con backoff exponencial
↓
¿Sigue fallando? → Mantener en cola (máx 1000) → Log de error en consolaAPI
Métodos de Logging
logger.trace(message: string, meta?: any): void
logger.debug(message: string, meta?: any): void
logger.info(message: string, meta?: any): void
logger.warn(message: string, meta?: any): void
logger.error(message: string, meta?: any): void
logger.fatal(message: string, meta?: any): void
logger.log(level: LogLevel, message: string, meta?: any): voidMétodos de Control
await logger.flush(): Promise<void> // Forzar escritura de buffers pendientes
await logger.close(): Promise<void> // Cerrar logger y limpiar recursosEjemplos Avanzados
Configuración Multi-Transport
const logger = new Mercy({
level: 'info',
transports: [
// Console solo para warnings y errores
{
type: 'console',
level: 'warn',
colors: true
},
// Archivo general con rotación diaria
{
type: 'file',
filePath: './logs/app.log',
rotation: {
type: 'date',
datePattern: 'YYYY-MM-DD',
maxFiles: 7,
compress: true
}
},
// Archivo solo para errores con rotación por tamaño
{
type: 'file',
filePath: './logs/errors.log',
level: 'error',
rotation: {
type: 'size',
maxSize: '50MB',
maxFiles: 3,
compress: true
}
},
// HTTP para errores críticos (envío inmediato)
{
type: 'http',
url: 'https://alerts.example.com/webhook',
level: 'error',
batchSize: 1,
auth: {
type: 'bearer',
token: process.env.ALERT_TOKEN
}
}
]
});Rotación por Hora
const logger = new Mercy({
transports: [
{
type: 'file',
filePath: './logs/hourly.log',
rotation: {
type: 'date',
datePattern: 'YYYY-MM-DD-HH', // Rota cada hora
maxFiles: 24, // Mantener 24 horas
compress: true
}
}
]
});HTTP Transport con Batching
const logger = new Mercy({
transports: [
{
type: 'http',
url: 'https://logs.example.com/api/ingest',
method: 'POST',
batchSize: 50, // Enviar cada 50 logs
flushInterval: 10000, // O cada 10 segundos
timeout: 15000,
retries: 5,
auth: {
type: 'bearer',
token: process.env.LOG_API_TOKEN
},
headers: {
'X-Service-Name': 'my-app',
'X-Environment': process.env.NODE_ENV
}
}
]
});Configuración por Ambiente
const isDev = process.env.NODE_ENV === 'development';
const logger = new Mercy({
level: isDev ? 'trace' : 'info',
silent: process.env.SILENT_LOGS === 'true',
useWorker: !isDev, // Worker en producción, directo en desarrollo
transports: [
{
type: 'console',
colors: isDev,
level: isDev ? 'trace' : 'warn'
},
...(isDev ? [] : [
{
type: 'file',
filePath: './logs/app.log',
rotation: {
type: 'date',
datePattern: 'YYYY-MM-DD',
maxFiles: 30,
compress: true
}
},
{
type: 'http',
url: process.env.LOG_ENDPOINT!,
batchSize: 100,
auth: {
type: 'bearer',
token: process.env.LOG_TOKEN!
}
}
])
]
});Graceful Shutdown
process.on('SIGINT', async () => {
console.log('Shutting down gracefully...');
await logger.flush(); // Asegurar que se envíen todos los logs
await logger.close(); // Cerrar transports y worker
process.exit(0);
});
process.on('SIGTERM', async () => {
await logger.flush();
await logger.close();
process.exit(0);
});Usando Metadata para Tags
// Incluir tags en el campo meta
logger.info('Usuario logueado', {
tags: ['auth', 'login', 'success'],
userId: 123,
ip: '192.168.1.1'
});
logger.error('Pago rechazado', {
tags: ['payment', 'error', 'critical'],
errorCode: 'INSUFFICIENT_FUNDS',
amount: 150.00
});Worker Thread
El logger puede ejecutarse en un Worker Thread separado para no bloquear el hilo principal.
Mensajes soportados:
log- Escribir un logflush- Forzar flush de todos los transportsclose- Cerrar worker y todos los transportsping- Health check
Respuestas del worker:
flushed- Flush completadoclosed- Worker cerrado exitosamentepong- Respuesta a ping (health check)heartbeat- Worker está vivo (cada 30s)error- Error al procesar mensaje
Características:
- Health checks cada 60 segundos
- Reconexión automática (hasta 3 intentos)
- Fallback a modo directo si falla
- Timeout de flush: 10 segundos
- Timeout de close: 5 segundos
Notas Importantes
- Worker fallback: Si el worker falla 3 veces, automáticamente cambia a modo directo
- Rotación on-demand: Se verifica antes de cada escritura (sin timers en background)
- Timeout de flush: 10 segundos máximo
- Timeout HTTP: 10 segundos por defecto (configurable)
- Cola de reintentos HTTP: Máximo 1000 logs para evitar memory leaks
- Health checks: Cada 60 segundos en modo worker
- Multiplataforma: Funciona en Windows, Linux y macOS
- Timestamps: Usa formato ISO 8601 (
2025-10-04T20:49:15.588Z) para legibilidad
Mejoras Sugeridas
Para HTTP Transport
- Rate Limiting: Limitar requests por segundo
- Confirmación de Recepción: Servidor devuelve IDs de logs recibidos
- Circuit Breaker: Pausar envíos si el endpoint falla repetidamente
- Metadata Global: Incluir service, version, environment en todos los logs
- Webhooks: Notificar cuando se pierden logs por exceder la cola
- Chunking Inteligente: Dividir batches muy grandes (>1MB) automáticamente
- Formatos Adicionales: Logstash, Syslog, custom serializers
Rendimiento
- Worker Threads: Logging asíncrono sin bloquear el hilo principal
- Batching: Agrupación de logs para HTTP transport (reduce overhead)
- Compresión: Archivos rotados se comprimen automáticamente (ahorra espacio)
- Recuperación: Workers caídos se reinician automáticamente (hasta 3 veces)
- Fallback: Si el worker falla, automáticamente cambia a modo directo (sin pérdida de logs)
- On-demand rotation: Verifica rotación solo al escribir (sin overhead de timers)
Licencia
MIT
Autor
Ramiro Lopez
