minervajs-auditlogs
Version:
Servicio de Registros de consola, es un módulo de gestión de mensajes para su uso en auditoria, depuración, informativos, avisos y error.
279 lines (245 loc) • 9.14 kB
JavaScript
/** @module Audit_Logs */
const fs = require('node:fs/promises');
const path = require('node:path');
// Códigos de escape ANSI para colores
const colores =
{
reset: '\x1b[0m',
rojo: '\x1b[31m', //
verde: '\x1b[32m', //
amarillo: '\x1b[33m', //
azul: '\x1b[34m', //
magenta: '\x1b[35m',
cian: '\x1b[36m',
gris: '\x1b[90m', //
};
/**
* Clase Audit_Logs personalizada que permite registrar mensajes en la consola con colores
* por nivel y también guardarlos en archivos de log rotativos basados en el número de líneas.
*/
class Audit_Logs
{
/**
* Crea una instancia del Audit_Logs.
* @param {string} [prefijo=''] - Un prefijo opcional que se añadirá a cada mensaje de log.
* @param {string} [filenameBase='app.log'] - El nombre base del archivo de log. Se le
* añadirá un sufijo numérico para la rotación.
* @param {boolean} [useColors=true] - Indica si se deben usar colores en la salida de la consola.
* @param {number} [maxLines=10000] - El número máximo de líneas por archivo de log antes de la rotación.
* @param {boolean} [writeToDisk=false] - Indica si se realizaran escrituras a disco en paralelo despues de la escritura en consola.
*/
constructor(prefijo = 'AuditLogs', options = {})
{
/**
* El prefijo que se añade a cada mensaje de log.
* @type {string}
*/
this.prefijo = prefijo;
/**
* El nombre base del archivo de log.
* @type {string}
*/
this.filenameBase = path.resolve(__dirname, options?.filenameBase ?? 'app.log');
/**
* Indica si se deben usar colores en la salida de la consola.
* @type {boolean}
*/
this.useColors = options?.useColors ?? true;
/**
* Indica si se realizaran escrituras en modo depuracion, esto permite adicionar caracteristicas de depuracion, como maxLines < 100 lineas.
* @type {boolean}
*/
this.debuger = options?.debuger ?? false;
/**
* El número máximo de líneas por archivo de log antes de la rotación.
* No se permite un minimo menor a 100 lineas a menos que estemos trabajando en modo debug = true
* @type {number}
*/
this.maxLines = options?.maxLines ?? 10000;
if (this.maxLines < 100 && this.debuger === false)
{ this.maxLines = 100; }
/**
* Indica si se realizaran escrituras a disco en paralelo despues de la escritura en consola.
* @type {boolean}
*/
this.writeToDisk = options?.writeToDisk ?? false;
/**
* Permitir a los usuarios definir su propio formato para las líneas de log.
* Esto podría incluir el orden de los elementos (timestamp, prefijo, nivel, mensaje), la inclusión de información adicional (como el nombre del archivo y la línea donde se generó el log)
* o formatos más estructurados (como JSON)
* @type {string}
*/
this.format = options?.format ?? '[%timestamp%] [%prefix%]%colorbegin% [%level%] %message% %colorend%';
/**
* El número de línea actual en el archivo de log actual.
* @private
* @type {number}
*/
this.currentLineCount = 0;
/**
* El número de secuencia del archivo de log actual.
* @private
* @type {number}
*/
this.fileSequence = 1;
/**
* La ruta completa del archivo de log actual.
* @private
* @type {string}
*/
this.currentFilename = this.getFilename();
/**
* Cola de promesas para asegurar el orden de escritura
* @private
* @type {Promise}
*/
this.writeQueue = Promise.resolve();
this.start(`Prefijo: ${this.prefijo}`);
this.start(`Nombre Archivo Base:${this.filenameBase}`);
this.start(`Uso de Color: ${this.useColors}`);
this.start(`Max Lineas: ${this.maxLines}`);
this.start(`Escritura a Disco: ${this.writeToDisk}`);
}
/**
* Genera el nombre del archivo de log actual basado en el nombre base y la secuencia.
* @private
* @returns {string} La ruta completa del archivo de log actual.
*/
getFilename()
{
const { name, ext } = path.parse(this.filenameBase);
return `${name}-${this.fileSequence}${ext}`;
}
/**
* Rota el archivo de log actual, cerrándolo y creando uno nuevo con la siguiente secuencia.
* No necesitamos explícitamente cerrar el archivo con appendFile; el sistema operativo se encarga.
* La próxima escritura a un archivo con un nombre diferente creará uno nuevo.
* @async
* @private
*/
async rotateLogFile()
{
let l_message;
this.currentLineCount = 0;
this.fileSequence++;
this.currentFilename = this.getFilename();
}
/**
* Registra un mensaje con el nivel especificado.
* @async
* @param {string} nivel - El nivel del log ('info', 'debug', 'warn', 'error').
* @param {string} mensaje - El mensaje que se va a registrar.
*/
log(nivel, mensaje)
{
const ahora = new Date().toISOString();
const nivelTexto = nivel.toUpperCase();
let lineaConsola = this.formatLog(nivel, mensaje);
const lineaArchivo = this.formatLog(nivel, mensaje, "archivo"); //`[${ahora}] [${this.prefijo}] [${nivelTexto}] ${mensaje}\n`;
// Mostrar en consola
console.log(lineaConsola);
// Escribir en archivo
// Si writeToDisk es false, simplemente omitimos la escritura.
if (this.writeToDisk)
{
// Encolar la operación de escritura
this.writeQueue = this.writeQueue.then(async () => {
try
{
this.currentLineCount++;
if (this.currentLineCount >= this.maxLines)
{ await fs.appendFile(this.currentFilename, lineaArchivo + `Rotando archivo de log ${this.currentFilename}\r`, 'utf8');
await this.rotateLogFile(); }
else
{ await fs.appendFile(this.currentFilename, lineaArchivo, 'utf8'); }
}
catch (error)
{
console.error('Error al escribir en el archivo de log:', error);
return Promise.resolve();
}
});
}
}
formatLog(a_nivel, a_mensaje, a_tipo = 'consola')
{
let l_formatted = this.format || '[%timestamp%] [%prefix%]%colorbegin% [%level%] %message% %colorend%';
const l_now = new Date().toISOString();
l_formatted = l_formatted.replace('%timestamp%', l_now);
l_formatted = l_formatted.replace('%level%', a_nivel.toUpperCase());
l_formatted = l_formatted.replace('%prefix%', this.prefijo);
if (a_tipo == 'archivo')
{
l_formatted = l_formatted.replace('%colorbegin%', '');
l_formatted = l_formatted.replace('%colorend%', '');
l_formatted = l_formatted + '\n';
}
// Aplicar colores a la salida de la consola
if (this.useColors)
{
switch (a_nivel)
{
case 'start':
l_formatted = l_formatted = l_formatted.replace('%colorbegin%', colores.gris);
l_formatted = l_formatted.replace('%colorend%', colores.reset);
break;
case 'info':
l_formatted = l_formatted = l_formatted.replace('%colorbegin%', colores.verde);
l_formatted = l_formatted.replace('%colorend%', colores.reset);
break;
case 'debug':
l_formatted = l_formatted = l_formatted.replace('%colorbegin%', colores.azul);
l_formatted = l_formatted.replace('%colorend%', colores.reset);
break;
case 'warn':
l_formatted = l_formatted = l_formatted.replace('%colorbegin%', colores.amarillo);
l_formatted = l_formatted.replace('%colorend%', colores.reset);
break;
case 'error':
l_formatted = l_formatted = l_formatted.replace('%colorbegin%', colores.rojo);
l_formatted = l_formatted.replace('%colorend%', colores.reset);
break;
}
}
else
{
l_formatted = l_formatted = l_formatted.replace('%colorbegin%', colores.gris);
l_formatted = l_formatted.replace('%colorend%', colores.reset);
}
l_formatted = l_formatted.replace('%message%', a_mensaje);
return l_formatted;
}
/**
* Registra un mensaje con nivel 'Start'.
* @param {string} mensaje - El mensaje inicializacion.
*/
start(mensaje) { this.log('start', mensaje); }
/**
* Registra un mensaje con nivel 'info'.
* @param {string} mensaje - El mensaje informativo.
*/
info(mensaje) { this.log('info', mensaje); }
/**
* Registra un mensaje con nivel 'debug'.
* @param {string} mensaje - El mensaje de depuración.
*/
debug(mensaje) { this.log('debug', mensaje); }
/**
* Registra un mensaje con nivel 'error'.
* @param {string} mensaje - El mensaje de error.
*/
error(mensaje) { this.log('error', mensaje); }
/**
* Registra un mensaje con nivel 'warn'.
* @param {string} mensaje - El mensaje de advertencia.
*/
warn(mensaje) { this.log('warn', mensaje); }
}
module.exports = Audit_Logs;
// {
// getInstance: (prefijo, filenameBase, useColors, maxLines, writeToDisk) => {
// if (!instance)
// { instance = new Audit_Logs(prefijo, filenameBase, useColors, maxLines, writeToDisk); }
// return instance;
// },
// };