Seguimiento de Errores con Sentry en Express
Cómo integrar Sentry para seguimiento de errores en apps Node.js Express con el SDK v8+ — manejadores de error, spans de rendimiento, releases y source maps.
Visión General
Sentry captura los errores no manejados de tu aplicación, los agrupa por similitud y te avisa con el stack trace, el contexto de la petición, los datos del usuario y los breadcrumbs — los eventos que precedieron al error. En Express, el SDK instrumenta tus rutas automáticamente, así que cualquier error lanzado o promesa rechazada se convierte en un evento sin necesidad de try/catch por todas partes.
Esta receta usa @sentry/node v8+ — la API cambió bastante respecto a la v7 (el middleware Sentry.Handlers ya no existe). Los ejemplos de abajo funcionan con el SDK que obtienes hoy al hacer npm install @sentry/node.
Cuándo Usar
- Aplicaciones en producción donde necesitas alertas de error en tiempo real
- Seguir tasas de error y detectar regresiones entre releases
- Capturar bugs reportados por usuarios con stack traces completos y contexto de la petición
- Monitorizar el rendimiento (trazado de spans) junto al seguimiento de errores
- Priorizar bugs por severidad, frecuencia y usuarios afectados
Cuándo No Usar
- Desarrollo local — usa el depurador y la salida de consola
- Aplicaciones sin manejo de errores — arregla primero tus bloques try/catch
- Herramientas internas de poco tráfico — el plan gratuito de Sentry tiene límite de eventos
- Solo envío de logs — Sentry registra errores y spans, no logs generales (para eso mira logging estructurado con Pino)
Solución
Instalación
npm install @sentry/node
Inicializa antes de cargar tu app
El SDK parchea http, express y otros módulos en el momento del import — así que Sentry.init() debe ejecutarse antes que el código de tu aplicación. La forma más limpia es un archivo instrument.js aparte:
// instrument.js
const Sentry = require("@sentry/node");
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV || "development",
tracesSampleRate: 0.1,
});
Cárgalo primero en app.js:
require("./instrument"); // debe ser el primer require
const Sentry = require("@sentry/node");
const express = require("express");
const app = express();
app.get("/api/users/:id", (req, res) => {
if (req.params.id === "0") {
throw new Error("Invalid user ID");
}
res.json({ id: req.params.id, name: "Alice" });
});
// Regístralo después de todas las rutas — captura los errores que llegan hasta él
Sentry.setupExpressErrorHandler(app);
app.listen(3000);
En proyectos ESM, deja Sentry.init() en instrument.mjs y arranca con node --import ./instrument.mjs app.js — el flag garantiza el orden de importación.
Captura manual de errores
Sentry.captureException(new Error("Something went wrong"));
Sentry.captureMessage("Rate limit approaching", "warning");
try {
JSON.parse("{invalid}");
} catch (err) {
Sentry.captureException(err);
}
Añadir contexto a los errores
// Establece el usuario — se adjunta a cada evento hasta que se limpia
Sentry.setUser({
id: 42,
email: "alice@example.com",
username: "alice",
});
// Los tags son filtrables en la UI de Sentry
Sentry.setTag("page_locale", "en-US");
Sentry.setTag("feature_flag", "new_checkout");
// El contexto extra es buscable pero no filtrable
Sentry.setExtra("order_id", "ord-123");
Sentry.setExtra("cart_items", 3);
// Limpia el usuario tras el logout
Sentry.setUser(null);
Breadcrumbs para depurar
Sentry.addBreadcrumb({
category: "auth",
message: "User authenticated",
level: "info",
data: { method: "oauth", provider: "google" },
});
Sentry.addBreadcrumb({
category: "http",
message: "GET /api/users/42",
level: "info",
data: { status_code: 200, duration_ms: 45 },
});
// Si después ocurre un error, el evento muestra estos breadcrumbs
Span personalizado para monitorizar rendimiento
startTransaction desapareció en v8 — usa startSpan con un callback; el span termina solo cuando el callback acaba y los spans anidados pasan a ser hijos:
await Sentry.startSpan(
{ name: "process_order", op: "function" },
async (span) => {
await Sentry.startSpan({ name: "validate_payment", op: "task" }, async () => {
// ... validar el pago ...
});
await Sentry.startSpan({ name: "save_order", op: "db" }, async () => {
// ... guardar en la base de datos ...
});
span.setAttribute("order_id", "ord-123");
}
);
Seguimiento de releases
Sentry.init({
dsn: process.env.SENTRY_DSN,
release: process.env.SENTRY_RELEASE || "1.0.0",
environment: process.env.NODE_ENV,
});
// Sentry ahora agrupa los errores por release y marca regresiones
Source maps para stack traces legibles
npm install @sentry/cli --save-dev
Sube los source maps durante el build:
sentry-cli sourcemaps upload --release 1.0.0 ./dist
O configúralos en sentry.properties:
defaults.url=https://sentry.io
defaults.org=your-org
defaults.project=your-project
auth.token=your-auth-token
Si puedes, mantén el token en SENTRY_AUTH_TOKEN — los tokens commiteados se filtran.
Integración con Winston
const winston = require("winston");
const Sentry = require("@sentry/node");
const sentryTransport = {
log(info) {
if (info.level === "error") {
Sentry.captureException(new Error(info.message));
}
return true;
},
};
const logger = winston.createLogger({
transports: [new winston.transports.Console(), sentryTransport],
});
logger.error("Database connection failed");
// Envía tanto a la consola como a Sentry
Para una configuración de Winston lista para producción con rotación de archivos, mira Winston daily rotate.
Filtrado y muestreo
Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 0.1, // 10% de las transacciones
ignoreErrors: [
"ResizeObserver loop limit exceeded",
"Non-Error promise rejection captured",
],
beforeSend(event) {
if (event.request?.url?.includes("/health")) {
return null; // No enviar el ruido del health check
}
return event;
},
});
Para controlar qué errores de ruta captura setupExpressErrorHandler, pasa shouldHandleError a través de la integración:
Sentry.init({
dsn: process.env.SENTRY_DSN,
integrations: [
Sentry.expressIntegration({
shouldHandleError(error) {
const status = Number(error.status ?? error.statusCode ?? 500);
return status >= 500; // por defecto: solo los 5xx llegan a Sentry
},
}),
],
});
Explicación
El SDK v8 funciona por auto-instrumentación: al importar @sentry/node, envuelve http, express y otros módulos para que las peticiones entrantes, las llamadas salientes y los errores lanzados generen spans y eventos sin tocar tus handlers. Por eso importa el orden de carga — si Express se importa antes de Sentry.init(), los hooks de instrumentación se enganchan demasiado tarde y los errores pasan en silencio. El archivo instrument.js (o el flag --import en ESM) existe precisamente para garantizar ese orden.
setupExpressErrorHandler(app) registra el middleware de error de Sentry al final de tu cadena de middleware. Por defecto solo captura errores 5xx — los 4xx se consideran errores de cliente esperados y se ignoran, lo que reserva tu cuota para los fallos de verdad. shouldHandleError permite ampliar o estrechar ese filtro.
Un caso límite que conviene conocer: Express 4 no reenvía los errores lanzados dentro de handlers async al middleware de error. La integración de Sentry cubre la mayoría de los casos, pero si ves eventos que faltan en Express 4, envuelve los handlers con un asyncHandler (ver el FAQ) o actualiza a Express 5, que reenvía los rechazos de forma nativa.
El flujo es este:
tracesSampleRate controla solo la parte de rendimiento — los errores siempre se envían al 100%. Una tasa de 0.1 retiene el 10% de las transacciones, suficiente para detectar spans lentos sin quemar cuota. beforeSend se ejecuta sobre cada evento antes de salir de tu proceso; devolver null lo descarta, que es donde debe morir el ruido de health checks y scrapers.
Para correlacionar peticiones de extremo a extremo entre servicios, los spans que crea Sentry alimentan directamente el trazado distribuido.
Variantes
Usar Sentry con Fastify
Fastify tiene su propia integración en v8 — registra setupFastifyErrorHandler una vez, sin hooks manuales:
require("./instrument");
const Sentry = require("@sentry/node");
const Fastify = require("fastify");
const fastify = Fastify();
// Registra hooks que capturan errores de rutas y plugins
Sentry.setupFastifyErrorHandler(fastify);
fastify.get("/api/error", async () => {
throw new Error("Fastify error");
});
Usar Sentry con TypeScript (ESM)
import * as Sentry from "@sentry/node";
Sentry.init({
dsn: process.env.SENTRY_DSN,
integrations: [Sentry.httpIntegration()],
tracesSampleRate: 0.1,
});
interface UserContext {
id: string;
email: string;
}
function setSentryUser(user: UserContext): void {
Sentry.setUser(user);
}
Mejores Prácticas
- Para una guía más completa, consulta Sentry: Seguimiento de Errores, Triage y Resolución.
- Inicializa en un
instrument.jsaparte cargado antes de tu app — el orden de importación decide si la auto-instrumentación funciona. - Registra
setupExpressErrorHandler(app)después de todas las rutas — solo ve los errores que llegan al final de la cadena. - Establece
releasecon tu SHA de commit o tag de versión — te permite detectar regresiones entre despliegues. - Sube los source maps en CI — los stack traces minificados son inútiles sin ellos.
- Usa
beforeSendpara descartar ruido (health checks, 404s de assets, errores de extensiones). - Pon
tracesSampleRatea 0.1 en producción — 1.0 quema tu cuota de eventos rápido. - Usa
setUser()para identificar quién sufrió el error — de oro a la hora de responder tickets de soporte. - Añade breadcrumbs en las acciones clave (auth, llamadas a API, cambios de estado) para reconstruir el camino hasta el error.
Errores Comunes
- Importar Sentry después de la app: si Express carga antes que
Sentry.init(), la auto-instrumentación nunca se engancha y los errores pasan sin reportar. - Registrar el manejador de error demasiado pronto:
setupExpressErrorHandlerantes de tus rutas solo captura errores del middleware que se ejecuta después. - Poner el DSN en el código: usa una variable de entorno. Commitear el DSN lo expone.
- No subir los source maps: el código minificado produce stack traces ilegibles. Súbelos siempre en CI.
tracesSampleRate: 1.0en producción: vale para staging, caro con tráfico real.- No limpiar el contexto de usuario en el logout: los errores posteriores al logout se atribuyen al usuario anterior.
Lectura Adicional
- Documentación de la integración Express de Sentry: configuración y opciones oficiales del SDK actual.
- Guía de migración a Sentry JavaScript v8: todos los cambios de API si vienes de
Sentry.Handlers. - Sentry: Seguimiento de Errores, Triage y Resolución: la guía en profundidad con flujos de triage y ajuste de alertas.
Código companion: proyecto starter de Express + Sentry — instrument.js, un app.js ejecutable con el manejador de error conectado y un .env.example.
Preguntas frecuentes
¿Cómo compruebo que Sentry funciona?
Crea una ruta de prueba:
app.get("/debug-sentry", () => {
throw new Error("Sentry test error");
});
Visita la ruta y revisa tu panel de Sentry buscando el error.
¿Cuál es la diferencia entre captureException y captureMessage?
captureException recibe un objeto Error e incluye el stack trace. captureMessage recibe un string y crea un evento a nivel de mensaje. Usa captureException para errores y captureMessage para avisos o información.
¿Cómo manejo los errores asíncronos?
Express 5 reenvía las promesas rechazadas de los handlers async al middleware de error automáticamente. En Express 4, envuelve los handlers async para que los rechazos lleguen al manejador de error:
const asyncHandler = (fn) => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
app.get("/api/async", asyncHandler(async (req, res) => {
const data = await fetchData();
res.json(data);
}));
¿Puedo usar Sentry también para errores de frontend?
Sí. Instala @sentry/react o @sentry/vue para el frontend. Usa la misma organización y proyecto — Sentry separa los errores de frontend y backend por plataforma.
¿Cómo reduzco los costes de Sentry?
- Baja
tracesSampleRate(0.01 en apps de mucho tráfico) - Usa
ignoreErrorsybeforeSendpara filtrar el ruido conocido - Mantén
shouldHandleErroren el filtro por defecto de 5xx para que los 4xx no consuman cuota - Pon
maxBreadcrumbsa 20 en lugar de los 100 por defecto
Recursos Relacionados
Logging de Alta Performance con pino
Cómo usar pino para logging estructurado JSON rápido en Node.js, incluyendo niveles de log, child loggers, transports e integración con Express y Fastify.
RecipeRotar Logs Diariamente con Winston
Cómo configurar rotación diaria de logs en Node.js usando winston y winston-daily-rotate-file, incluyendo límites de tamaño, retención, compresión y combinación de transports.
GuideTrazado distribuido: OpenTelemetry, Jaeger y Zipkin
Domina el trazado distribuido con OpenTelemetry, Jaeger y Zipkin. Propagación de trazas entre servicios, contexto de span, estrategias de muestreo y depuración en producción.
RecipeAgregación de Logs
Centraliza logs de servicios distribuidos con ELK, Fluentd y Loki para búsqueda, alertado y troubleshooting en producción.
RecipeMonitoreo de APIs con Prometheus
Monitorea rendimiento y salud de APIs con métricas Prometheus, collectors personalizados y reglas de alertamiento.
RecipeLogging Estructurado JSON con structlog
Cómo emitir logs estructurados JSON en Python usando structlog, incluyendo context binding, niveles de log, processors e integración con standard logging.