24. Логирование (pino, winston)

Логирование — глаза и уши твоего сервера. Без логов в production ты слепой.
console.log — почему недостаточно
Заголовок раздела «console.log — почему недостаточно»// ❌ console.log в productionconsole.log('Пользователь зарегистрировался'); // нет даты, нет уровняconsole.log('Error:', error); // нет контекстаconsole.log(`User ${id} logged in`); // нет структуры
// ✅ Правильное логированиеlogger.info({ userId: 42, action: 'register' }, 'Пользователь зарегистрирован');logger.error({ err, userId: 42 }, 'Ошибка при обновлении профиля');Pino — быстрый JSON логгер
Заголовок раздела «Pino — быстрый JSON логгер»npm install pino pino-prettyconst pino = require('pino');
// Создание логгераconst logger = pino({ level: process.env.LOG_LEVEL || 'info',
// Красивый вывод в dev, JSON в production transport: process.env.NODE_ENV !== 'production' ? { target: 'pino-pretty', options: { colorize: true } } : undefined,});
// Уровни логированияlogger.trace('Очень подробно'); // 10logger.debug('Для отладки'); // 20logger.info('Информация'); // 30 (по умолчанию)logger.warn('Предупреждение'); // 40logger.error('Ошибка'); // 50logger.fatal('Критичная ошибка'); // 60
// Структурированные логи (ключевое преимущество)// {"level":30,"time":1705000000,"userId":42,"email":"[email protected]","msg":"Регистрация"}
// Логирование ошибокtry { await riskyOperation();} catch (err) { logger.error({ err }, 'Ошибка операции'); // Pino автоматически сериализует Error (message, stack, type)}Pino + Express
Заголовок раздела «Pino + Express»const pinoHttp = require('pino-http');
// Middleware для автоматического логирования запросовapp.use(pinoHttp({ logger,
// Кастомные поля customProps: (req) => ({ userId: req.user?.id, }),
// Уровень по статус-коду customLogLevel: (req, res, err) => { if (res.statusCode >= 500 || err) return 'error'; if (res.statusCode >= 400) return 'warn'; return 'info'; },
// Что логировать serializers: { req: (req) => ({ method: req.method, url: req.url, // НЕ логируем заголовки (могут содержать токены!) }), res: (res) => ({ statusCode: res.statusCode, }), },}));
// Использование в роутахapp.get('/users', (req, res) => { req.log.info('Запрос списка пользователей'); // req.log — child логгер с request ID res.json({ data: [] });});Winston — гибкий логгер
Заголовок раздела «Winston — гибкий логгер»npm install winstonconst winston = require('winston');const { combine, timestamp, json, printf, colorize } = winston.format;
const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info',
// JSON формат для production format: combine( timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), json() ),
// Транспорты — куда писать логи transports: [ // Файл для ошибок new winston.transports.File({ filename: 'logs/error.log', level: 'error', maxsize: 10 * 1024 * 1024, // 10 МБ maxFiles: 5, // ротация — 5 файлов }),
// Файл для всех логов new winston.transports.File({ filename: 'logs/combined.log', maxsize: 10 * 1024 * 1024, maxFiles: 10, }), ],});
// Красивый вывод в devif (process.env.NODE_ENV !== 'production') { logger.add(new winston.transports.Console({ format: combine( colorize(), timestamp({ format: 'HH:mm:ss' }), printf(({ timestamp, level, message, ...meta }) => { const metaStr = Object.keys(meta).length ? JSON.stringify(meta) : ''; return `${timestamp} ${level}: ${message} ${metaStr}`; }) ), }));}
// Использованиеlogger.info('Сервер запущен', { port: 3000 });logger.error('Ошибка БД', { error: err.message, query: 'SELECT ...' });Что и когда логировать
Заголовок раздела «Что и когда логировать»// ✅ ЛОГИРУЙlogger.info({ userId, action: 'login' }, 'Вход пользователя');logger.info({ orderId, total }, 'Заказ создан');logger.warn({ userId, attempts: 5 }, 'Много неудачных попыток входа');logger.error({ err, userId, requestId }, 'Ошибка обработки запроса');
// ❌ НЕ ЛОГИРУЙlogger.info({ password: '123' }); // НИКОГДА пароли!logger.info({ token: 'eyJhb...' }); // НИКОГДА токены!logger.info({ creditCard: '4111...' }); // НИКОГДА данные карт!logger.debug('Here 1'); // бесполезноlogger.info(`User: ${JSON.stringify(userObj)}`); // лучше: logger.info({ user })Request ID — отслеживание запроса
Заголовок раздела «Request ID — отслеживание запроса»const { v4: uuid } = require('uuid');
// Middleware добавляет уникальный ID каждому запросуapp.use((req, res, next) => { req.requestId = req.headers['x-request-id'] || uuid(); res.setHeader('X-Request-ID', req.requestId);
// Child логгер с requestId req.log = logger.child({ requestId: req.requestId }); next();});
// Теперь в любом месте обработки можно отследить запросapp.get('/users', async (req, res) => { req.log.info('Получение пользователей'); const users = await db.users.findMany(); req.log.info({ count: users.length }, 'Пользователи получены'); res.json({ data: users });});
// В логах:// {"requestId":"abc-123","msg":"Получение пользователей"}// {"requestId":"abc-123","count":42,"msg":"Пользователи получены"}Pino vs Winston
Заголовок раздела «Pino vs Winston»Pino Winston────────────────────────── ──────────────────────────~5x быстрее МедленнееJSON по умолчанию Гибкие форматыМинимальный API Богатый APIpino-pretty для dev Встроенное форматированиеpino-http для Express express-winstonМеньше зависимостей Больше фич (ротация, и т.д.)
Рекомендация:API / Микросервисы → Pino (скорость!)Традиционные приложения → Winston (гибкость)Практика
Заголовок раздела «Практика»- Настрой pino логгер с pino-pretty в dev и JSON в production
- Подключи pino-http для автоматического логирования запросов
- Добавь Request ID middleware для отслеживания запросов
- Настрой winston с ротацией файлов и разными уровнями
- Создай helper
sanitizeLog(obj), который маскирует чувствительные поля