Почему ваш JavaScript-код кричит о помощи, а вы не слышите?
Представьте: вы запустили крутое веб-приложение. Пользователи заходят, кликают, всё работает… или нет? На самом деле, где-то в недрах браузера тихо падает ошибка: Uncaught TypeError: Cannot read property 'length' of undefined. Пользователь просто закрывает вкладку и уходит к конкурентам, а вы даже не узнаете, что что-то пошло не так.
Добро пожаловать в реальность frontend-разработки. Без мониторинга и логирования JavaScript приложений вы работаете вслепую. Но не спешите хвататься за голову — сегодня мы разберем, как превратить хаос ошибок в стройную систему оповещений. Вы узнаете, как правильно логировать события, отслеживать производительность и не утонуть в море данных.
Эта статья — ваш шпаргалка по выживанию в production. Поехали!
Console.log — ваш друг или враг?
Начнем с базы. console.log() — это первое, что приходит в голову каждому разработчику. И это нормально. Но если вы используете его в production-коде, остановитесь. Почему? Потому что:
- Он засоряет консоль пользователя (да, некоторые её открывают).
- Влияет на производительность при большом количестве вызовов.
- Не даёт структурированной информации.
Лучше используйте уровни логирования. Вот пример правильного подхода:
// Плохо
console.log('User clicked button');
// Хорошо
console.info('User interaction', {
action: 'click',
element: '#submit-btn',
timestamp: Date.now()
});
// Для ошибок
console.error('API request failed', {
status: 500,
endpoint: '/api/users'
});
// Для предупреждений
console.warn('Deprecated method used', {
method: 'fetchData_v1'
});
Но даже это — лишь вершина айсберга. Настоящий мониторинг JavaScript приложений начинается там, где консоль недоступна.
Структурированное логирование: JSON вам в помощь
Когда ваше приложение разрастается, хаотичные сообщения в консоли превращаются в кашу. Выход — структурированное логирование. Отправляйте данные в формате JSON, чтобы их можно было парсить и анализировать.
Вот как можно сделать простой логгер:
class Logger {
constructor(context = {}) {
this.context = context;
}
log(level, message, data = {}) {
const logEntry = {
timestamp: new Date().toISOString(),
level,
message,
...this.context,
...data
};
// В production отправляем на сервер
if (process.env.NODE_ENV === 'production') {
this.sendToServer(logEntry);
} else {
// В development выводим в консоль
console[level](`[${level.toUpperCase()}] ${message}`, data);
}
}
sendToServer(entry) {
// Используем sendBeacon для гарантированной отправки
navigator.sendBeacon('/api/logs', JSON.stringify(entry));
}
info(message, data) { this.log('info', message, data); }
warn(message, data) { this.log('warn', message, data); }
error(message, data) { this.log('error', message, data); }
}
// Использование
const logger = new Logger({ appVersion: '1.2.3', userId: getUserId() });
logger.info('Page loaded', { url: window.location.href, loadTime: performance.now() });
Обратите внимание на navigator.sendBeacon() — это лучший способ отправить данные перед уходом пользователя со страницы. Обычные fetch-запросы могут быть отменены.
Отлов ошибок: try/catch — это только начало
Глобальные ошибки — самые коварные. Они могут возникнуть в любом месте, и их легко пропустить. Вот как настроить тотальный мониторинг JavaScript ошибок:
Глобальный обработчик ошибок
// Перехватываем необработанные ошибки
window.addEventListener('error', (event) => {
logger.error('Global error caught', {
message: event.message,
source: event.filename,
line: event.lineno,
column: event.colno,
stack: event.error?.stack
});
// Не даем ошибке "упасть" в консоль
event.preventDefault();
});
// Перехватываем rejected promises
window.addEventListener('unhandledrejection', (event) => {
logger.error('Unhandled promise rejection', {
reason: event.reason?.message || event.reason,
stack: event.reason?.stack
});
event.preventDefault();
});
Monkey patching для fetch и XMLHttpRequest
Хотите логировать все AJAX-запросы? Переопределите методы:
const originalFetch = window.fetch;
window.fetch = async (...args) => {
const startTime = performance.now();
try {
const response = await originalFetch(...args);
const duration = performance.now() - startTime;
logger.info('Fetch request', {
url: args[0],
method: args[1]?.method || 'GET',
status: response.status,
duration: `${duration.toFixed(2)}ms`
});
return response;
} catch (error) {
logger.error('Fetch failed', {
url: args[0],
error: error.message
});
throw error;
}
};
Мониторинг производительности: не дайте пользователю ждать
Медленное приложение = потерянные деньги. Используйте Performance API для отслеживания метрик:
// Отслеживаем Core Web Vitals
function trackWebVitals() {
// First Contentful Paint
const paintEntries = performance.getEntriesByType('paint');
paintEntries.forEach(entry => {
logger.info(`Paint metric: ${entry.name}`, {
value: entry.startTime
});
});
// Largest Contentful Paint
if ('largestContentfulPaint' in PerformanceObserver) {
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
logger.info('LCP', { value: lastEntry.startTime });
}).observe({ type: 'largest-contentful-paint', buffered: true });
}
// First Input Delay
if ('firstInput' in PerformanceObserver) {
new PerformanceObserver((list) => {
const entry = list.getEntries()[0];
logger.info('FID', {
delay: entry.processingStart - entry.startTime,
duration: entry.duration
});
}).observe({ type: 'first-input', buffered: true });
}
}
document.addEventListener('DOMContentLoaded', trackWebVitals);
Инструменты для production-мониторинга
Самописные решения хороши для начала, но в серьёзных проектах нужны специализированные сервисы. Вот топ-3:
- Sentry — золотой стандарт для отслеживания ошибок. Даёт source maps, стеки вызовов, контекст пользователя.
- LogRocket — записывает сессии пользователей. Видите не только ошибку, но и что пользователь делал до неё.
- Datadog RUM — комплексный мониторинг реальных пользователей (Real User Monitoring).
Пример интеграции с Sentry:
import * as Sentry from '@sentry/browser';
Sentry.init({
dsn: 'https://your-dsn@sentry.io/project