Skip to content
StackPractices
intermediate Por Mathias Paulenko

Error Tracking con Sentry en Express

Cómo integrar Sentry para error tracking en aplicaciones Node.js Express, incluyendo error handlers, performance monitoring, release tracking y source maps.

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Overview

Sentry captura errores no manejados en tu aplicación, los agrupa por similitud y envía alerts. Muestra el stack trace, context del request, info del usuario y breadcrumbs (eventos que llevaron al error). En Express, el middleware de Sentry envuelve el pipeline de requests para que cualquier error thrown o unhandled rejection se capture automáticamente.

When to Use

  • Aplicaciones en producción donde necesitas alerts de error en tiempo real
  • Trackear error rates y detección de regresión a través de releases
  • Capturar bugs reportados por usuarios con stack traces completos y context del request
  • Monitorear performance (transaction tracing) junto con error tracking
  • Triage de bugs por severidad, frecuencia y usuarios afectados

When NOT to Use

  • Desarrollo local — usa el debugger y output de consola
  • Aplicaciones sin error handling — arregla tus try/catch blocks primero
  • Herramientas internas de bajo tráfico — el free tier de Sentry tiene límites de eventos
  • Observability solo con logging — Sentry es para errores, no para shipping general de logs

Solution

Setup

npm install @sentry/node

Integración básica con Express

const express = require("express");
const Sentry = require("@sentry/node");

Sentry.init({
  dsn: "https://your-dsn@sentry.io/123",
  environment: process.env.NODE_ENV || "development",
  tracesSampleRate: 0.1,
});

const app = express();

// El request handler de Sentry debe ser el primer middleware
app.use(Sentry.Handlers.requestHandler());

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" });
});

// El error handler de Sentry debe ir después de todas las rutas
app.use(Sentry.Handlers.errorHandler());

app.listen(3000);

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);
}

Agregar context a errores

// Setear user context
Sentry.setUser({
  id: 42,
  email: "alice@example.com",
  username: "alice",
});

// Setear tags (filtrables en el UI de Sentry)
Sentry.setTag("page_locale", "en-US");
Sentry.setTag("feature_flag", "new_checkout");

// Setear extra context (searchable pero no filterable)
Sentry.setExtra("order_id", "ord-123");
Sentry.setExtra("cart_items", 3);

// Limpiar user después de logout
Sentry.setUser(null);
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 un error ocurre después, Sentry muestra estos breadcrumbs

Transaction personalizada para performance monitoring

const transaction = Sentry.startTransaction({
  name: "process_order",
  op: "function",
});

try {
  const span = transaction.startChild({ op: "validate_payment" });
  // ... validar pago ...
  span.finish();

  const dbSpan = transaction.startChild({ op: "save_order" });
  // ... guardar en DB ...
  dbSpan.finish();
} catch (err) {
  Sentry.captureException(err);
} finally {
  transaction.finish();
}

Release tracking

Sentry.init({
  dsn: "https://your-dsn@sentry.io/123",
  release: process.env.SENTRY_RELEASE || "1.0.0",
  environment: process.env.NODE_ENV,
});

// Sentry ahora agrupa errores por release y muestra detección de regresión

Source maps para stack traces legibles

npm install @sentry/cli --save-dev
// sentry.config.js
module.exports = {
  org: "your-org",
  project: "your-project",
  authToken: process.env.SENTRY_AUTH_TOKEN,
  release: process.env.SENTRY_RELEASE,
};

Sube source maps durante el build:

sentry-cli sourcemaps upload --release 1.0.0 ./dist

Integración con Winston

const winston = require("winston");
const Sentry = require("@sentry/node");

const logTransport = new winston.transports.Console();

const sentryTransport = {
  log(info) {
    if (info.level === "error") {
      Sentry.captureException(new Error(info.message));
    }
    return true;
  },
};

const logger = winston.createLogger({
  transports: [logTransport, sentryTransport],
});

logger.error("Database connection failed");
// Envía tanto a console como a Sentry

