UNPKG

ready-to-go-node-template

Version:

A node boilerplate template with built-in support for socket and MongoDB using Mongoose.

134 lines (112 loc) 4.23 kB
import winston from 'winston'; import fs from 'fs'; import path from 'path'; import chokidar from 'chokidar'; const logDirectory = 'logs'; const logFileName = 'combined.log'; const logFilePath = path.resolve(process.cwd(), logDirectory, logFileName); class ConsoleLoggerRedirection { static stripAnsi(str) { if (typeof str === 'string') { return str.replace(/\u001B\[[0-9;]*[JKmsu]/g, ''); } return str; // Return as is if not a string } static stringifyIfObject(arg) { return typeof arg === 'object' ? JSON.stringify(arg, (key, value) => { return typeof value === 'bigint' ? Number(value) : value; }) : arg; } redirectConsoleLogsToWinston() { const maxFileSize = 1000000; // Maximum size of each log file in bytes const maxFiles = 5; // Maximum number of log files to keep const fileTransport = new winston.transports.File({ filename: logFilePath, maxFiles: maxFiles, maxsize: maxFileSize, tailable: true }); const logger = winston.createLogger({ level: 'silly', format: winston.format.combine( winston.format.timestamp(), winston.format.printf(info => `${info.timestamp} ${info.level}: ${info.message}`) ), transports: [fileTransport], }); const originalConsole = { ...console }; console.log = (...args) => { const cleanedArgs = args.map(arg => ConsoleLoggerRedirection.stripAnsi(ConsoleLoggerRedirection.stringifyIfObject(arg))); logger.info(cleanedArgs.join(' ')); originalConsole.log.apply(console, args); // Use apply to call the original function }; console.error = (...args) => { const cleanedArgs = args.map(arg => ConsoleLoggerRedirection.stripAnsi(ConsoleLoggerRedirection.stringifyIfObject(arg))); logger.error(cleanedArgs.join(' ')); originalConsole.error.apply(console, args); // Use apply to call the original function }; console.table = (...args) => { // Exclude console.table logs from being stored originalConsole.table.apply(console, args); // Call the original function without logging to winston }; } watchLogFile(io) { const logFilePath = path.resolve(process.cwd(), 'logs/combined.log'); if (!fs.existsSync(logFilePath)) { fs.writeFileSync(logFilePath, '', { flag: 'wx' }, (err) => { if (err) { console.error('Error creating log file:', err); } else { console.log('Log file created successfully.'); } }) } const watcher = chokidar.watch(logDirectory, { ignored: /^\./, persistent: true }); watcher .on('change', (filePath) => { if (filePath === 'logs/combined.log') { fs.readFile(logFilePath, 'utf8', async (err, rawData) => { if (err) { console.error(err); } else { const data = await processAndSortLogs(rawData); // Emit 'log-updated' event with the new log data io.emit('log-updated', { data }); } }); } }) .on('error', (error) => console.error('Error watching log file:', error)); } } export const consoleLoggerRedirection = new ConsoleLoggerRedirection(); export const processAndSortLogs = (data) => { let logs = data.split('\n') .filter(line => line.length > 0) .map(line => { try { const [timestamp, level, ...messageArr] = line.split(' '); const message = messageArr.join(' '); if (isValidJSON(message)) { const parsedMessage = JSON.parse(message); return { timestamp, level, message: parsedMessage }; } else { return { timestamp, level, message }; // Return the message as a string if it's not JSON } } catch (err) { return { error: 'Invalid log format', line }; // Return the raw line in case of error } }); logs = logs.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp)); // Return only the last 600 logs return logs.slice(-600); }; // Utility function to check if a string is valid JSON export const isValidJSON = (str) => { try { JSON.parse(str); return true; } catch (e) { return false; } };