JS Tower
Node.jsМодуль 7: Тестирование и деплой

Логирование

Winston, Morgan, уровни логов

Цель урока

В этом уроке ты научишься:

  • Настраивать Winston
  • Логировать HTTP запросы с Morgan
  • Структурировать логи

Winston

Установка

npm install winston

Базовая настройка

const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
    new winston.transports.File({ filename: 'logs/combined.log' })
  ]
});

// В development добавляем консоль
if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.combine(
      winston.format.colorize(),
      winston.format.simple()
    )
  }));
}

module.exports = logger;

Использование

const logger = require('./logger');

logger.error('Ошибка!', { error: err.message });
logger.warn('Предупреждение');
logger.info('Информация', { userId: 123 });
logger.debug('Отладка');

Уровни логов

УровеньПриоритетИспользование
error0Ошибки
warn1Предупреждения
info2Важная информация
http3HTTP запросы
verbose4Подробности
debug5Отладка
silly6Всё подряд

Morgan (HTTP логи)

Установка

npm install morgan

Интеграция с Winston

const morgan = require('morgan');
const logger = require('./logger');

// Создаём stream для Winston
const stream = {
  write: (message) => logger.http(message.trim())
};

// Middleware
app.use(morgan('combined', { stream }));

// Или кастомный формат
app.use(morgan(':method :url :status :response-time ms', { stream }));

Продвинутая конфигурация

const winston = require('winston');
const path = require('path');

const logDir = 'logs';

const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.combine(
    winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  defaultMeta: { service: 'my-api' },
  transports: [
    // Ошибки в отдельный файл
    new winston.transports.File({
      filename: path.join(logDir, 'error.log'),
      level: 'error',
      maxsize: 5242880,  // 5MB
      maxFiles: 5
    }),
    // Все логи
    new winston.transports.File({
      filename: path.join(logDir, 'combined.log'),
      maxsize: 5242880,
      maxFiles: 5
    })
  ],
  exceptionHandlers: [
    new winston.transports.File({
      filename: path.join(logDir, 'exceptions.log')
    })
  ],
  rejectionHandlers: [
    new winston.transports.File({
      filename: path.join(logDir, 'rejections.log')
    })
  ]
});

if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.combine(
      winston.format.colorize(),
      winston.format.printf(({ level, message, timestamp, ...meta }) => {
        return `${timestamp} [${level}]: ${message} ${
          Object.keys(meta).length ? JSON.stringify(meta) : ''
        }`;
      })
    )
  }));
}

module.exports = logger;

Логирование в middleware

// Middleware для логирования запросов
function requestLogger(req, res, next) {
  const start = Date.now();
  
  res.on('finish', () => {
    const duration = Date.now() - start;
    
    logger.http('Request', {
      method: req.method,
      url: req.originalUrl,
      status: res.statusCode,
      duration: `${duration}ms`,
      ip: req.ip,
      userAgent: req.get('user-agent')
    });
  });
  
  next();
}

app.use(requestLogger);

Логирование ошибок

// Error handler с логированием
app.use((err, req, res, next) => {
  logger.error('Error', {
    message: err.message,
    stack: err.stack,
    path: req.path,
    method: req.method,
    body: req.body,
    user: req.user?.id
  });
  
  res.status(err.status || 500).json({
    error: process.env.NODE_ENV === 'production' 
      ? 'Internal Server Error' 
      : err.message
  });
});

Практика

Задание: Логгер для API

Задача: Настрой логирование для Express API.

Решение:

// logger.js
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'logs/app.log' })
  ]
});

if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.simple()
  }));
}

module.exports = logger;

// app.js
const logger = require('./logger');
const morgan = require('morgan');

app.use(morgan('combined', {
  stream: { write: (msg) => logger.http(msg.trim()) }
}));

Проверь себя

  1. Какие уровни логов существуют?
  2. Зачем нужен Morgan?
  3. Как логировать ошибки?