← Назад к вопросам

Как мониторишь исключения в JavaScript?

2.0 Middle🔥 191 комментариев
#JavaScript Core#Браузер и сетевые технологии

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Мониторинг исключений в JavaScript

Мониторинг ошибок (exception tracking) — критический аспект разработки production приложений. Это позволяет быстро обнаружить и исправить проблемы, о которых пользователи даже не знают.

Способ 1: Встроенные обработчики ошибок

Перехват необработанных исключений:

// 1. Обработка синхронных ошибок
window.addEventListener('error', (event) => {
  console.error('Ошибка:', {
    message: event.message,
    filename: event.filename,
    lineno: event.lineno,
    colno: event.colno,
    error: event.error
  });
  
  // Отправка на сервер
  fetch('/api/logs/error', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      type: 'javascript_error',
      message: event.message,
      stack: event.error?.stack,
      url: window.location.href,
      timestamp: new Date().toISOString()
    })
  });
});

// 2. Обработка Promise отклонений (async/await ошибки)
window.addEventListener('unhandledrejection', (event) => {
  console.error('Необработанное отклонение Promise:', event.reason);
  
  fetch('/api/logs/error', {
    method: 'POST',
    body: JSON.stringify({
      type: 'unhandled_rejection',
      reason: String(event.reason),
      stack: event.reason?.stack,
      timestamp: new Date().toISOString()
    })
  });
});

Способ 2: Обёртывание функций try-catch

Охватывание критичного кода:

// Оборачиваем функцию
function handleUserLogin(credentials) {
  try {
    validateCredentials(credentials);
    const user = fetchUser(credentials);
    setUserData(user);
    console.log('Пользователь авторизован');
  } catch (error) {
    console.error('Ошибка при входе:', error);
    
    // Логируем ошибку
    logError({
      action: 'login',
      error: error.message,
      stack: error.stack
    });
    
    // Показываем пользователю
    showNotification('Ошибка при входе. Попробуйте ещё раз.', 'error');
  }
}

// Декоратор для оборачивания всех методов класса
function catchErrors(target, propertyKey, descriptor) {
  const originalMethod = descriptor.value;
  
  descriptor.value = async function(...args) {
    try {
      return await originalMethod.apply(this, args);
    } catch (error) {
      console.error(`Ошибка в ${propertyKey}:`, error);
      logError({ method: propertyKey, error });
      throw error;
    }
  };
  
  return descriptor;
}

class UserService {
  @catchErrors
  async getUserProfile(userId) {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  }
}

Способ 3: Перехват fetch запросов

Мониторинг сетевых ошибок:

// Оборачиваем встроенный fetch
const originalFetch = window.fetch;

window.fetch = function(...args) {
  const [resource, config] = args;
  const startTime = performance.now();
  
  return originalFetch.apply(this, args)
    .then(response => {
      const duration = performance.now() - startTime;
      
      // Логируем медленные запросы
      if (duration > 3000) {
        logWarning({
          type: 'slow_request',
          url: resource,
          duration,
          status: response.status
        });
      }
      
      // Логируем ошибки API
      if (!response.ok) {
        logError({
          type: 'api_error',
          url: resource,
          status: response.status,
          statusText: response.statusText
        });
      }
      
      return response;
    })
    .catch(error => {
      logError({
        type: 'network_error',
        url: resource,
        error: error.message
      });
      throw error;
    });
};

Способ 4: Кастомный Error Logger

Структурированное логирование ошибок:

class ErrorLogger {
  constructor(apiEndpoint = '/api/logs/error') {
    this.apiEndpoint = apiEndpoint;
    this.queue = [];
    this.maxQueueSize = 50;
    this.batchInterval = 5000; // 5 сек
    
    this.startBatching();
  }
  
  log(error, context = {}) {
    const errorData = {
      message: error.message,
      stack: error.stack,
      type: error.name,
      context: context,
      url: window.location.href,
      userAgent: navigator.userAgent,
      timestamp: new Date().toISOString(),
      userId: this.getUserId(),
      sessionId: this.getSessionId()
    };
    
    this.queue.push(errorData);
    
    // Отправляем сразу если очередь переполнена
    if (this.queue.length >= this.maxQueueSize) {
      this.flush();
    }
  }
  
  startBatching() {
    setInterval(() => {
      if (this.queue.length > 0) {
        this.flush();
      }
    }, this.batchInterval);
  }
  
  flush() {
    if (this.queue.length === 0) return;
    
    const errors = [...this.queue];
    this.queue = [];
    
    // Отправляем на сервер
    fetch(this.apiEndpoint, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ errors }),
      keepalive: true // Отправляем даже при закрытии вкладки
    }).catch(err => {
      console.error('Не удалось отправить логи:', err);
      // Восстанавливаем очередь если ошибка
      this.queue.unshift(...errors);
    });
  }
  
  getUserId() {
    return localStorage.getItem('userId') || 'anonymous';
  }
  
  getSessionId() {
    return sessionStorage.getItem('sessionId') || 'unknown';
  }
}

// Использование
const errorLogger = new ErrorLogger();

window.addEventListener('error', (event) => {
  errorLogger.log(event.error, { type: 'uncaught_error' });
});

