Advanced (Actualizado: 2/6/2026)

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.

Logging y monitoreo con Claude Code: guía práctica de observabilidad

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ñalPregunta principalRestricción para Claude Code
LogsQué ocurrióJSON, campos fijos, redacción de PII
MétricasQué tan grave esrate, p95, tasa de errores
TrazasDónde se fue el tiempopropagar traceparent y nombrar spans
Health checksSi una dependencia sirveestado 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.

#Claude Code #logging #monitoreo #OpenTelemetry #observabilidad
Gratis

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.

Masa

Sobre el autor

Masa

Ingeniero enfocado en workflows prácticos con Claude Code.