import * as winston from 'winston'; import * as DailyRotateFile from 'winston-daily-rotate-file'; {{#if hasSentry}} import * as Sentry from '@sentry/node'; {{/if}} {{#if hasElasticsearch}} import { ElasticsearchTransport } from 'winston-elasticsearch'; import { Client } from '@elastic/elasticsearch'; {{/if}} // Configuración de niveles de log personalizados const customLevels = { levels: { error: 0, warn: 1, info: 2, http: 3, debug: 4, }, colors: { error: 'red', warn: 'yellow', info: 'green', http: 'magenta', debug: 'blue', }, }; // Agregar colores a winston winston.addColors(customLevels.colors); // Formato personalizado para logs const customFormat = winston.format.combine( winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.errors({ stack: true }), winston.format.json(), winston.format.printf(({ timestamp, level, message, stack, ...meta }) => { let log = `${timestamp} [${level.toUpperCase()}]: ${message}`; if (stack) { log += `\n${stack}`; } if (Object.keys(meta).length > 0) { log += `\n${JSON.stringify(meta, null, 2)}`; } return log; }) ); // Formato para consola con colores const consoleFormat = winston.format.combine( winston.format.colorize({ all: true }), winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.printf(({ timestamp, level, message, stack, ...meta }) => { let log = `${timestamp} [${level}]: ${message}`; if (stack) { log += `\n${stack}`; } if (Object.keys(meta).length > 0) { log += `\n${JSON.stringify(meta, null, 2)}`; } return log; }) ); // Configuración de transports const transports: winston.transport[] = []; {{#if enableConsoleLog}} // Transport para consola transports.push( new winston.transports.Console({ level: process.env.LOG_LEVEL || 'info', format: consoleFormat, handleExceptions: true, handleRejections: true, }) ); {{/if}} {{#if enableFileLog}} // Transport para archivos con rotación diaria transports.push( new DailyRotateFile({ filename: 'logs/application-%DATE%.log', datePattern: 'YYYY-MM-DD', zippedArchive: true, maxSize: '20m', maxFiles: '14d', level: 'info', format: customFormat, handleExceptions: true, handleRejections: true, }) ); // Transport separado para errores transports.push( new DailyRotateFile({ filename: 'logs/error-%DATE%.log', datePattern: 'YYYY-MM-DD', zippedArchive: true, maxSize: '20m', maxFiles: '30d', level: 'error', format: customFormat, handleExceptions: true, handleRejections: true, }) ); // Transport para logs de auditoría transports.push( new DailyRotateFile({ filename: 'logs/audit-%DATE%.log', datePattern: 'YYYY-MM-DD', zippedArchive: true, maxSize: '20m', maxFiles: '90d', level: 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.json() ), }) ); {{/if}} {{#if hasElasticsearch}} // Transport para Elasticsearch const esClient = new Client({ node: process.env.ELASTICSEARCH_URL || 'http://localhost:9200', auth: { username: process.env.ELASTICSEARCH_USERNAME || '', password: process.env.ELASTICSEARCH_PASSWORD || '', }, }); transports.push( new ElasticsearchTransport({ client: esClient, level: 'info', index: 'application-logs', typeName: '_doc', transformer: (logData) => { return { '@timestamp': new Date().toISOString(), level: logData.level, message: logData.message, meta: logData.meta, environment: process.env.NODE_ENV || 'development', application: process.env.APP_NAME || 'backend-app', }; }, }) ); {{/if}} // Configuración principal de Winston const logger = winston.createLogger({ levels: customLevels.levels, level: process.env.LOG_LEVEL || 'info', format: customFormat, defaultMeta: { service: process.env.APP_NAME || 'backend-app', environment: process.env.NODE_ENV || 'development', version: process.env.APP_VERSION || '1.0.0', }, transports, exitOnError: false, }); {{#if hasSentry}} // Configuración de Sentry para errores críticos if (process.env.SENTRY_DSN) { Sentry.init({ dsn: process.env.SENTRY_DSN, environment: process.env.NODE_ENV || 'development', tracesSampleRate: 1.0, }); // Transport personalizado para Sentry const sentryTransport = new winston.transports.Console({ level: 'error', format: winston.format.combine( winston.format.errors({ stack: true }), winston.format.json() ), }); sentryTransport.log = function(info, callback) { if (info.level === 'error') { const error = info.stack ? new Error(info.message) : info.message; if (error instanceof Error) { error.stack = info.stack; } Sentry.captureException(error, { tags: { level: info.level, service: info.service, }, extra: info.meta || {}, }); } callback(); }; logger.add(sentryTransport); } {{/if}} // Función para crear loggers específicos por módulo export function createModuleLogger(module: string): winston.Logger { return logger.child({ module }); } // Función para log de performance export function logPerformance( operation: string, duration: number, metadata?: any ): void { logger.info('Performance metric', { type: 'performance', operation, duration, ...metadata, }); } // Función para log de auditoría export function logAudit( action: string, userId?: string, resource?: string, metadata?: any ): void { logger.info('Audit log', { type: 'audit', action, userId, resource, timestamp: new Date().toISOString(), ...metadata, }); } // Función para log de seguridad export function logSecurity( event: string, severity: 'low' | 'medium' | 'high' | 'critical', metadata?: any ): void { const level = severity === 'critical' || severity === 'high' ? 'error' : 'warn'; logger.log(level, 'Security event', { type: 'security', event, severity, timestamp: new Date().toISOString(), ...metadata, }); } // Función para log de requests HTTP export function logHttpRequest( method: string, url: string, statusCode: number, duration: number, userId?: string, metadata?: any ): void { logger.http('HTTP Request', { type: 'http', method, url, statusCode, duration, userId, timestamp: new Date().toISOString(), ...metadata, }); } // Configuración para desarrollo if (process.env.NODE_ENV === 'development') { logger.add( new winston.transports.Console({ level: 'debug', format: winston.format.combine( winston.format.colorize(), winston.format.simple() ), }) ); } // Manejo de excepciones no capturadas process.on('uncaughtException', (error) => { logger.error('Uncaught Exception', { error: error.message, stack: error.stack }); {{#if hasSentry}} Sentry.captureException(error); {{/if}} process.exit(1); }); process.on('unhandledRejection', (reason, promise) => { logger.error('Unhandled Rejection', { reason, promise }); {{#if hasSentry}} Sentry.captureException(new Error(`Unhandled Rejection: ${reason}`)); {{/if}} }); export default logger; export { logger, customLevels, customFormat, consoleFormat, };