Back

Лучшие практики логирования ошибок в JavaScript

Лучшие практики логирования ошибок в 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']
});

Основные компоненты эффективного логирования ошибок 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.

OpenReplay