← Назад к вопросам
Как мониторишь исключения в 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 сек
Лучшие практики мониторинга
- Логируй контекст — userId, URL, действие пользователя
- Не логируй чувствительные данные — пароли, токены
- Группируй ошибки — по типу и стеку вызовов
- Устанавливай alerty для критичных ошибок — в Slack, PagerDuty
- Анализируй тренды — какие ошибки самые частые
- Воспроизводи проблемы — используй session replay
- Исправляй быстро — сразу создавай tickets для top ошибок
- Мониторь производительность — не только ошибки
Мониторинг производительности
// 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 приложению. Инвестируй в это с самого начала разработки.