try {
  riskyOperation();
} catch (error) {
  errorLogger.log(error, { action: 'riskyOperation' });
}

Способ 5: Sentry (production решение)

Профессиональный мониторинг ошибок:

// Установка: npm install @sentry/react @sentry/tracing

import * as Sentry from "@sentry/react";
import { BrowserTracing } from "@sentry/tracing";

Sentry.init({
  // DSN — ключ для отправки ошибок на Sentry
  dsn: process.env.REACT_APP_SENTRY_DSN,
  
  // Окружение
  environment: process.env.NODE_ENV,
  
  // Трейсинг для отслеживания производительности
  integrations: [
    new BrowserTracing(),
    new Sentry.Replay({
      maskAllText: true, // Скрывать текст для приватности
      blockAllMedia: true
    })
  ],
  
  // Отправлять 100% ошибок в production
  tracesSampleRate: 1.0,
  
  // Записывать session replay для 10% пользователей
  replaysSessionSampleRate: 0.1,
  
  // Для ошибок записывать replay всегда
  replaysOnErrorSampleRate: 1.0
});

// Использование
try {
  processData(data);
} catch (error) {
  Sentry.captureException(error, {
    contexts: {
      user: { userId: currentUser.id },
      request: { url: window.location.href }
    },
    tags: {
      severity: 'high',
      feature: 'checkout'
    }
  });
}

// Компонент с обработкой ошибок
class ErrorBoundary extends Sentry.ErrorBoundary {
  render() {
    if (this.state.hasError) {
      return <h1>Что-то пошло не так</h1>;
    }
    return this.props.children;
  }
}

Способ 6: React Error Boundary

Обработка ошибок в React:

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }
  
  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }
  
  componentDidCatch(error, errorInfo) {
    // Логируем ошибку
    console.error('React Error:', error, errorInfo);
    
    // Отправляем на сервер
    fetch('/api/logs/error', {
      method: 'POST',
      body: JSON.stringify({
        type: 'react_error',
        error: error.toString(),
        componentStack: errorInfo.componentStack
      })
    });
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <div className="error-container">
          <h2>Что-то пошло не так</h2>
          <p>{this.state.error?.message}</p>
          <button onClick={() => window.location.reload()}>
            Перезагрузить страницу
          </button>
        </div>
      );
    }
    
    return this.props.children;
  }
}

// Использование
<ErrorBoundary>
  <App />
</ErrorBoundary>

Способ 7: Console переопределение

Перехват console логов:

const logs = [];

// Сохраняем оригинальные методы
const originalError = console.error;
const originalWarn = console.warn;
const originalLog = console.log;

// Переопределяем console.error
console.error = function(...args) {
  logs.push({
    level: 'error',
    message: args.join(' '),
    timestamp: new Date().toISOString()
  });
  
  // Вызываем оригинальный метод
  originalError.apply(console, args);
};

// Аналогично для warn и log
console.warn = function(...args) {
  logs.push({ level: 'warn', message: args.join(' '), timestamp: new Date() });
  originalWarn.apply(console, args);
};

// Отправляем логи на сервер
function sendLogsToServer() {
  if (logs.length > 0) {
    fetch('/api/logs', {
      method: 'POST',
      body: JSON.stringify({ logs })
    });
    logs.length = 0; // Очищаем очередь
  }
}

setInterval(sendLogsToServer, 10000); // Каждые 10 сек

Лучшие практики мониторинга

  1. Логируй контекст — userId, URL, действие пользователя
  2. Не логируй чувствительные данные — пароли, токены
  3. Группируй ошибки — по типу и стеку вызовов
  4. Устанавливай alerty для критичных ошибок — в Slack, PagerDuty
  5. Анализируй тренды — какие ошибки самые частые
  6. Воспроизводи проблемы — используй session replay
  7. Исправляй быстро — сразу создавай tickets для top ошибок
  8. Мониторь производительность — не только ошибки

Мониторинг производительности

// Web Vitals — ключевые метрики производительности
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

getCLS(metric => console.log('Cumulative Layout Shift:', metric.value));
getFID(metric => console.log('First Input Delay:', metric.value));
getFCP(metric => console.log('First Contentful Paint:', metric.value));
getLCP(metric => console.log('Largest Contentful Paint:', metric.value));
getTTFB(metric => console.log('Time to First Byte:', metric.value));

Рекомендуемая архитектура

// 1. Встроенные обработчики для всех необработанных ошибок
window.addEventListener('error', globalErrorHandler);
window.addEventListener('unhandledrejection', globalRejectionHandler);

// 2. ErrorLogger для структурированного логирования
const errorLogger = new ErrorLogger();

// 3. Sentry для production
if (process.env.NODE_ENV === 'production') {
  Sentry.init({ ... });
}

// 4. Error Boundary для React
<ErrorBoundary>
  <App />
</ErrorBoundary>

// 5. Мониторинг критичных операций
try {
  await criticalOperation();
} catch (error) {
  errorLogger.log(error, { severity: 'high' });
}

Эффективный мониторинг исключений — это ключ к стабильному production приложению. Инвестируй в это с самого начала разработки.

Как мониторишь исключения в JavaScript? | PrepBro