Логирование в JavaScript приложениях: лучшие практики для Node.js
Логирование — это не просто запись сообщений в консоль. В современных Node.js приложениях это критически важный инструмент для отладки, мониторинга и аудита. Правильно настроенное логирование позволяет быстро находить ошибки, анализировать поведение пользователей и обеспечивать стабильность работы сервиса. В этой статье мы разберем лучшие практики логирования в JavaScript (Node.js), от выбора библиотеки до настройки централизованного сбора логов.
1. Выбор библиотеки для логирования
Встроенный console.log хорош только для разработки. Для production-среды необходимы специализированные библиотеки, которые предоставляют уровни логирования, форматирование, ротацию файлов и интеграцию с внешними системами.
Рассмотрим три популярных варианта:
- Winston — самая популярная библиотека. Гибкая, с поддержкой множества транспортов (файлы, консоль, HTTP, базы данных). Идеальна для сложных систем.
- Pino — сверхбыстрый логгер. Рекомендуется для высоконагруженных приложений, где производительность критична.
- Bunyan — генерирует логи в формате JSON. Удобен для машинной обработки.
Пример настройки Winston:
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
defaultMeta: { service: 'user-service' },
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('Сервис запущен');
logger.error('Ошибка подключения к БД', { db: 'main' });
2. Использование уровней логирования
Уровни логирования помогают фильтровать сообщения по важности. Стандартная иерархия (от наименее к наиболее критичному):
- debug — детальная отладочная информация (только для разработки).
- info — стандартные события (запрос обработан, пользователь авторизован).
- warn — потенциальные проблемы (низкий уровень памяти, медленный запрос).
- error — критические ошибки (падение модуля, исключение).
Правило: В production используйте уровень info или выше. debug должен быть отключен, чтобы не забивать диски и не снижать производительность.
Пример динамической смены уровня:
const logger = require('./logger');
if (process.env.LOG_LEVEL) {
logger.level = process.env.LOG_LEVEL;
}
logger.debug('Это сообщение не появится в production');
logger.info('Пользователь залогинился');
logger.error('Не удалось сохранить данные');
3. Структурированные логи (JSON)
Текстовые логи сложно парсить. Современная практика — использовать структурированные логи в формате JSON. Это позволяет легко интегрироваться с системами анализа (ELK, Splunk, Grafana Loki).
Что должно быть в каждом логе:
- timestamp — точное время события (ISO 8601).
- level — уровень логирования.
- message — человекочитаемое описание.
- context — дополнительные данные (ID запроса, имя пользователя, код ошибки).
Пример с Pino:
const pino = require('pino');
const logger = pino({
level: 'info',
timestamp: pino.stdTimeFunctions.isoTime,
formatters: {
level(label) {
return { level: label };
},
},
});
logger.info({ userId: 123, action: 'login' }, 'Пользователь вошел в систему');
// Вывод: {"level":30,"time":"2024-01-15T10:30:00.000Z","pid":1234,"hostname":"server1","userId":123,"action":"login","msg":"Пользователь вошел в систему"}
4. Централизованный сбор и ротация логов
В production-среде логи должны собираться в одном месте. Это упрощает мониторинг и поиск ошибок. Основные подходы:
- Ротация файлов — библиотека
winston-daily-rotate-fileавтоматически архивирует старые логи. - Отправка в stdout — контейнеризированные приложения (Docker) должны писать логи в stdout/stderr, а система оркестрации (Kubernetes) сама собирает их.
- Внешние сервисы — Logstash, Datadog, Sentry. Winston и Pino имеют транспорты для прямой отправки.
Пример ротации файлов с Winston:
const winston = require('winston');
require('winston-daily-rotate-file');
const transport = new winston.transports.DailyRotateFile({
filename: 'logs/application-%DATE%.log',
datePattern: 'YYYY-MM-DD',
maxSize: '20m',
maxFiles: '14d',
});
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [transport],
});
logger.info('Лог с ротацией');
5. Логирование HTTP запросов
Для веб-приложений критично логировать входящие запросы. Популярные middleware:
- morgan — простой и быстрый логгер для Express. Работает в формате Apache.
- pino-http — интеграция Pino с HTTP-запросами.
Пример с morgan и Winston:
const express = require('express');
const morgan = require('morgan');
const winston = require('winston');
const app = express();
const logger = winston.createLogger({
transports: [new winston.transports.Console()],
});
// Создаем поток для morgan в Winston
const stream = {
write: (message) => logger.info(message.trim()),
};
app.use(morgan('combined', { stream }));
app.get('/', (req, res) => {
res.send('Hello World');
});
app.listen(3000);
6. Чего следует избегать
Ошибки, которые часто допускают разработчики: