2025-11-04 21:51:05 +00:00
|
|
|
|
const fs = require('fs');
|
|
|
|
|
|
const path = require('path');
|
|
|
|
|
|
// Создать директорию для логов если её нет
|
|
|
|
|
|
const logsDir = path.join(__dirname, '../logs');
|
|
|
|
|
|
if (!fs.existsSync(logsDir)) {
|
|
|
|
|
|
fs.mkdirSync(logsDir, { recursive: true });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-10 20:13:22 +00:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-21 01:14:56 +00:00
|
|
|
|
// Цвета для консоли (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: '🔒'
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-04 21:51:05 +00:00
|
|
|
|
// Функция для логирования
|
|
|
|
|
|
const log = (level, message, data = {}) => {
|
|
|
|
|
|
const timestamp = new Date().toISOString();
|
2025-11-21 01:14:56 +00:00
|
|
|
|
const emoji = levelEmojis[level] || '📋';
|
|
|
|
|
|
const logMessage = `[${timestamp}] ${emoji} [${level.toUpperCase()}] ${message}`;
|
|
|
|
|
|
const serializedData = Object.keys(data).length ? ` ${JSON.stringify(data, null, 2)}` : '';
|
2025-11-10 20:13:22 +00:00
|
|
|
|
const fullMessage = `${logMessage}${serializedData}`;
|
2025-11-04 21:51:05 +00:00
|
|
|
|
|
2025-11-21 01:14:56 +00:00
|
|
|
|
// Логирование в консоль с цветами
|
|
|
|
|
|
let colorCode = colors.reset;
|
2025-11-04 21:51:05 +00:00
|
|
|
|
if (level === 'error') {
|
2025-11-21 01:14:56 +00:00
|
|
|
|
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);
|
2025-11-04 21:51:05 +00:00
|
|
|
|
} else {
|
2025-11-21 01:14:56 +00:00
|
|
|
|
colorCode = colors.cyan;
|
|
|
|
|
|
console.log(`${colorCode}${logMessage}${colors.reset}`, data);
|
2025-11-04 21:51:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-10 20:13:22 +00:00
|
|
|
|
const fileName = `${level}-${getDatePrefix()}.log`;
|
|
|
|
|
|
appendLog(fileName, fullMessage);
|
2025-11-21 01:14:56 +00:00
|
|
|
|
|
|
|
|
|
|
// Также писать все логи в общий файл
|
|
|
|
|
|
appendLog(`all-${getDatePrefix()}.log`, fullMessage);
|
2025-11-04 21:51:05 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Middleware для логирования запросов
|
|
|
|
|
|
const requestLogger = (req, res, next) => {
|
|
|
|
|
|
const start = Date.now();
|
|
|
|
|
|
|
2025-11-21 01:14:56 +00:00
|
|
|
|
// Логировать входящий запрос (только для важных роутов)
|
|
|
|
|
|
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'
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-04 21:51:05 +00:00
|
|
|
|
// Логировать после завершения запроса
|
|
|
|
|
|
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,
|
2025-11-21 01:14:56 +00:00
|
|
|
|
userAgent: req.get('user-agent')?.substring(0, 100),
|
|
|
|
|
|
userId: req.user?.id || req.user?._id || 'anonymous'
|
2025-11-04 21:51:05 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-04 22:41:35 +00:00
|
|
|
|
// Пропустить логирование для публичных роутов (health, корневой роут)
|
2025-11-21 01:14:56 +00:00
|
|
|
|
if (req.path === '/health' || req.path === '/' || req.path === '/favicon.ico') {
|
2025-11-04 22:41:35 +00:00
|
|
|
|
// Логировать только ошибки для публичных роутов
|
|
|
|
|
|
if (res.statusCode >= 400) {
|
|
|
|
|
|
log('error', 'Request failed', logData);
|
|
|
|
|
|
}
|
|
|
|
|
|
return; // Не логировать успешные запросы к публичным роутам
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-21 01:14:56 +00:00
|
|
|
|
if (res.statusCode >= 500) {
|
|
|
|
|
|
log('error', 'Server error', logData);
|
|
|
|
|
|
} else if (res.statusCode >= 400) {
|
|
|
|
|
|
log('warn', 'Client error', logData);
|
2025-11-04 22:41:35 +00:00
|
|
|
|
} else if (res.statusCode >= 300 && res.statusCode !== 304) {
|
|
|
|
|
|
// 304 - это нормально (кеш), не логируем
|
2025-11-21 01:14:56 +00:00
|
|
|
|
log('info', 'Redirect', logData);
|
2025-11-04 21:51:05 +00:00
|
|
|
|
} else {
|
2025-11-21 01:14:56 +00:00
|
|
|
|
// Успешный запрос
|
|
|
|
|
|
if (duration > 1000) {
|
|
|
|
|
|
// Медленный запрос
|
|
|
|
|
|
log('warn', 'Slow request', { ...logData, slow: true });
|
|
|
|
|
|
} else {
|
|
|
|
|
|
log('info', 'Request completed', logData);
|
|
|
|
|
|
}
|
2025-11-04 21:51:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
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 можно отправить уведомление
|
2025-11-10 20:13:22 +00:00
|
|
|
|
const securityMessage = `[${new Date().toISOString()}] [SECURITY] ${type}: ${JSON.stringify(securityData)}`;
|
|
|
|
|
|
appendLog(`security-${getDatePrefix()}.log`, securityMessage);
|
2025-11-04 21:51:05 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-21 01:14:56 +00:00
|
|
|
|
// Дополнительные утилиты для логирования
|
|
|
|
|
|
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
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-11-04 21:51:05 +00:00
|
|
|
|
module.exports = {
|
|
|
|
|
|
log,
|
2025-11-21 01:14:56 +00:00
|
|
|
|
logError,
|
|
|
|
|
|
logSuccess,
|
|
|
|
|
|
logDebug,
|
|
|
|
|
|
logPerformance,
|
2025-11-04 21:51:05 +00:00
|
|
|
|
requestLogger,
|
|
|
|
|
|
logSecurityEvent
|
|
|
|
|
|
};
|
|
|
|
|
|
|