Filtrado y sampling

Sentry.init({
  dsn: "https://your-dsn@sentry.io/123",
  tracesSampleRate: 0.1, // 10% de transactions
  ignoreErrors: [
    "ResizeObserver loop limit exceeded",
    "Non-Error promise rejection captured",
  ],
  denyUrls: [
    /extensions\//i,
    /safari-extension/i,
  ],
  beforeSend(event) {
    if (event.request.url.includes("/health")) {
      return null; // No enviar errores de health check
    }
    return event;
  },
});

Variants

Usar Sentry con Fastify

const Fastify = require("fastify");
const Sentry = require("@sentry/node");

Sentry.init({ dsn: "https://your-dsn@sentry.io/123" });

const fastify = Fastify();

fastify.addHook("onRequest", async (request, reply) => {
  request.sentry = Sentry.getCurrentHub();
});

fastify.addHook("onError", async (request, reply, error) => {
  Sentry.captureException(error);
});

fastify.get("/api/error", async () => {
  throw new Error("Fastify error");
});

Usar Sentry con TypeScript

import * as Sentry from "@sentry/node";

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  integrations: [
    new Sentry.Integrations.Http({ tracing: true }),
  ],
  tracesSampleRate: 0.1,
});

interface UserContext {
  id: string;
  email: string;
}

function setSentryUser(user: UserContext): void {
  Sentry.setUser(user);
}

Best Practices

  • For a deeper guide, see Sentry: Error Tracking, Triage, and Resolution.

  • Coloca Sentry.Handlers.requestHandler() antes de todo otro middleware — necesita envolver el request completo

  • Coloca Sentry.Handlers.errorHandler() después de todas las rutas — atrapa errores que se escapan

  • Setea release a tu git commit SHA o version tag — habilita detección de regresión

  • Sube source maps en CI — los stack traces minificados son inútiles sin ellos

  • Usa beforeSend para filtrar ruido (health checks, 404s de assets, errores de extensiones de navegador)

  • Setea tracesSampleRate a 0.1 en producción — 1.0 abruma tu quota de Sentry

  • Usa setUser() para identificar quién experimentó el error — invaluable para tickets de soporte

  • Agrega breadcrumbs para acciones clave (auth, API calls, state changes) para trazar el path del error

Common Mistakes

  • No usar el middleware de error handler: sin Sentry.Handlers.errorHandler(), el error handler default de Express atrapa errores antes que Sentry.
  • Setear DSN en código: usa una environment variable. Commitear el DSN lo expone.
  • No subir source maps: el código minificado produce stack traces ilegibles. Siempre súbelos en CI.
  • Enviar demasiados eventos: sin sampling o filtrado, Sentry envía cada error. Filtra ruido con ignoreErrors y denyUrls.
  • No limpiar user context en logout: los errores después de logout se atribuyen al usuario anterior.

FAQ

¿Cómo testeo que Sentry funciona?

Crea una ruta de test:

app.get("/debug-sentry", () => {
  throw new Error("Sentry test error");
});

Visita la ruta y revisa tu dashboard de Sentry para el error.

¿Cuál es la diferencia entre captureException y captureMessage?

captureException toma un Error object e incluye el stack trace. captureMessage toma un string y crea un evento a nivel de breadcrumb. Usa captureException para errores y captureMessage para warnings o info.

¿Cómo manejo errores async?

El middleware de Sentry para Express atrapa errores async automáticamente en Express 5+. Para Express 4, envuelve los async handlers:

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 para errores frontend también?

Sí. Instala @sentry/react o @sentry/vue para frontend. Usa la misma organización y proyecto — Sentry separa errores frontend y backend por plataforma.

¿Cómo reduzco costos de Sentry?

  • Baja tracesSampleRate (0.01 para apps de alto tráfico)
  • Usa ignoreErrors para filtrar ruido conocido
  • Usa beforeSend para dropear eventos de health checks y assets estáticos
  • Setea maxBreadcrumbs a 20 en lugar del default 100