Лучшие практики логирования ошибок в JavaScript
JavaScript-приложения в продакшене ежедневно падают незаметно. Пользователи сталкиваются с ошибками, которые разработчики никогда не видят, что приводит к плохому пользовательскому опыту и потере прибыли. В чём разница между приложениями, которые отслеживают эти проблемы, и теми, которые этого не делают? Правильное логирование ошибок.
Эта статья охватывает основные практики внедрения надёжного логирования ошибок JavaScript как во фронтенд-, так и в бэкенд-окружениях. Вы узнаете, как выйти за рамки console.log, внедрить структурированное логирование с проверенными фреймворками и построить систему, которая фиксирует критические ошибки до того, как о них сообщат пользователи.
Ключевые выводы
- Консольное логирование не обеспечивает сохранность данных, централизацию и структуру, необходимые для продакшен-окружений
- Структурированное логирование с фреймворками вроде Winston или Pino предоставляет машиночитаемые данные для анализа
- Обработка ошибок на фронтенде требует глобальных обработчиков и специфичных для фреймворков решений, таких как React Error Boundaries
- Защита конфиденциальных данных и включение контекстной информации критически важны для эффективного логирования
Почему консольное логирование не подходит для продакшена
Большинство разработчиков начинают с console.log() для отладки. Хотя этот подход адекватен во время разработки, он не работает в продакшене:
// This error disappears into the user's browser
try {
processPayment(order);
} catch (error) {
console.error(error); // Lost forever in production
}
Консольным методам не хватает:
- Сохранности данных за пределами текущей сессии
- Централизованного сбора информации от всех пользователей
- Структурированных данных для анализа
- Уровней важности для приоритизации
- Защиты конфиденциальных данных
Продакшен-приложениям необходимо логирование, которое захватывает, структурирует и передаёт ошибки в централизованное место для анализа.
Внедрение структурированного логирования с проверенными фреймворками
Выбор подходящего фреймворка
Для логирования в Node.js доминируют два фреймворка:
Winston предлагает гибкость и обширные возможности транспортировки:
const winston = require('winston');
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.json(),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'error.log', level: 'error' })
]
});
Pino приоритизирует производительность с минимальными накладными расходами:
const pino = require('pino');
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
timestamp: pino.stdTimeFunctions.isoTime,
formatters: {
level: (label) => ({ level: label })
}
});
Структурирование логов
Замените неструктурированные строки на JSON-объекты, которые машины могут парсить и анализировать:
// Bad: Unstructured string
logger.info(`User ${userId} failed login attempt`);
// Good: Structured JSON
logger.info({
event: 'login_failed',
userId: userId,
ip: request.ip,
timestamp: new Date().toISOString(),
userAgent: request.headers['user-agent']
});
Discover how at OpenReplay.com.
Основные компоненты эффективного логирования ошибок JavaScript
1. Используйте соответствующие уровни логирования
Внедрите согласованные уровни важности по всему приложению:
logger.debug('Detailed debugging information');
logger.info('Normal application flow');
logger.warn('Warning: degraded performance detected');
logger.error('Error occurred but application continues');
logger.fatal('Critical failure, application shutting down');
2. Всегда включайте трассировку стека
Захватывайте полный контекст ошибки для отладки:
process.on('uncaughtException', (error) => {
logger.fatal({
message: error.message,
stack: error.stack,
timestamp: new Date().toISOString()
});
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
logger.error({
message: 'Unhandled Promise Rejection',
reason: reason,
promise: promise
});
});
3. Добавляйте контекстную информацию
Включайте идентификаторы запросов, пользователей и данные сессий для отслеживания проблем:
const requestLogger = logger.child({
requestId: generateRequestId(),
sessionId: request.session.id
});
requestLogger.info('Processing payment request');
4. Защищайте конфиденциальные данные
Никогда не логируйте пароли, токены или персональную информацию:
const logger = pino({
redact: ['password', 'creditCard', 'ssn', 'authorization']
});
// These fields will be automatically redacted
logger.info({
user: email,
password: 'secret123', // Will show as [REDACTED]
action: 'login_attempt'
});
Стратегии обработки ошибок на фронтенде
Глобальные обработчики ошибок
Захватывайте все необработанные ошибки в браузерных окружениях:
window.addEventListener('error', (event) => {
logToServer({
message: event.message,
source: event.filename,
line: event.lineno,
column: event.colno,
stack: event.error?.stack
});
});
window.addEventListener('unhandledrejection', (event) => {
logToServer({
type: 'unhandledRejection',
reason: event.reason,
promise: event.promise
});
});
React Error Boundaries
Для React-приложений внедрите error boundaries для отлова ошибок компонентов:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
logger.error({
message: error.toString(),
componentStack: errorInfo.componentStack,
timestamp: new Date().toISOString()
});
}
render() {
if (this.state.hasError) {
return <h2>Something went wrong. Please refresh the page.</h2>;
}
return this.props.children;
}
}
Централизация логов для анализа
Направляйте все логи в stdout и позвольте вашей инфраструктуре управлять маршрутизацией:
// Configure logger to output to stdout only
const logger = pino({
transport: {
target: 'pino-pretty',
options: {
destination: 1 // stdout
}
}
});
Этот подход позволяет Docker, Kubernetes или log-шипперам вроде Fluentd собирать и направлять логи в централизованные системы для анализа. Для клиентских приложений реализуйте простую конечную точку для приёма и пересылки логов из браузеров в вашу централизованную инфраструктуру логирования.
Заключение
Эффективное логирование ошибок JavaScript требует большего, чем просто замена console.log на фреймворк. Оно требует структурированных данных, соответствующих уровней важности, полного контекста ошибок и централизованного сбора. Внедряя эти практики с фреймворками вроде Winston или Pino, защищая конфиденциальные данные и устанавливая правильные error boundaries во фронтенд-коде, вы создаёте систему, которая отлавливает проблемы до того, как они повлияют на пользователей. Начните с этих основ, а затем расширяйте систему в зависимости от специфических потребностей мониторинга вашего приложения.
Часто задаваемые вопросы
Pino имеет минимальные накладные расходы, добавляя всего 2-3% задержки в большинстве случаев. Winston немного тяжелее, но всё равно незначителен для большинства приложений. Оба готовы к продакшену и используются высоконагруженными приложениями по всему миру.
Используйте встроенные функции редактирования в вашем фреймворке логирования для автоматического маскирования конфиденциальных полей. Определите список имён полей для редактирования, таких как пароли, токены и номера кредитных карт. Всегда регулярно проверяйте свои логи на случайную утечку данных.
Логируйте ошибки с обеих сторон. Клиентское логирование захватывает проблемы, специфичные для браузера, и JavaScript-ошибки, которые никогда не достигают вашего сервера. Серверное логирование обрабатывает ошибки API и сбои бэкенда. Используйте централизованную систему для агрегации обоих источников.
Настройте немедленные оповещения для уровней error и fatal. Уровень warning может вызывать ежедневные сводки. Уровни info и debug должны быть доступны для поиска, но не вызывать оповещений, если только вы не расследуете конкретные проблемы или не отслеживаете критические бизнес-события.
Understand every bug
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.