Logging y monitoreo con Claude Code: guía práctica de observabilidad
Diseña logs estructurados, OpenTelemetry, alertas y handoff de incidentes con Claude Code.
Define primero el contrato operativo
Si le pides a Claude Code “añade logs”, lo normal es recibir más console.log. Eso ayuda en local, pero no basta durante un incidente real. Una base seria de logging y monitoreo debe responder qué acción de usuario inició la petición, qué requestId la acompaña, qué servicio se volvió lento, qué dependencia falló y qué información se puede pasar de forma segura al siguiente responsable.
El Observability primer de OpenTelemetry explica la observabilidad como la capacidad de entender un sistema desde fuera y contestar preguntas nuevas. La página What is OpenTelemetry? recuerda además que OpenTelemetry no es el backend de observabilidad, sino una capa neutral de instrumentación. Este artículo convierte esa idea en prompts para Claude Code, snippets copiables, reglas de alerta, revisión de dashboards y formato de handoff.
Masa lo probó en una API pequeña de checkout. El primer prompt decía “haz los logs más detallados”, y Claude Code propuso registrar todo el body cuando fallaba un pago. No había número de tarjeta, pero sí podía haber email, cupón, dirección y notas del cliente. Al cambiar el prompt para empezar por los campos prohibidos, usar requestId y traceparent, y exigir pruebas de redacción, la revisión pasó de “parece razonable” a “podemos usarlo con datos de producción”.
| Señal | Pregunta principal | Restricción para Claude Code |
|---|---|---|
| Logs | Qué ocurrió | JSON, campos fijos, redacción de PII |
| Métricas | Qué tan grave es | rate, p95, tasa de errores |
| Trazas | Dónde se fue el tiempo | propagar traceparent y nombrar spans |
| Health checks | Si una dependencia sirve | estado y latency por dependencia |
Prompt seguro y reglas en CLAUDE.md
La documentación de memoria de Claude Code explica que CLAUDE.md aporta instrucciones de proyecto, usuario u organización al inicio de cada sesión. Para observabilidad, pon ahí niveles de log, nombres de campos, datos prohibidos, comandos de test, dashboards y plantilla de incidente. Combínalo con buenas prácticas de CLAUDE.md y con las páginas oficiales de Claude Code permissions y hooks.
Claude Code task:
- Add observability to the checkout API only.
- Keep all changes inside src/checkout and tests/checkout.
- Use structured JSON logs with requestId and traceparent.
- Never log passwords, tokens, cookies, email, phone, address,
raw prompt text, or full request/response bodies.
- Add tests proving redaction and requestId propagation.
- Add a /healthz report with database and cache latency.
- Add alert rules for 5xx rate, p95 latency, and redaction failure.
- Show a diff summary and remaining manual checks at the end.
El prompt es estrecho a propósito. No deja que Claude Code cambie rutas sin relación, invente una plataforma de monitoreo ni use logs crudos de producción como contexto. Pide un diff operativo que se pueda revisar, probar y revertir.
Logs estructurados y correlación
La OWASP Logging Cheat Sheet trata el logging como una capacidad de seguridad: hay que revisar campos, probar inyección de logs, controlar acceso y simular fallos del propio sistema de logs. Por eso conviene pedir a Claude Code tests de redacción y de propagación, no solo mensajes bonitos.
Este logger JSON no usa dependencias. Guárdalo como structured-logger.mjs y ejecútalo con Node.js 18 o superior.
import { randomUUID } from "node:crypto";
const rank = { debug: 10, info: 20, warn: 30, error: 40 };
const current = process.env.LOG_LEVEL || "info";
const threshold = rank[current] ?? rank.info;
const secretKeys = [
"password",
"token",
"authorization",
"cookie",
"set-cookie",
"apikey",
];
function cleanText(value) {
return String(value).replace(/[\r\n\t]/g, " ").slice(0, 500);
}
function redact(value) {
if (Array.isArray(value)) return value.map(redact);
if (!value || typeof value !== "object") return value;
return Object.fromEntries(
Object.entries(value).map(([key, item]) => {
if (secretKeys.includes(key.toLowerCase())) {
return [key, "[REDACTED]"];
}
return [key, redact(item)];
}),
);
}
export function log(level, message, fields = {}) {
if ((rank[level] ?? 99) < threshold) return;
const entry = {
ts: new Date().toISOString(),
level,
service: process.env.SERVICE_NAME || "checkout-api",
env: process.env.NODE_ENV || "development",
requestId: fields.requestId || randomUUID(),
msg: cleanText(message),
...redact(fields),
};
process.stdout.write(`${JSON.stringify(entry)}\n`);
}
log("info", "payment accepted", {
requestId: "req_demo_001",
userId: "user_123",
amount: 4980,
token: "sk_live_should_not_leak",
});
En una aplicación web, devuelve el ID de petición en la respuesta y guárdalo en contexto asíncrono. La especificación W3C Trace Context describe cómo crear o propagar traceparent. Usa x-request-id para la correlación de tu aplicación y traceparent para trazas distribuidas.
import { AsyncLocalStorage } from "node:async_hooks";
import { randomUUID } from "node:crypto";
import type { Request, Response, NextFunction } from "express";
import { log } from "./structured-logger";
type RequestContext = {
requestId: string;
traceparent?: string;
userId?: string;
};
const storage = new AsyncLocalStorage<RequestContext>();
export function getRequestContext() {
return storage.getStore();
}
export function requestContext(
req: Request,
res: Response,
next: NextFunction,
) {
const started = performance.now();
const user = (req as Request & { user?: { id?: string } }).user;
const requestId =
req.get("x-request-id") ||
req.get("cf-ray") ||
randomUUID();
const context = {
requestId,
traceparent: req.get("traceparent"),
userId: user?.id,
};
res.setHeader("x-request-id", requestId);
storage.run(context, () => {
res.on("finish", () => {
const durationMs = Math.round(performance.now() - started);
const level = res.statusCode >= 500
? "error"
: res.statusCode >= 400
? "warn"
: "info";
log(level, "http request completed", {
requestId,
method: req.method,
path: req.path,
statusCode: res.statusCode,
durationMs,
});
});
next();
});
}
Mantén los niveles simples: debug para detalles locales, info para eventos normales importantes, warn para riesgos recuperables y error para investigación humana. Claude Code no debe cambiar el flujo de negocio si un log falla.
OpenTelemetry básico
OpenTelemetry ayuda a correlacionar logs, métricas y trazas antes de enviarlas al backend que ya use tu equipo. Para Node.js, verifica siempre la guía oficial de JavaScript Node.js y la de exporters JavaScript.
npm install @opentelemetry/sdk-node \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-proto \
@opentelemetry/exporter-metrics-otlp-proto \
@opentelemetry/sdk-metrics
const opentelemetry = require("@opentelemetry/sdk-node");
const {
getNodeAutoInstrumentations,
} = require("@opentelemetry/auto-instrumentations-node");
const {
OTLPTraceExporter,
} = require("@opentelemetry/exporter-trace-otlp-proto");
const {
OTLPMetricExporter,
} = require("@opentelemetry/exporter-metrics-otlp-proto");
const {
PeriodicExportingMetricReader,
} = require("@opentelemetry/sdk-metrics");
process.env.OTEL_SERVICE_NAME ||= "checkout-api";
const endpoint =
process.env.OTEL_EXPORTER_OTLP_ENDPOINT ||
"http://localhost:4318";
const sdk = new opentelemetry.NodeSDK({
traceExporter: new OTLPTraceExporter({
url: `${endpoint}/v1/traces`,
}),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({
url: `${endpoint}/v1/metrics`,
}),
exportIntervalMillis: 30000,
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
process.on("SIGTERM", () => {
sdk.shutdown().finally(() => process.exit(0));
});
flowchart LR
A["Accion de usuario"] --> B["Aplicacion"]
B --> C["Log estructurado"]
B --> D["Metrica"]
B --> E["Trace span"]
C --> F["Almacen de logs"]
D --> G["Regla de alerta"]
E --> H["Backend de trazas"]
F --> I["Handoff de incidente"]
G --> I
H --> I
Health checks y alertas
Un buen health check no es solo 200 OK. Debe revisar base de datos, cache, cola y APIs externas por separado, devolver latency y evitar secretos.
function timeout(ms) {
return new Promise((_, reject) => {
setTimeout(() => reject(new Error("timeout")), ms);
});
}
export async function buildHealthReport(checks) {
const started = Date.now();
const results = {};
for (const [name, check] of Object.entries(checks)) {
const before = Date.now();
try {
await Promise.race([check(), timeout(800)]);
results[name] = {
status: "ok",
latencyMs: Date.now() - before,
};
} catch (error) {
const message =
error instanceof Error ? error.message : String(error);
results[name] = {
status: "fail",
latencyMs: Date.now() - before,
reason: message.slice(0, 120),
};
}
}
const failed = Object.values(results)
.filter((item) => item.status === "fail")
.length;
return {
status: failed ? "degraded" : "ok",
uptimeSec: Math.round(process.uptime()),
totalLatencyMs: Date.now() - started,
checks: results,
};
}
Las alertas deben basarse en tasas y percentiles, no en una única línea ruidosa.
groups:
- name: checkout-api
rules:
- alert: CheckoutHigh5xxRate
expr: |
sum(rate(http_requests_total{
service="checkout-api",
status_code=~"5.."
}[5m]))
/
sum(rate(http_requests_total{
service="checkout-api"
}[5m])) > 0.02
for: 10m
labels:
severity: page
annotations:
summary: "Checkout 5xx rate is above 2%"
- alert: CheckoutP95LatencyHigh
expr: |
histogram_quantile(
0.95,
sum by (le) (
rate(http_request_duration_seconds_bucket{
service="checkout-api"
}[5m])
)
) > 1.5
for: 15m
labels:
severity: ticket
annotations:
summary: "Checkout p95 latency is above 1.5s"
Tres casos de uso concretos
El primero es una API de checkout ecommerce. Conserva orderId, requestId, paymentProvider y amount, pero nunca tarjeta, email, dirección ni token. Separa alertas de tasa 5xx, fallos de pago y latencia p95 del proveedor.
El segundo es un panel administrativo SaaS. Login, cambios de permisos, invitaciones y cambios de plan deben ir a audit logs. El cuerpo del email de invitación y notas privadas no. Pide a Claude Code separar audit logs de logs de aplicación, distinguir actor ID y target user ID, y añadir tests RBAC.
El tercero es un sitio de medios o blog CMS. Rastrea publicación, clics CTA, formularios enviados, errores de generación de imágenes y traducciones incompletas. Solo mirar page views no mejora ingresos. Separa cta_click y generate_lead, y revisa la guía de analytics con Claude Code.
Si tienes microservicios, combina esto con la guía de microservicios. Si service.name, rutas o etiquetas de entorno cambian entre servicios, OpenTelemetry se vuelve difícil de consultar.
Fallos comunes e incidente handoff
Los fallos típicos son previsibles: registrar todo el body, usar mensajes libres imposibles de consultar, crear demasiados IDs de correlación, devolver ok en /healthz sin revisar dependencias y crear alertas sin dueño. Con Claude Code hay otro riesgo: pegar logs crudos en el prompt. Antes de pedir ayuda, enmascara logs, comparte métricas agregadas y usa IDs cortos.
{
"incident_id": "INC-2026-06-02-001",
"severity": "SEV2",
"owner": "oncall-api",
"customer_impact": "Checkout errors for some card payments",
"first_seen": "2026-06-02T09:15:00+09:00",
"request_ids": ["req_7f3a", "req_8b21"],
"trace_ids": ["7bba9f33312b3dbb8b2c2c62bb7abe2d"],
"dashboards": ["Checkout API overview"],
"current_hypothesis": "Payment provider latency spike",
"actions_taken": ["Disabled checkout_v2 feature flag"],
"next_checks": ["Compare p95 latency by region"],
"do_not_do": ["Do not paste raw customer data into prompts"]
}
Para una investigación segura, lee también la guía de permisos.
Revisión, CTA y verificación
Revisa dashboards cada semana: cinco errores principales, p95, crecimiento de volumen de logs, alertas falsas, fallos de redacción e incidentes abiertos. Cada mes toma un incidente real y pregunta si logs, métricas y trazas habrían guiado más rápido a una persona nueva de guardia.
Para empezar solo, usa el cheatsheet gratuito. Para prompts y material reutilizable, revisa la página de productos. Si tu equipo necesita estándares de logging, CLAUDE.md, permisos, CI e incidentes sobre un repositorio real, la consultoría y formación Claude Code es el siguiente paso.
Al probar este flujo, lo que más redujo retrabajo fue escribir primero los campos prohibidos. structured-logger.mjs convirtió el token en [REDACTED] y normalizó saltos de línea. Al simular un fallo de cache, el health check pasó a degraded; con solo requestId y traceId en el handoff, la revisión fue mucho más corta.
PDF gratis: cheatsheet de Claude Code
Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.
Cuidamos tus datos y no enviamos spam.
Sobre el autor
Masa
Ingeniero enfocado en workflows prácticos con Claude Code.
Artículos relacionados
Permission receipt para Claude Code: alcance, prueba y rollback
Patrón de permission receipt para Claude Code: acciones permitidas, aprobación, pruebas, rollback y CTA de ingresos.
Agent Harness seguro para Claude Code y Codex: permisos, verificacion y rollback
Diseña un Agent Harness seguro para Claude Code y Codex con permisos, plan, verificaciones y rollback.
Subagentes de Claude Code: guía práctica para delegar trabajo de forma segura
Guía práctica de subagentes en Claude Code para dividir artículos y código: reglas, prompts, riesgos y checklist.