Логирование: 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