Логирование в JavaScript приложениях: лучшие практики для начинающих
Логирование — это основа отладки и мониторинга любого приложения. Без качественных логов вы будете «иголку в стоге сена» искать, когда что-то пойдет не так. В этой статье мы разберем лучшие практики логирования в JavaScript (Node.js), подходящие как для небольших проектов, так и для продакшена.
1. Почему не стоит полагаться на console.log?
Многие начинающие разработчики используют console.log для всего. Это удобно на этапе разработки, но в production-среде такой подход приводит к проблемам:
- Нет уровней логирования — вы не можете отфильтровать ошибки от информационных сообщений.
- Сложно управлять выводом — нельзя отключить логи для определенных модулей.
- Плохая производительность — сериализация объектов в
console.logможет быть затратной. - Отсутствие структуры — логи не имеют единого формата (JSON, время, уровень).
Вместо этого используйте специализированные библиотеки. Вот простой, но правильный пример с использованием Winston:
// logger.js
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(),
}));
}
module.exports = logger;
2. Уровни логирования: когда что использовать
Стандартные уровни логирования (по возрастанию серьезности): debug, info, warn, error. Важно понимать, когда какой уровень применять:
- debug — детальная информация для разработчиков (значения переменных, состояние системы). Отключается в production.
- info — обычные события работы приложения (запрос обработан, пользователь зарегистрирован).
- warn — потенциально опасные ситуации (низкий запас памяти, устаревший API).
- error — ошибки, которые требуют вмешательства (исключения, сбои БД).
Пример правильного использования с библиотекой Pino (самая быстрая для Node.js):
const pino = require('pino');
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
transport: {
target: 'pino-pretty',
options: { colorize: true }
}
});
// Использование
logger.info({ userId: 123 }, 'Пользователь вошел в систему');
logger.error(new Error('Ошибка подключения к БД'), 'Критическая ошибка');
logger.debug('Значение переменной x = 42'); // не выведется, если level = 'info'
3. Структурированное логирование: JSON и контекст
Вместо текстовых строк используйте структурированные логи (обычно JSON). Это позволяет:
- Легко парсить логи инструментами (ELK, Graylog).
- Добавлять контекст (ID запроса, пользователь, версия).
- Фильтровать и агрегировать данные.
Пример структурированного лога с контекстом:
const logger = require('./logger');
app.use((req, res, next) => {
req.requestId = uuidv4();
next();
});
app.get('/api/users', async (req, res) => {
logger.info({
requestId: req.requestId,
method: req.method,
url: req.originalUrl,
ip: req.ip
}, 'Входящий запрос');
try {
const users = await getUsers();
logger.info({ requestId: req.requestId, count: users.length }, 'Пользователи получены');
res.json(users);
} catch (err) {
logger.error({
requestId: req.requestId,
error: err.message,
stack: err.stack
}, 'Ошибка получения пользователей');
res.status(500).send('Internal Server Error');
}
});
4. Лучшие практики для production
При развертывании приложения в production соблюдайте следующие правила:
- Не логируйте пароли и секреты — используйте санитайзеры для удаления чувствительных данных.
- Ротация логов — используйте
winston-daily-rotate-fileили средства системы (logrotate). - Асинхронное логирование — библиотеки вроде Pino работают асинхронно, не блокируя event loop.
- Централизованный сбор — отправляйте логи в ELK (Elasticsearch, Logstash, Kibana) или Sentry для ошибок.
- Логирование HTTP-запросов — используйте
morganдля Express:
const morgan = require('morgan');
const express = require('express');
const app = express();
// Формат combined для production
app.use(morgan('combined', {
skip: (req, res) => res.statusCode < 400 // логировать только ошибки
}));
5. Пример: настройка Winston для Express
Соберем все вместе — полный пример настройки логирования для Express-приложения с Winston:
const winston = require('winston');
const express = require('express');
const app = express();
// 1. Создаем логгер
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
],
});
// 2. Добавляем форматированный вывод в консоль для разработки
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
}));
}
// 3. Middleware для логирования запросов
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
logger.info({
method: req.method,
url: req.originalUrl,
status: res.statusCode,
duration: Date.now() - start,
});
});
next();
});
// 4. Пример обработчика
app.get('/error', (req