Логирование в Node.js: от console.log к нормальным логам
Содержание
О проверке кода. Примеры не запускались в этой сессии (по договорённости — фокус на контенте, а не прогонах). Говорим прямо, а не ставим бейдж «проверено».
Логи — глаза приложения в продакшене: когда что-то ломается, только они говорят, что произошло. console.log для этого не годится. Разберём, как логировать по-взрослому в Node.js.
Почему console.log не для продакшена
console.log('пользователь вошёл'); // а какой? когда? насколько это важно?
console.log неплох для отладки на своей машине, но в продакшене у него серьёзные изъяны:
- нет уровней важности — ошибка и рутинное событие в одной куче, важное тонет;
- нет структуры — произвольный текст, по которому не поиск, ни фильтрация;
- не пишет в файлы и системы сбора логов — только в консоль;
- синхронный — под нагрузкой запись в консоль блокирует event loop, замедляя сервер.
Для продакшена берут библиотеку логирования. Но сначала — принципы, они важнее инструмента.
Уровни логирования
logger.error('платёж не прошёл', { orderId, error }); // критично, требует внимания
logger.warn('медленный ответ БД', { ms: 1500 }); // подозрительно
logger.info('пользователь вошёл', { userId }); // важное событие
logger.debug('промежуточное значение', { data }); // детали для отладки
Всегда в проде
info и выше
error— сбой, нужно вниманиеwarn— подозрительное, но не сбойinfo— ключевые события (вход, заказ)- Фильтр: показывать info и выше
Только при отладке
debug/trace
debug— детали для диагностикиtrace— совсем подробно- В проде отключены — иначе шум и объём
- Включают по NODE_ENV или флагу
Уровни задают важность и позволяют фильтровать. В продакшене показывают info и выше, а debug держат выключенным — иначе логи распухают и важное теряется. При диагностике проблемы debug включают через переменную окружения. Это ровно то, чего лишён плоский console.log.
Структурное логирование
Ключевая идея для серьёзного проекта — логи как данные, а не текст:
// ❌ текстовый лог — не поискать, не отфильтровать
console.log(`Пользователь ${userId} купил ${item} за ${price}`);
// ✅ структурный лог — JSON с полями
logger.info('покупка', { userId, item, price, requestId });
// {"level":"info","msg":"покупка","userId":42,"item":"книга","price":500,"requestId":"abc"}
Структурные логи — это JSON с полями, а не произвольная строка. Их преимущество раскрывается в системах сбора логов (Elasticsearch, Loki, облачные сервисы): можно искать «все события userId=42», фильтровать по requestId, строить графики по полям. С текстовым логом такое невозможно — только грубый поиск по строке. Поле requestId, общее для всех логов одного запроса, позволяет проследить весь путь запроса через сервис.
Библиотеки: pino и winston
// pino — самый быстрый, структурный JSON из коробки
const pino = require('pino');
const logger = pino();
logger.info({ userId: 42 }, 'пользователь вошёл');
logger.error({ err }, 'ошибка обработки');
pino— самая быстрая библиотека, пишет структурные JSON-логи с минимальными накладными расходами (важно: логирование не должно тормозить сервер). Оптимальный выбор для большинства новых проектов;winston— гибче в настройке: несколько назначений вывода (файл, консоль, внешний сервис), форматы, ротация. Берут, когда нужна сложная маршрутизация логов.
Обе решают всё, чего не может console.log: уровни, структура, асинхронная запись, вывод куда угодно. Правило прежнее: инструмент под задачу — для старта достаточно pino с настройками по умолчанию.
Что НЕЛЬЗЯ логировать
// ❌ КАТАСТРОФА — секреты и персданные в логах
logger.info('вход', { email, password }); // пароль в логах!
logger.info('оплата', { cardNumber, cvv }); // данные карты!
// ✅ маскируем или исключаем
logger.info('вход', { email }); // без пароля
logger.info('оплата', { cardLast4: '1234' }); // только последние цифры
Логи — не место для секретов. Они хранятся, просматриваются многими людьми и уходят в системы сбора, где живут долго. Пароль, токен, номер карты, персональные данные в логах — это утечка, иногда с юридическими последствиями (GDPR, 152-ФЗ). Чувствительные поля маскируют (последние 4 цифры) или исключают перед записью. pino умеет автоматически вырезать заданные поля (redaction).
Что стоит логировать
- входящие запросы — метод, путь, статус ответа, время обработки,
requestId; - ошибки — с полным стеком и контекстом (что за операция, какие данные);
- ключевые бизнес-события — вход, регистрация, оплата, важное действие;
- проблемы производительности — медленные запросы к БД, долгие внешние вызовы;
- старт и остановку сервиса, подключение к БД.
Хороший лог отвечает на вопрос «что происходило перед сбоем» без необходимости воспроизводить проблему. Плохой — либо пуст в нужный момент, либо утонул в мусоре из-за отсутствия уровней.
Смежные темы: переменные окружения — уровень логов по NODE_ENV; обработка ошибок — что логировать при сбое; отладка — логи против отладчика. Полный список — в уроках Node.js.