Logs y Observabilidad
Logging estructurado para Node, Edge y Workers con IDs de solicitud, redacción de secretos y ejemplos por ruta. Funciona en Vercel, Cloudflare y servidores Node.
Conceptos básicos
- Logs estructurados: emite objetos JSON, no cadenas sueltas; facilita buscar y correlacionar.
- IDs de correlación: añade
request_id
a todos los logs de una solicitud. - Niveles:
debug
en dev,info
normal,warn
anomalías,error
fallos. - Redacción: nunca registres secretos, tokens o cookies.
Qué incluye
- API de logging común para Node y Edge/Workers (misma forma).
- Node (funciones de servidor Next, serverless, Node): Pino rápido con JSON.
- Edge/Workers (Vercel Edge, Cloudflare Workers, middleware): implementación ligera con
console.log
JSON.
- Propagación de
request_id
vía middleware (sin tocar cookies). - Ejemplo conectado en la ruta de presign de almacenamiento.
Archivos
- Node:
src/lib/logger/server.ts
- Edge:
src/lib/logger/edge.ts
- Tipos:
src/lib/logger/types.ts
- Middleware:
src/middleware.ts
- Ejemplo:
src/app/api/storage/uploads/route.ts
Archivos clave
- Node:
src/lib/logger/server.ts
- Edge:
src/lib/logger/edge.ts
- Tipos:
src/lib/logger/types.ts
- Middleware:
src/middleware.ts
- Ejemplo:
src/app/api/storage/uploads/route.ts
Dónde ver los logs
- Local: consola del terminal (salida JSON de una línea). Para legibilidad usa
pino-pretty
:pnpm dev 2>&1 | pnpx pino-pretty
- guardar en archivo:
pnpm dev 2>&1 | pnpx pino-pretty | tee logs/dev.log
- Vercel: Deployments → Functions → Logs, o
vercel logs
. - Cloudflare Workers:
wrangler tail
o panel de Logs.
Uso (Node)
import { logger, requestIdFromHeaders } from '@/lib/logger/server'
export async function POST(req: Request){
const rid = requestIdFromHeaders(req.headers)
const log = logger.child({ request_id: rid, route: '/api/ejemplo' })
log.info({ event: 'ejemplo.start' })
// ...
log.info({ event: 'ejemplo.ok' })
return new Response('ok')
}
Uso (Edge)
import { logger } from '@/lib/logger/edge'
export const runtime = 'edge'
export function GET(){
logger.info({ event: 'edge.heartbeat' })
return new Response('ok')
}
Config
LOG_LEVEL
y reglas de redacción en los archivos del logger.- No hay archivo de logs por defecto: escribimos a stdout/stderr. En local, usa
tee
si quieres guardar:pnpm dev 2>&1 | tee logs/dev.json
.
Uso (Node)
import { logger, requestIdFromHeaders } from '@/lib/logger/server'
export async function POST(req: Request){
const rid = requestIdFromHeaders(req.headers)
const log = logger.child({ request_id: rid, route: '/api/ejemplo' })
const start = Date.now()
try {
log.info({ event: 'ejemplo.start' })
// ...
log.info({ event: 'ejemplo.ok', duration_ms: Date.now() - start })
return new Response('ok')
} catch (e:any) {
log.error({ event: 'ejemplo.error', message: e?.message })
return new Response('error', { status: 500 })
}
}
Uso (Edge)
import { logger } from '@/lib/logger/edge'
export const runtime = 'edge'
export function GET(){
logger.info({ event: 'edge.heartbeat' })
return new Response('ok')
}
Helper: withApiLogging (Node)
import { withApiLogging } from '@/lib/logger/server'
export const POST = withApiLogging(async (req) => {
// lógica
return new Response('ok')
}, { route: '/api/foo', event: 'foo.process' })
Ejemplo ya conectado
src/app/api/storage/uploads/route.ts
emitestorage.presign.create
con{ request_id, user_id, file_id, key, size, content_type, bucket }
y erroresstorage.presign.create.error
.
Buenas prácticas
- Usa
request_id
en loggers hijo por petición y añadeduration_ms
al finalizar. - Mensajes cortos; prioriza campos.
- Evita registrar cuerpos completos; mejor hashes/campos puntuales.
Redacción a medida
- Node: edita
redactPaths
ensrc/lib/logger/server.ts
(Pino reemplaza por[REDACTED]
). - Edge: edita
redactKeys
ensrc/lib/logger/edge.ts
.
Sustituir console.*
- En
src/app/api/**
ysrc/services/**
, importalogger
(Node) y usalogger.info|warn|error
. - En cliente, limita
console
y usa un capturador de errores (p. ej., Sentry).
Integraciones opcionales
- Sentry (
@sentry/nextjs
) para excepciones y trazas. - Sinks gestionados (Axiom, Better Stack, Datadog, New Relic):
- Node: transport/Pino o captura stdout.
- Edge: ingesta HTTP o logs de plataforma.
Solución de problemas
- Redirecciones inesperadas a login: asegúrate de que el middleware no sobrescribe cabeceras de solicitud/cookies.
- Falta de logs en Edge: verifica
runtime = 'edge'
y usawrangler tail
/Vercel logs.
Buenas prácticas
- Usa
request_id
yduration_ms
; evita cuerpos completos. - Sustituye
console.*
por el logger en código del servidor.
Cargas de archivos privadas (S3 / R2)
Guía paso a paso, apta para principiantes, para añadir cargas privadas por usuario con almacenamiento compatible con S3. Incluye conceptos, configuración, variables de entorno, API, UI, errores y migración S3↔R2.
Servicio de Email (Resend)
Integra email transaccional con Resend. Verifica tu dominio, crea claves API, renderiza plantillas de bienvenida y pago en el servidor y envíalas a través de Resend con configuración por entorno.