13. Мониторинг с Sentry

Sentry — платформа мониторинга ошибок. Ловит исключения, трассировки запросов и проблемы производительности в реальном времени.
Зачем мониторинг
Заголовок раздела «Зачем мониторинг»Без мониторинга:- Узнаёшь о баге от пользователя через Telegram- "Всё упало ночью, мы не знаем когда"- Нет контекста — что случилось перед ошибкой?- Stack trace потерян
С Sentry:- Алерт приходит раньше пользователя- Точное время, частота, окружение- Полный stack trace с контекстом- Какой пользователь, какой браузер, какой запросУстановка Sentry в Next.js
Заголовок раздела «Установка Sentry в Next.js»npx @sentry/wizard@latest -i nextjsWizard автоматически:
- Установит
@sentry/nextjs - Создаст
sentry.client.config.ts,sentry.server.config.ts - Настроит
next.config.js
# Или вручнуюnpm install @sentry/nextjsКонфигурация
Заголовок раздела «Конфигурация»import * as Sentry from '@sentry/nextjs';
Sentry.init({ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.NODE_ENV,
// % транзакций для performance monitoring tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,
// % сессий для session replay replaysSessionSampleRate: 0.1, replaysOnErrorSampleRate: 1.0, // 100% при ошибках
integrations: [ Sentry.replayIntegration(), ],
// Игнорировать известные не-ошибки ignoreErrors: [ 'ResizeObserver loop limit exceeded', 'Network request failed', /^Non-Error promise rejection/, ],
beforeSend(event) { // Убрать чувствительные данные if (event.request?.headers) { delete event.request.headers['Authorization']; delete event.request.headers['Cookie']; } return event; },});import * as Sentry from '@sentry/nextjs';
Sentry.init({ dsn: process.env.SENTRY_DSN, environment: process.env.NODE_ENV, tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,});Ручная обработка ошибок
Заголовок раздела «Ручная обработка ошибок»import * as Sentry from '@sentry/nextjs';
// Захват ошибкиtry { await riskyOperation();} catch (error) { Sentry.captureException(error, { extra: { userId: user.id, operation: 'riskyOperation', data: JSON.stringify(requestData), }, tags: { feature: 'payments', severity: 'critical', }, }); throw error;}
// Захват сообщения (не ошибки)Sentry.captureMessage('Payment webhook received', { level: 'info', extra: { webhookId: id },});
// Breadcrumb — хлебные крошки для контекстаSentry.addBreadcrumb({ category: 'auth', message: 'User logged in', level: 'info', data: { userId: user.id },});
// Идентификация пользователяSentry.setUser({ id: user.id, email: user.email, username: user.name,});
// Очистить пользователя при logoutSentry.setUser(null);Error Boundary в React
Заголовок раздела «Error Boundary в React»'use client';
import * as Sentry from '@sentry/nextjs';import { Component, ErrorInfo, ReactNode } from 'react';
interface Props { children: ReactNode; fallback?: ReactNode;}
interface State { hasError: boolean; eventId?: string;}
export class ErrorBoundary extends Component<Props, State> { state: State = { hasError: false };
static getDerivedStateFromError() { return { hasError: true }; }
componentDidCatch(error: Error, info: ErrorInfo) { const eventId = Sentry.captureException(error, { extra: { componentStack: info.componentStack }, }); this.setState({ eventId }); }
render() { if (this.state.hasError) { return ( <div className="error-boundary"> <h2>Что-то пошло не так</h2> <button onClick={() => Sentry.showReportDialog({ eventId: this.state.eventId })}> Сообщить об ошибке </button> <button onClick={() => this.setState({ hasError: false })}> Попробовать снова </button> </div> ); } return this.props.children; }}Performance Monitoring
Заголовок раздела «Performance Monitoring»// Отслеживание производительности кастомных операцийimport * as Sentry from '@sentry/nextjs';
async function processOrder(orderId: string) { return Sentry.startSpan( { name: 'processOrder', op: 'business.order', attributes: { orderId }, }, async (span) => { // Вложенные spans const order = await Sentry.startSpan( { name: 'fetchOrder', op: 'db.query' }, () => db.orders.findById(orderId) );
span.setAttribute('order.amount', order.total);
const result = await Sentry.startSpan( { name: 'chargeCard', op: 'http.client' }, () => stripe.charges.create({ amount: order.total }) );
return result; } );}Source Maps — читаемые stack traces
Заголовок раздела «Source Maps — читаемые stack traces»const { withSentryConfig } = require('@sentry/nextjs');
module.exports = withSentryConfig( { // твой next.config.js }, { org: 'my-org', project: 'myapp',
// Загружать source maps только в production silent: !process.env.CI,
// Скрыть source maps от пользователей hideSourceMaps: true,
// Автоматически загружать source maps после сборки widenClientFileUpload: true, });Alerts и Notifications
Заголовок раздела «Alerts и Notifications»В Sentry Dashboard → Alerts:
# Пример правил алертов:
Новая ошибка впервые: - Trigger: First seen issue - Action: Email + Slack
Рост ошибок: - Trigger: Number of events > 100/hour - Action: PagerDuty + Slack
Деградация производительности: - Trigger: p95 latency > 2000ms - Action: Email
Critical severity: - Trigger: tags["severity"] == "critical" - Action: SMS + PagerDutyИнтеграция с GitHub Actions
Заголовок раздела «Интеграция с GitHub Actions»- name: Create Sentry release uses: getsentry/action-release@v1 env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_ORG: my-org SENTRY_PROJECT: myapp with: environment: production version: ${{ github.sha }}Ключевые моменты
Заголовок раздела «Ключевые моменты»- Sentry ловит ошибки в реальном времени с полным контекстом
- DSN — ключ проекта,
NEXT_PUBLIC_для клиента, без префикса для сервера tracesSampleRate— не ставь 1.0 в production, нагружает базуbeforeSend— фильтруй чувствительные данные перед отправкойSentry.setUser()— идентифицируй пользователей для фильтрации ошибок- Source maps — критичны для читаемых stack traces в минифицированном коде
- Alerts — настрой уведомления в Slack/Telegram до того как узнают пользователи
Интерактивный пример
Заголовок раздела «Интерактивный пример»Дашборд мониторинга ошибок: