Guía completa de monitoreo de errores frontend: Sentry, SourceMap y captura de excepciones transparente para el usuario
前端工程
Por qué el monitoreo de errores frontend es indispensable
Los usuarios que encuentran pantallas en blanco, botones que no responden o errores de API en producción — si no lo sabes, no puedes solucionarlo. El monitoreo de errores frontend es el puente que conecta la experiencia real del usuario con la conciencia del desarrollador.
| Costo de no monitorear | Beneficio de monitorear |
|---|---|
| Bugs descubiertos solo por quejas de usuarios | Detección de anomalías en tiempo real, respuesta a nivel de minutos |
| Problemas esporádicos irreproducibles | Stack traces completos + repetición del comportamiento del usuario |
| Incidentes en producción que duran horas | Alertas automáticas, MTTR drásticamente reducido |
| Correcciones basadas en conjeturas | Basado en datos, localización precisa |
Las cinco fuentes de errores frontend
1. Errores de sintaxis y tiempo de ejecución
// ReferenceError: x is not defined
console.log(x);
// TypeError: Cannot read properties of undefined
const user = undefined;
user.name;
2. Errores de carga de recursos
// Fallos de carga de imágenes, scripts, hojas de estilo
const img = new Image();
img.src = '/broken-image.png';
img.onerror = () => {
trackError({ type: 'resource', tag: 'img', src: img.src });
};
3. Excepciones Promise no capturadas
// El tipo de error más fácilmente pasado por alto
fetch('/api/data')
.then(res => res.json())
// .then lanza una excepción pero no hay .catch
.then(data => data.results.map(r => r.name));
4. Excepciones de API
// Códigos de error HTTP + códigos de error de negocio
fetch('/api/order')
.then(res => {
if (!res.ok) {
trackError({ type: 'http', status: res.status, url: res.url });
}
return res.json();
});
5. Errores de Web Worker / iframe
// Las excepciones internas del Worker no se propagan al hilo 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 errores: Cuatro líneas de defensa
Línea de defensa 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
};
Limitación: No puede capturar errores de carga de recursos ni excepciones Promise.
Línea de defensa 2: window.addEventListener('error')
window.addEventListener('error', (event) => {
if (event.target instanceof HTMLElement) {
// Error de carga de recursos
trackError({
type: 'resource',
tagName: event.target.tagName,
src: event.target.src || event.target.href,
});
} else {
// Error de tiempo de ejecución (puede duplicarse con onerror, necesita desduplicación)
trackError({
type: 'runtime',
message: event.message,
stack: event.error?.stack,
});
}
}, true); // Usar fase de captura
Línea de defensa 3: window.addEventListener('unhandledrejection')
window.addEventListener('unhandledrejection', (event) => {
trackError({
type: 'unhandledRejection',
reason: event.reason?.message || String(event.reason),
stack: event.reason?.stack,
});
});
Línea de defensa 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;
}
}
Deobfuscación de SourceMap: Restaurando stack traces reales desde código minificado
El código de producción está minificado y ofuscado, lo que hace que los stack traces de errores sean completamente ilegibles. La deobfuscación de SourceMap es la capacidad central para la resolución de problemas en producción.
Ejemplo 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 deobfuscado
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)
Estrategias de seguridad de SourceMap
| Estrategia | Seguridad | Complejidad | Mejor para |
|---|---|---|---|
| NPM privado + deobfuscación en CI | Alta | Media | La mayoría de equipos |
| Deobfuscación del lado del servidor con Sentry | Media | Baja | Integración rápida |
| SourceMap inline en Base64 | Baja | Baja | Solo entorno de desarrollo |
| Archivos .map independientes en producción | Muy baja | Baja | No 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;
}
Integración de Sentry en la práctica
Instalación e inicialización
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;
},
});
Integración con React Router
const SentryRoutes = Sentry.withSentryReactRouterV6Routing(Routes);
function App() {
return (
<SentryRoutes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
</SentryRoutes>
);
}
Reporte manual y breadcrumbs
// Reporte manual de excepciones de negocio
Sentry.captureException(new Error('Order payment timeout'), {
tags: { orderId: 'ORD-12345', paymentMethod: 'alipay' },
extra: { retryCount: 3, lastAttempt: Date.now() },
});
// Agregar breadcrumbs (rastro de comportamiento del usuario)
Sentry.addBreadcrumb({
category: 'ui.click',
message: 'Clicked submit button',
level: 'info',
});
Construyendo un sistema de monitoreo ligero
Si no quieres depender de servicios de terceros, puedes construir el tuyo propio:
SDK de recolección de datos
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;
}
}
Panel de métricas clave
| Métrica | Cálculo | Umbral de alerta |
|---|---|---|
| Tasa de errores JS | PV de errores / PV total | > 0.1% |
| Tasa de fallos de API | Cantidad de 5xx / Total de solicitudes | > 1% |
| Tasa de fallos en primera pantalla | PV de pantalla en blanco / PV total | > 0.01% |
| Tiempo medio de reparación | Tiempo desde la primera alerta hasta el despliegue | > 30min |
Lista de verificación de mejores prácticas
- Cobertura completa con cuatro líneas de defensa: onerror + evento error + unhandledrejection + Error Boundary
- Sin SourceMap en producción: Artefactos de compilación separados, deobfuscación en entorno de CI
- Muestreo y filtrado de errores: Evitar que errores de alta frecuencia sin sentido ahoguen los problemas reales
- Repetición del comportamiento del usuario: Breadcrumbs + Session Replay para acelerar la localización
- Niveles de alerta: P0 notificación inmediata, P1 seguimiento por ticket, P2 resumen en reporte semanal
- Asociación de versiones: Cada release lleva un número de versión, los errores se asocian automáticamente con cambios de código
- Cumplimiento de privacidad: Sanitizar los datos reportados, no incluir información personal del usuario
- Correlación de rendimiento: Vincular el monitoreo de errores con el monitoreo de rendimiento para descubrir las causas raíz de la degradación del rendimiento
Prueba estas herramientas que se ejecutan en tu navegador — no requieren registro →
#错误监控#Sentry#SourceMap#异常捕获#DevOps