Логирование в JavaScript приложениях: лучшие практики на практике
Логирование — это не просто запись сообщений в консоль. В production-среде грамотно настроенное логирование становится главным инструментом мониторинга, отладки и аудита. Без него вы работаете вслепую. В этой статье мы разберем лучшие практики логирования для JavaScript-приложений на Node.js, от выбора библиотеки до настройки ротации файлов. Все примеры кода готовы к использованию.
1. Почему console.log — это плохо?
На этапе разработки мы все используем console.log. Это быстро и удобно. Но в продакшене такой подход опасен:
- Нет уровней логирования. Вы не сможете отфильтровать ошибки от информационных сообщений.
- Синхронная блокировка. Стандартный
console.logможет замедлить приложение при большом потоке запросов. - Отсутствие структуры. JSON-логи невозможно парсить без дополнительных костылей.
- Нет ротации. Лог-файл будет расти до бесконечности, пока не заполнит диск.
Вместо этого используйте профессиональные библиотеки. Две самые популярные в Node.js — Winston и Pino.
2. Выбор библиотеки: Winston vs Pino
Обе библиотеки решают одну задачу, но имеют разные приоритеты.
Winston — универсальный и гибкий
Winston — это «швейцарский нож» логирования. Он поддерживает множество транспортов (файлы, консоль, HTTP, базы данных) и легко настраивается.
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' }),
],
});
// В разработке добавляем вывод в консоль
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple(),
}));
}
logger.info('Сервер запущен на порту 3000');
logger.error('Ошибка подключения к БД', { db: 'postgres' });
Pino — скорость прежде всего
Pino позиционируется как самый быстрый логгер для Node.js. Он на 5-6x быстрее Winston благодаря использованию sonic-boom (асинхронная запись). Идеален для high-load проектов.
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({ err: new Error('Timeout') }, 'Запрос превысил лимит');
Совет: Для микросервисов и API с высокой нагрузкой выбирайте Pino. Для сложных конфигураций с несколькими транспортами — Winston.
3. Уровни логирования: не пишите всё в info
Используйте стандартную шкалу уровней (по возрастанию важности):
trace/debug— только для разработки. Детальная отладка.info— нормальная работа приложения: запуск, успешные запросы, бизнес-события.warn— потенциальные проблемы: медленные запросы, нехватка памяти, устаревшие методы.error— исключения, сбои, падения. Должны быть всегда в отдельном файле или системе мониторинга.fatal— критические ошибки, после которых приложение не может продолжить работу.
Настройте переменную окружения LOG_LEVEL, чтобы менять уровень фильтрации без перезапуска кода:
// Пример с Winston
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
// ...
});
// В production: LOG_LEVEL=warn
// В development: LOG_LEVEL=debug
4. Структурированные логи: JSON вместо текста
Текстовые логи вида [INFO] User logged in — это прошлый век. Современные системы (ELK, Splunk, Grafana) ожидают JSON. Структурированные логи позволяют фильтровать по полям, строить дашборды и искать ошибки за секунды.
// Плохо
logger.info('Payment processed for user ' + userId + ' amount ' + amount);
// Хорошо
logger.info({
event: 'payment.processed',
userId: userId,
amount: amount,
currency: 'USD',
timestamp: new Date().toISOString()
}, 'Payment processed');
Добавляйте обязательные поля в каждый лог: requestId (для трейсинга), serviceName, environment. Это спасет при дебаге распределенных систем.
5. Ротация логов и управление дисковым пространством
Если вы пишете логи в файлы, без ротации они займут весь диск. Используйте модуль winston-daily-rotate-file для Winston или встроенные возможности Pino.
// Winston + ротация
const DailyRotateFile = require('winston-daily-rotate-file');
const transport = new DailyRotateFile({
filename: 'logs/application-%DATE%.log',
datePattern: 'YYYY-MM-DD',
maxSize: '20m', // Максимальный размер файла
maxFiles: '14d', // Хранить логи 14 дней
zippedArchive: true // Сжимать старые файлы
});
logger.add(transport);
Для Pino используйте pino-roll или настройте ротацию через систему (logrotate в Linux).
6. Безопасность: никогда не логируйте секреты
Пароли, токены, номера кредитных карт, персональные данные — всё это не должно попадать в логи. Один случайный console.log(req.body) может привести к утечке данных и штрафам по GDPR.
Используйте редикторы (redactors) — функции, которые заменяют чувствительные поля на звездочки:
// Пример для Pino
const logger = pino({
redact: {
paths: ['password', 'token', 'creditCard