Логирование: Winston, Pino
Winston и Pino — популярные Node.js библиотеки логирования, заменяющие console.log: они поддерживают уровни логов, форматирование (JSON/pretty), запись в файлы и интеграцию с мониторинговыми системами.
Зачем нужно
console.log в production — антипаттерн: нет уровней важности, нет структурированного вывода, нельзя фильтровать по severity. Structured logging (JSON-логи) позволяет агрегировать и искать логи в ELK Stack, Grafana Loki, Datadog. Уровни (debug, info, warn, error) помогают настроить verbosity для разных окружений.
Где используется
- HTTP-запросы и ответы (access log)
- Ошибки приложения (error log)
- Бизнес-события (создание заказа, платёж)
- Отладочная информация в development
- Интеграция с Sentry, Datadog, Grafana Loki
Основной контент
Winston — гибкая настройка
npm install winston
// logger/winston.js
const winston = require('winston');
const path = require('path');
const { combine, timestamp, json, errors, colorize, simple } = winston.format;
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: combine(
errors({ stack: true }), // логировать stack trace ошибок
timestamp,
json
),
transports: [
// В production — пишем JSON в файлы
new winston.transports.File({
filename: path.join('logs', 'error.log'),
level: 'error'
}),
new winston.transports.File({
filename: path.join('logs', 'combined.log')
})
]
});
// В development — красивый вывод в консоль
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: combine(colorize, simple)
}));
}
module.exports = logger;
// Использование
const logger = require('./logger/winston');
logger.info('Server started', { port: 3000 });
logger.warn('Rate limit exceeded', { ip: '192.168.1.1', endpoint: '/api/login' });
logger.error('Database connection failed', { error: err.message, stack: err.stack });
// Создание child logger с контекстом
const requestLogger = logger.child({ requestId: req.id, userId: req.user?.id });
requestLogger.info('Processing payment', { orderId: 42 });
Winston middleware для Express
// middleware/httpLogger.js
const morgan = require('morgan');
const logger = require('../logger/winston');
const stream = {
write: (message) => logger.http(message.trim())
};
module.exports = morgan(':method :url :status :response-time ms', { stream });
Pino — максимальная производительность
npm install pino pino-pretty # pino-pretty для dev
// logger/pino.js
const pino = require('pino');
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
// В production — JSON вывод
...(process.env.NODE_ENV === 'production'
? {}
: {
transport: {
target: 'pino-pretty',
options: { colorize: true }
}
}
)
});
module.exports = logger;
// Pino с Express (pino-http)
// npm install pino-http
const pinoHttp = require('pino-http');
const logger = require('./logger/pino');
app.use(pinoHttp({ logger }));
app.get('/api/users', (req, res) => {
req.log.info({ userId: 1 }, 'Fetching user'); // child logger с request context
res.json();
});
Уровни логирования
error — ошибки требующие немедленного внимания
warn — потенциальные проблемы, нештатные ситуации
info — нормальные события: запуск, запросы, бизнес-события
http — HTTP-запросы (access log)
verbose — подробная информация о работе
debug — отладочные данные, значения переменных
silly — максимальная детализация
# Установить уровень через env
LOG_LEVEL=debug node app.js
# В production — только error и warn
LOG_LEVEL=warn node app.js
Частые ошибки
console.logв production — нет уровней, нет структуры, нельзя фильтровать- Логировать sensitive данные — никогда не логировать пароли, токены, номера карт; маскировать через
req.body.password = '[REDACTED]' - Синхронная запись в файл — блокирует Event Loop; Winston и Pino пишут асинхронно
- Не ротировать логи — логи растут бесконечно; использовать
winston-daily-rotate-fileили logrotate
Связанные темы
Ресурсы
🎓 Источник: Логирование на Node.js
- 📅 2019-04-25 · YouTube · [Marp](../../Documents/TimurShemsedinov/2019-04-25 — Логирование на Node.js и JavaScript (4DkZj2Cdokc).md)
- Тезисы:
- Несколько источников и приёмников: один логер пишет одновременно в stdout, файл, удалённый стрим
- Цвета только для TTY (
process.stdout.isTTY) — иначе мусор в файле - Стрим открывается один раз, флаг закеширован — никаких
fs.appendFileна каждую запись - Buffer определяет производительность: чем больше buffer — тем меньше syscalls, но больше потерь при крахе
- Сброс по таймауту (
flush every 1s) — компромисс между производительностью и потерей - Ротация по часу, дню или размеру + архивирование старых
- Не писать в один файл из двух процессов (cluster) — гонки
- При crash процесса буфер может не успеть сброситься → потеря последних логов
- Многострочный stack trace в одну строку для удобного grep
- Обрезка длинных путей в стек-трейсах (
node_modules→~) - Перехват
console.logчерез VM-песочницу — прозрачно подменить - MetaLog (Metarhia): 7.5 KB, события
onOpen/onClose/onRotate, тест на 60 MB
- Цитата: «Структурные JSON-логи не люблю — TSV парсится не хуже, читается человеком, меньше места»
🎓 Источник: Декомпозиция логера (Летняя школа 2022 #19)
- 📅 2022-08-24 · YouTube
- Тезисы: разбор MetaLog по слоям: writer, formatter, rotation, transports
🎓 Источник: Logger в Летней школе 2022 #20
- 📅 2022-08-25 · YouTube
- Тезисы: доработка Logger, fetch polyfill, расширение EventEmitter