const fs = require('fs'); const path = require('path'); // Создать директорию для логов если её нет const logsDir = path.join(__dirname, '../logs'); if (!fs.existsSync(logsDir)) { fs.mkdirSync(logsDir, { recursive: true }); } const getDatePrefix = () => { return new Date().toISOString().slice(0, 10); }; const appendLog = (fileName, message) => { const filePath = path.join(logsDir, fileName); fs.appendFile(filePath, `${message}\n`, (err) => { if (err) { console.error('Ошибка записи в лог файл:', err); } }); }; // Цвета для консоли (ANSI коды) const colors = { reset: '\x1b[0m', bright: '\x1b[1m', dim: '\x1b[2m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m', white: '\x1b[37m' }; // Эмодзи для уровней const levelEmojis = { debug: '🔍', info: '📝', success: '✅', warn: '⚠️', error: '❌', security: '🔒' }; // Функция для логирования const log = (level, message, data = {}) => { const timestamp = new Date().toISOString(); const emoji = levelEmojis[level] || '📋'; const logMessage = `[${timestamp}] ${emoji} [${level.toUpperCase()}] ${message}`; const serializedData = Object.keys(data).length ? ` ${JSON.stringify(data, null, 2)}` : ''; const fullMessage = `${logMessage}${serializedData}`; // Логирование в консоль с цветами let colorCode = colors.reset; if (level === 'error') { colorCode = colors.red; console.error(`${colors.bright}${colorCode}${logMessage}${colors.reset}`, data); } else if (level === 'warn' || level === 'security') { colorCode = colors.yellow; console.warn(`${colors.bright}${colorCode}${logMessage}${colors.reset}`, data); } else if (level === 'success') { colorCode = colors.green; console.log(`${colors.bright}${colorCode}${logMessage}${colors.reset}`, data); } else if (level === 'debug') { colorCode = colors.dim; console.log(`${colorCode}${logMessage}${colors.reset}`, data); } else { colorCode = colors.cyan; console.log(`${colorCode}${logMessage}${colors.reset}`, data); } const fileName = `${level}-${getDatePrefix()}.log`; appendLog(fileName, fullMessage); // Также писать все логи в общий файл appendLog(`all-${getDatePrefix()}.log`, fullMessage); }; // Middleware для логирования запросов const requestLogger = (req, res, next) => { const start = Date.now(); // Логировать входящий запрос (только для важных роутов) if (req.path.startsWith('/api/') && !req.path.includes('/health')) { log('debug', 'Incoming request', { method: req.method, path: req.path, query: req.query, body: req.method === 'POST' || req.method === 'PUT' ? (req.body && Object.keys(req.body).length > 0 ? { ...req.body, password: req.body.password ? '***' : undefined } : 'empty') : undefined, ip: req.ip, userId: req.user?.id || req.user?._id || 'anonymous' }); } // Логировать после завершения запроса res.on('finish', () => { const duration = Date.now() - start; const logData = { method: req.method, path: req.path, status: res.statusCode, duration: `${duration}ms`, ip: req.ip, userAgent: req.get('user-agent')?.substring(0, 100), userId: req.user?.id || req.user?._id || 'anonymous' }; // Пропустить логирование для публичных роутов (health, корневой роут) if (req.path === '/health' || req.path === '/' || req.path === '/favicon.ico') { // Логировать только ошибки для публичных роутов if (res.statusCode >= 400) { log('error', 'Request failed', logData); } return; // Не логировать успешные запросы к публичным роутам } if (res.statusCode >= 500) { log('error', 'Server error', logData); } else if (res.statusCode >= 400) { log('warn', 'Client error', logData); } else if (res.statusCode >= 300 && res.statusCode !== 304) { // 304 - это нормально (кеш), не логируем log('info', 'Redirect', logData); } else { // Успешный запрос if (duration > 1000) { // Медленный запрос log('warn', 'Slow request', { ...logData, slow: true }); } else { log('info', 'Request completed', logData); } } }); next(); }; // Логирование подозрительной активности const logSecurityEvent = (type, req, details = {}) => { const securityData = { type, ip: req.ip, userAgent: req.get('user-agent'), path: req.path, method: req.method, userId: req.user?.id || 'anonymous', ...details }; log('warn', 'Security event', securityData); // В production можно отправить уведомление const securityMessage = `[${new Date().toISOString()}] [SECURITY] ${type}: ${JSON.stringify(securityData)}`; appendLog(`security-${getDatePrefix()}.log`, securityMessage); }; // Дополнительные утилиты для логирования const logError = (context, error, additionalData = {}) => { log('error', `Error in ${context}`, { error: error.message, stack: error.stack, name: error.name, ...additionalData }); }; const logSuccess = (message, data = {}) => { log('success', message, data); }; const logDebug = (message, data = {}) => { // Логировать debug только в development if (process.env.NODE_ENV === 'development') { log('debug', message, data); } }; // Логирование производительности const logPerformance = (operation, duration, data = {}) => { const level = duration > 1000 ? 'warn' : 'info'; log(level, `Performance: ${operation}`, { duration: `${duration}ms`, ...data }); }; module.exports = { log, logError, logSuccess, logDebug, logPerformance, requestLogger, logSecurityEvent };