Логирование в JavaScript приложениях: лучшие практики
Логирование — это не просто запись событий в файл. В современных Node.js приложениях это мощный инструмент мониторинга, отладки и аудита. Без качественного логирования найти причину падения продакшн-сервера становится игрой в угадайку. В этой статье мы разберем лучшие практики логирования в JavaScript (Node.js), которые помогут вам строить надежные и поддерживаемые системы.
Почему стандартный console.log — это плохая практика
Многие начинающие разработчики используют console.log для отладки. Однако в продакшене это приводит к проблемам:
- Отсутствие уровней: вы не можете разделить информационные сообщения и критические ошибки.
- Нет структуры: вывод в консоль — это просто строка, которую сложно парсить.
- Блокирующий ввод-вывод: синхронная запись может замедлить приложение.
- Отсутствие ротации: файлы логов будут расти бесконечно.
Вместо этого используйте специализированные библиотеки, такие как winston или pino. Они предоставляют гибкую настройку транспортов (вывод в файл, консоль, внешние сервисы) и асинхронную запись.
Выбор библиотеки для логирования: winston vs pino
На рынке Node.js есть два явных лидера. Рассмотрим их особенности:
Winston
Самая популярная библиотека. Поддерживает множество транспортов (файл, HTTP, MongoDB, Elasticsearch) и кастомные форматы. Отлично подходит для монолитных приложений.
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
new winston.transports.Console({ format: winston.format.simple() })
]
});
logger.info('Сервер запущен на порту 3000');
logger.error('Ошибка подключения к БД', { db: 'mongo', code: 500 });
Pino
Сверхбыстрая библиотека, которая в 5-10 раз быстрее winston. Идеальна для high-load систем и микросервисов. По умолчанию выводит JSON.
const pino = require('pino');
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
transport: {
target: 'pino/file',
options: { destination: './app.log' }
}
});
logger.info({ user: 'admin' }, 'Пользователь вошел в систему');
logger.error(new Error('Timeout'), 'Ошибка запроса');
Совет: для большинства проектов выбирайте winston из-за его гибкости. Если производительность критична — используйте pino.
Уровни логирования: как не утонуть в сообщениях
Правильное использование уровней — основа хорошего логирования. В Node.js принята следующая иерархия (от наименее к наиболее критичным):
- trace / silly — максимально детальная отладочная информация (обычно отключена в продакшене).
- debug — отладочные сообщения для разработчиков.
- info — подтверждение нормальной работы (запросы, запуск сервисов).
- warn — нештатная ситуация, но приложение продолжает работать (например, превышение лимита запросов).
- error — ошибка, требующая внимания (падение запроса, сбой внешнего сервиса).
- fatal — критическая ошибка, после которой приложение не может продолжать работу.
Настраивайте уровень логирования через переменные окружения:
const LOG_LEVEL = process.env.LOG_LEVEL || 'info';
const logger = winston.createLogger({ level: LOG_LEVEL });
В разработке ставьте debug, в продакшене — info или warn.
Структурированное логирование: JSON вместо строк
Строковые логи — это прошлый век. Современные системы (ELK, Grafana, DataDog) лучше всего работают с JSON-форматом. Преимущества:
- Машиночитаемость — легко парсить и фильтровать.
- Дополнительные поля — можно добавить ID запроса, пользователя, версию кода.
- Поддержка корреляции — связывание логов в распределенных системах.
Пример структурированного лога:
{
"level": "error",
"message": "Ошибка валидации",
"timestamp": "2025-03-15T10:30:00.000Z",
"requestId": "abc-123",
"userId": 42,
"error": {
"message": "Неверный email",
"code": "VALIDATION_ERR"
}
}
В winston это настраивается через формат:
const logger = winston.createLogger({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
)
});
Ротация логов и управление файлами
Если вы пишете логи в файлы, они будут расти бесконечно. Используйте ротацию, чтобы автоматически архивировать или удалять старые логи. Для winston есть пакет winston-daily-rotate-file:
const DailyRotateFile = require('winston-daily-rotate-file');
const transport = new DailyRotateFile({
filename: 'logs/application-%DATE%.log',
datePattern: 'YYYY-MM-DD',
maxSize: '20m',
maxFiles: '14d'
});
const logger = winston.createLogger({
transports: [transport]
});
Этот код создает новый файл каждый день, удаляет файлы старше 14 дней и ограничивает размер каждого файла 20 МБ.
Лучшие практики: чек-лист
Соберите все рекомендации в один список для быстрой проверки:
- Не логируйте секреты: пароли, токены, ключи API. Используйте фильтры или библиотеку
fast-redact. - Добавляйте контекст: ID запроса, пользователя, версию приложения. Это упрощает поиск проблем.
- Используйте асинхронную запись: чтобы не блоки