Guia Completo de Monitoramento de Erros Frontend: Sentry, SourceMap e Captura de Exceções Transparente para o Usuário

前端工程

Por que o Monitoramento de Erros Frontend é Indispensável

Usuários encontrando telas em branco, botões sem resposta ou erros de API em produção — se você não sabe disso, não pode corrigir. O monitoramento de erros frontend é a ponte que conecta a experiência real do usuário com a consciência do desenvolvedor.

Custo de Não Monitorar Benefício de Monitorar
Bugs descobertos apenas por reclamações de usuários Detecção de anomalias em tempo real, resposta em nível de minutos
Problemas esporádicos irreproduzíveis Stack traces completos + replay do comportamento do usuário
Incidentes em produção durando horas Alertas automáticos, MTTR drasticamente reduzido
Correções baseadas em suposições Baseado em dados, localização precisa

As Cinco Fontes de Erros Frontend

1. Erros de Sintaxe e Tempo de Execução

// ReferenceError: x is not defined
console.log(x);

// TypeError: Cannot read properties of undefined
const user = undefined;
user.name;

2. Erros de Carregamento de Recursos

// Falhas de carregamento de imagens, scripts, folhas de estilo
const img = new Image();
img.src = '/broken-image.png';
img.onerror = () => {
  trackError({ type: 'resource', tag: 'img', src: img.src });
};

3. Exceções Promise Não Capturadas

// O tipo de erro mais facilmente negligenciado
fetch('/api/data')
  .then(res => res.json())
  // .then lança uma exceção mas não há .catch
  .then(data => data.results.map(r => r.name));

4. Exceções de API

// Códigos de erro HTTP + códigos de erro de negócio
fetch('/api/order')
  .then(res => {
    if (!res.ok) {
      trackError({ type: 'http', status: res.status, url: res.url });
    }
    return res.json();
  });

5. Erros de Web Worker / iframe

// Exceções internas do Worker não se propagam para a thread principal
const worker = new Worker('/worker.js');
worker.onerror = (e) => {
  trackError({
    type: 'worker',
    message: e.message,
    filename: e.filename,
    lineno: e.lineno,
  });
};

Captura Global de Erros: Quatro Linhas de Defesa

Linha de Defesa 1: window.onerror

window.onerror = (message, source, lineno, colno, error) => {
  trackError({
    type: 'runtime',
    message,
    source,
    lineno,
    colno,
    stack: error?.stack,
  });
  return false; // Don't suppress default console output
};

Limitação: Não pode capturar erros de carregamento de recursos ou exceções Promise.

Linha de Defesa 2: window.addEventListener('error')

window.addEventListener('error', (event) => {
  if (event.target instanceof HTMLElement) {
    // Erro de carregamento de recursos
    trackError({
      type: 'resource',
      tagName: event.target.tagName,
      src: event.target.src || event.target.href,
    });
  } else {
    // Erro de tempo de execução (pode duplicar com onerror, precisa de desduplicação)
    trackError({
      type: 'runtime',
      message: event.message,
      stack: event.error?.stack,
    });
  }
}, true); // Usar fase de captura

Linha de Defesa 3: window.addEventListener('unhandledrejection')

window.addEventListener('unhandledrejection', (event) => {
  trackError({
    type: 'unhandledRejection',
    reason: event.reason?.message || String(event.reason),
    stack: event.reason?.stack,
  });
});

Linha de Defesa 4: React Error Boundary

class ErrorBoundary extends React.Component {
  state = { hasError: false };

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    trackError({
      type: 'react',
      message: error.message,
      stack: error.stack,
      componentStack: errorInfo.componentStack,
    });
  }

  render() {
    if (this.state.hasError) {
      return <FallbackUI onRetry={() => this.setState({ hasError: false })} />;
    }
    return this.props.children;
  }
}

Desobfuscação de SourceMap: Restaurando Stack Traces Reais a Partir de Código Minificado

O código de produção é minificado e ofuscado, tornando os stack traces de erros completamente ilegíveis. A desobfuscação de SourceMap é a capacidade central para solução de problemas em produção.

Exemplo de Stack Trace Minificado

TypeError: Cannot read properties of undefined (reading 'name')
  at a (main.abc123.js:1:2345)
  at o (main.abc123.js:1:5678)
  at t (main.abc123.js:1:9012)

Stack Trace Real Desobfuscado

TypeError: Cannot read properties of undefined (reading 'name')
  at getUserInfo (src/services/user.ts:42:15)
  at fetchDashboard (src/pages/dashboard.tsx:128:22)
  at useEffectCallback (src/pages/dashboard.tsx:115:5)

Estratégias de Segurança do SourceMap

Estratégia Segurança Complexidade Melhor Para
NPM privado + desobfuscação no CI Alta Média A maioria das equipes
Desobfuscação no servidor com Sentry Média Baixa Integração rápida
SourceMap inline em Base64 Baixa Baixa Apenas ambiente de desenvolvimento
Arquivos .map independentes em produção Muito Baixa Baixa Não recomendado
// Deobfuscate using the source-map library
import { SourceMapConsumer } from 'source-map';

async function applySourceMap(position, mapContent) {
  const consumer = await new SourceMapConsumer(mapContent);
  const original = consumer.originalPositionFor({
    line: position.line,
    column: position.column,
  });
  return original;
}

Integração do Sentry na Prática

Instalação e Inicialização

npm install @sentry/react @sentry/tracing
import * as Sentry from '@sentry/react';

Sentry.init({
  dsn: 'https://xxx@o123456.ingest.sentry.io/789',
  environment: process.env.NODE_ENV,
  release: process.env.APP_VERSION,
  tracesSampleRate: 0.1,
  replaysSessionSampleRate: 0.01,
  replaysOnErrorSampleRate: 1.0,
  integrations: [
    Sentry.browserTracingIntegration(),
    Sentry.replayIntegration(),
  ],
  beforeSend(event, hint) {
    // Filter meaningless errors
    if (event.exception?.values?.[0]?.type === 'ResizeObserver loop limit exceeded') {
      return null;
    }
    return event;
  },
});

Integração com React Router

const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes);

function App() {
  return (
    <SentryRoutes>
      <Route path="/" element={<Home />} />
      <Route path="/dashboard" element={<Dashboard />} />
    </SentryRoutes>
  );
}

Relatório Manual e Breadcrumbs

// Relatório manual de exceções de negócio
Sentry.captureException(new Error('Order payment timeout'), {
  tags: { orderId: 'ORD-12345', paymentMethod: 'alipay' },
  extra: { retryCount: 3, lastAttempt: Date.now() },
});

// Adicionar breadcrumbs (rastro de comportamento do usuário)
Sentry.addBreadcrumb({
  category: 'ui.click',
  message: 'Clicked submit button',
  level: 'info',
});

Construindo um Sistema de Monitoramento Leve

Se você não quer depender de serviços de terceiros, pode construir o seu próprio:

SDK de Coleta de Dados

class ErrorMonitor {
  constructor(options) {
    this.endpoint = options.endpoint;
    this.queue = [];
    this.timer = null;
    this.install();
  }

  install() {
    window.addEventListener('error', this.handleError.bind(this), true);
    window.addEventListener('unhandledrejection', this.handleRejection.bind(this));
  }

  handleError(event) {
    this.report({
      type: event.target instanceof HTMLElement ? 'resource' : 'runtime',
      message: event.message || event.target?.outerHTML,
      stack: event.error?.stack,
      timestamp: Date.now(),
      url: location.href,
      ua: navigator.userAgent,
    });
  }

  handleRejection(event) {
    this.report({
      type: 'unhandledRejection',
      message: event.reason?.message || String(event.reason),
      stack: event.reason?.stack,
      timestamp: Date.now(),
    });
  }

  report(data) {
    this.queue.push(data);
    if (!this.timer) {
      this.timer = setTimeout(() => this.flush(), 5000);
    }
  }

  flush() {
    if (this.queue.length === 0) return;
    const batch = this.queue.splice(0);
    navigator.sendBeacon(this.endpoint, JSON.stringify(batch));
    this.timer = null;
  }
}

Painel de Métricas Chave

Métrica Cálculo Limiar de Alerta
Taxa de Erros JS PV de erros / PV total > 0.1%
Taxa de Falhas de API Quantidade de 5xx / Total de requisições > 1%
Taxa de Falhas na Primeira Tela PV de tela em branco / PV total > 0.01%
Tempo Médio de Reparação Tempo da primeira alerta até o deploy > 30min

Lista de Verificação de Melhores Práticas

  1. Cobertura completa com quatro linhas de defesa: onerror + evento error + unhandledrejection + Error Boundary
  2. Sem SourceMap em produção: Artefatos de build separados, desobfuscação no ambiente de CI
  3. Amostragem e filtragem de erros: Evitar que erros de alta frequência sem sentido afoguem os problemas reais
  4. Replay do comportamento do usuário: Breadcrumbs + Session Replay para acelerar a localização
  5. Níveis de alerta: P0 notificação imediata, P1 acompanhamento por ticket, P2 resumo no relatório semanal
  6. Associação de versões: Cada release carrega um número de versão, erros são automaticamente associados a mudanças de código
  7. Conformidade de privacidade: Sanitizar os dados reportados, não incluir informações pessoais do usuário
  8. Correlação de desempenho: Vincular o monitoramento de erros com o monitoramento de desempenho para descobrir as causas raiz da degradação de desempenho

Experimente estas ferramentas executadas localmente no navegador — nenhum cadastro necessário →

#错误监控#Sentry#SourceMap#异常捕获#DevOps