Módulo 6: Debugging con Claude Code
Log Analysis con Claude Code
Log Analysis con Claude Code
Descripción de la cápsula
Si tuvieras que elegir una sola habilidad de debugging con Claude Code, debería ser esta: log analysis. El 70% de los bugs que encontrarás en tu día a día se pueden diagnosticar analizando logs. Y Claude Code es extraordinariamente bueno en esto — puede procesar 20, 50, o 200 líneas de logs y encontrar la aguja en el pajar mucho más rápido que tú leyendo línea por línea.
Pero hay una trampa: si le pegas solo la línea del error, obtendrás un diagnóstico genérico. Si le pegas 200 líneas sin contexto, obtendrás un diagnóstico disperso. La clave está en qué le das y cómo le pides que lo analice. En esta cápsula aprenderás a preparar logs para Claude Code, a dar el contexto correcto, y lo más importante: a evaluar su diagnóstico antes de actuar.
Qué Puede y Qué No Puede Ver Claude Code
Antes de pasar cualquier log a Claude Code, entiende qué está viendo:
Lo que Claude Code puede analizar
- ✅ Texto de logs que tú le proporcionas (copy/paste)
- ✅ Patrones de timestamps, niveles de severidad, mensajes de error
- ✅ Stack traces completos incluidos en los logs
- ✅ Secuencia de eventos (qué pasó antes del error)
- ✅ Correlación entre mensajes (request → processing → error)
Lo que Claude Code NO puede hacer
- ❌ Acceder a logs en tiempo real de tu aplicación
- ❌ Ejecutar tu aplicación para reproducir el error
- ❌ Ver el estado de variables en el momento del error
- ❌ Acceder a tus archivos de log directamente (necesitas copiar el contenido)
- ❌ Monitorear cambios en los logs mientras haces cambios
La implicación es clara: tú eres quien captura los logs y se los pasa. La calidad del diagnóstico depende directamente de la calidad de lo que le des.
Anatomía de un Buen Log para Diagnosis
No todos los logs son igualmente útiles. Compara estos dos escenarios:
Escenario malo: Solo el error
Tú le pasas a Claude Code:
────────────────────────────
KeyError: 'user_id'
────────────────────────────
Claude Code puede decirte que hay un diccionario donde se busca la clave user_id y no existe. Pero eso ya lo sabías. No tiene suficiente contexto para decirte dónde ni por qué.
Escenario bueno: Error con contexto
Tú le pasas a Claude Code:
────────────────────────────
2026-03-13 14:22:01 INFO Starting request: POST /api/tasks
2026-03-13 14:22:01 INFO Auth middleware: token validated
2026-03-13 14:22:01 INFO Auth middleware: user extracted from token
2026-03-13 14:22:01 DEBUG Request body: {"title": "New task", "priority": "high"}
2026-03-13 14:22:01 INFO TaskService.create_task called
2026-03-13 14:22:01 DEBUG Looking up user: user_data={'email': 'test@example.com', 'role': 'admin'}
2026-03-13 14:22:01 ERROR Unhandled exception in create_task
Traceback (most recent call last):
File "/app/services/task_service.py", line 45, in create_task
owner_id = user_data['user_id']
KeyError: 'user_id'
2026-03-13 14:22:01 ERROR Response: 500 Internal Server Error
────────────────────────────
Ahora Claude Code puede ver:
- El request era
POST /api/tasks - La autenticación pasó correctamente
- El
user_datacontieneemailyrolepero nouser_id - El código en
task_service.py:45esperauser_idpero el token no lo incluye - El fix probable: incluir
user_iden el token o usaremailcomo identificador
La diferencia entre un diagnóstico genérico y uno accionable está en el contexto que proporcionas.
La Regla del Contexto: 5 Líneas Arriba, 5 Líneas Abajo
Como regla general, cuando captures logs para pasarle a Claude Code, incluye al menos 5 líneas antes del error y 5 líneas después (si las hay). Esto captura:
- Antes: Qué operaciones se ejecutaron correctamente, qué datos estaban disponibles
- El error: El mensaje de error y el stack trace
- Después: Cómo respondió el sistema al error, si hubo cascada de errores
Cuándo necesitas más contexto
A veces 5 líneas no son suficientes. Incluye más contexto cuando:
- ✅ El error parece una consecuencia de algo que pasó antes (error de datos, estado incorrecto)
- ✅ Hay múltiples requests en los logs y necesitas aislar cuál falló
- ✅ El log muestra una secuencia de operaciones (inicio → procesamiento → error)
- ✅ El error es intermitente y necesitas comparar una ejecución exitosa con una fallida
Cuándo menos es más
Reduce el contexto cuando:
- ✅ Los logs tienen mucho ruido (health checks, metrics, etc.)
- ✅ Hay información sensible que no debes compartir (tokens, passwords, PII)
- ✅ El error es claro y el stack trace es suficiente
Cómo Pasar Logs a Claude Code: Prompt Templates
La forma en que le pides a Claude Code que analice logs afecta la calidad de la respuesta. Aquí hay templates probados:
Template 1: Diagnóstico general
Analiza estos logs de mi aplicación FastAPI. El endpoint POST /api/tasks
está devolviendo un error 500. Identifica la causa raíz y sugiere un fix.
Logs:
"""
[pegar logs aquí]
"""
Contexto adicional:
- La aplicación usa JWT para autenticación
- El modelo Task tiene campos: id, title, priority, owner_id, created_at
- Este endpoint funcionaba antes del último deploy
Template 2: Comparación exitoso vs fallido
Estos son los logs de dos requests al mismo endpoint. El primero funciona
correctamente, el segundo falla. Compara ambos e identifica qué es diferente
que causa el error.
Request exitoso:
"""
[pegar logs del request que funciona]
"""
Request fallido:
"""
[pegar logs del request que falla]
"""
Template 3: Error intermitente
Este error aparece aproximadamente 1 de cada 10 requests al endpoint
GET /api/users/{id}. Analiza estos logs que muestran 3 requests exitosos
y 1 fallido. ¿Qué patrón ves que podría explicar por qué falla
intermitentemente?
Logs (marcados con [OK] y [FAIL]):
"""
[pegar logs]
"""
Template 4: Secuencia de errores
Mi aplicación empezó a fallar hace 20 minutos. Estos son los primeros
errores que aparecieron. Identifica cuál fue el error ORIGINAL (root cause)
y cuáles son consecuencia de ese primer error.
Logs (ordenados cronológicamente):
"""
[pegar logs]
"""
Ejemplo Completo: Del Log al Fix
Veamos un escenario realista completo. Tu aplicación FastAPI de gestión de tareas tiene un endpoint que falla.
Paso 1: Capturar los logs
Tu aplicación imprime estos logs cuando falla:
2026-03-13 10:15:30 INFO uvicorn.access: "GET /api/tasks?status=completed HTTP/1.1" 200
2026-03-13 10:15:45 INFO uvicorn.access: "GET /api/tasks?status=pending HTTP/1.1" 200
2026-03-13 10:16:02 INFO Starting request: GET /api/tasks/stats
2026-03-13 10:16:02 INFO Auth: user admin@company.com authenticated
2026-03-13 10:16:02 DEBUG TaskService.get_stats called
2026-03-13 10:16:02 DEBUG Querying tasks for stats...
2026-03-13 10:16:02 DEBUG Total tasks: 47
2026-03-13 10:16:02 DEBUG Tasks by status: {'pending': 12, 'in_progress': 8, 'completed': 25, 'cancelled': 2}
2026-03-13 10:16:02 DEBUG Calculating completion rate...
2026-03-13 10:16:02 ERROR Unhandled exception in get_stats
Traceback (most recent call last):
File "/app/services/task_service.py", line 89, in get_stats
completion_rate = completed_count / total_active * 100
ZeroDivisionError: division by zero
2026-03-13 10:16:02 ERROR Response: 500 Internal Server Error
Paso 2: Preparar el prompt para Claude Code
Analiza estos logs de mi aplicación FastAPI. El endpoint GET /api/tasks/stats
devuelve 500. Identifica la causa raíz.
Logs:
"""
[los logs de arriba]
"""
Contexto:
- total_active debería ser total - cancelled
- Este endpoint funciona normalmente, pero falló hoy por primera vez
Paso 3: Diagnóstico de Claude Code (típico)
Claude Code probablemente dirá algo como:
El error es un
ZeroDivisionErroren la línea 89 detask_service.py. La variabletotal_activees 0, lo que causa la división por cero.Mirando los logs, el total de tasks es 47, con la distribución:
- pending: 12, in_progress: 8, completed: 25, cancelled: 2
Si
total_active = total - cancelled = 47 - 2 = 45, no debería ser cero. Esto sugiere que el cálculo detotal_activetiene un bug: probablemente está calculando algo diferente atotal - cancelled.Posible causa:
total_activese calcula filtrando por un status específico que no tiene tasks (por ejemplo, un status nuevo que se agregó pero no tiene registros), ocompleted_countytotal_activeestán usando queries diferentes que no son consistentes.
Paso 4: Verificar el diagnóstico
Ahora no aplicas el fix sugerido ciegamente. Verificas:
- Abres
task_service.pylínea 89 y lees el código real:
from fastapi import APIRouter, Depends, HTTPException
from typing import Dict, Any
from app.models import Task, TaskStatus
from app.database import get_db
from sqlalchemy.orm import Session
router = APIRouter()
class TaskService:
def __init__(self, db: Session):
self.db = db
def get_stats(self) -> Dict[str, Any]:
all_tasks = self.db.query(Task).all()
total = len(all_tasks)
by_status = {}
for task in all_tasks:
status = task.status.value
by_status[status] = by_status.get(status, 0) + 1
completed_count = by_status.get('completed', 0)
total_active = by_status.get('active', 0) # BUG: 'active' no es un status válido
completion_rate = completed_count / total_active * 100 # línea 89
return {
"total": total,
"by_status": by_status,
"completion_rate": completion_rate
}
-
Confirmas el diagnóstico:
total_activebusca el status'active'que no existe enTaskStatus. Debería sertotal - by_status.get('cancelled', 0). -
Claude Code acertó en la dirección pero no en el detalle exacto. Sin ver el código, no podía saber que el bug era buscar un status inexistente.
Paso 5: Aplicar y verificar el fix
completed_count = by_status.get('completed', 0)
cancelled_count = by_status.get('cancelled', 0)
total_active = total - cancelled_count
if total_active == 0:
completion_rate = 0.0
else:
completion_rate = completed_count / total_active * 100
Verificas ejecutando el endpoint de nuevo y confirmando que devuelve datos correctos.
Los 5 Errores Más Comunes al Pasar Logs a Claude Code
Error 1: Pasar solo la última línea del error
# Malo
"ZeroDivisionError: division by zero"
# Bueno
[20+ líneas con contexto: qué request, qué datos, qué operaciones previas]
Error 2: No limpiar información sensible
# Malo - incluye tokens reales
"Auth: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM..."
# Bueno - sanitizado
"Auth: Bearer [TOKEN_REDACTED]"
Error 3: No dar contexto sobre la aplicación
Sin contexto, Claude Code asume una aplicación genérica. Dile qué framework usas, qué base de datos, qué modelo de datos.
Error 4: Aceptar el primer diagnóstico sin verificar
Claude Code da hipótesis. Siempre verifica abriendo el archivo y la línea mencionada. El diagnóstico puede ser 80% correcto pero el 20% incorrecto cambia el fix.
Error 5: No incluir logs de requests exitosos
Si tienes un error intermitente, comparar un request exitoso con uno fallido es la técnica más poderosa. Claude Code puede encontrar la diferencia.
Evaluando el Diagnóstico de Claude Code
No todo lo que Claude Code dice sobre tus logs es correcto. Aquí hay un framework para evaluar:
Nivel de confianza alto
Confía más en el diagnóstico cuando:
- ✅ Claude Code señala una línea específica y explica por qué falla
- ✅ El diagnóstico es consistente con los datos en los logs
- ✅ La explicación incluye causa y efecto (A pasó, lo que causó B, que resultó en C)
- ✅ El error es un tipo común (KeyError, TypeError, ValueError) con causa clara
Nivel de confianza bajo
Desconfía del diagnóstico cuando:
- ⚠️ Claude Code dice "probablemente" o "posiblemente" sin evidencia en los logs
- ⚠️ El diagnóstico no explica por qué el error es intermitente
- ⚠️ La sugerencia de fix es genérica ("agrega un try/except")
- ⚠️ Claude Code sugiere que el problema está en una librería externa sin evidencia
Señales de que Claude Code está adivinando
- ❌ "Esto podría ser causado por..." seguido de 5 posibles causas sin priorizar
- ❌ Sugerencias que no se relacionan con los datos en los logs
- ❌ "El error probablemente está en la configuración" sin señalar qué configuración
- ❌ Diagnósticos que contradicen la información en los logs
Técnica Avanzada: Logs Estructurados
Si configuras tu aplicación con logging estructurado, Claude Code puede hacer análisis mucho más efectivos.
Logging básico vs estructurado
import logging
import json
from datetime import datetime
from typing import Any, Optional
logger = logging.getLogger("app")
def log_basic(message: str):
logger.info(message)
def log_structured(
event: str,
data: Optional[dict[str, Any]] = None,
error: Optional[str] = None
):
log_entry = {
"timestamp": datetime.utcnow().isoformat(),
"event": event,
"data": data or {},
}
if error:
log_entry["error"] = error
logger.info(json.dumps(log_entry))
Ejemplo de uso en un endpoint
from fastapi import FastAPI, HTTPException, Request
from pydantic import BaseModel
from typing import Optional
import logging
import json
from datetime import datetime
app = FastAPI()
logger = logging.getLogger("app")
class TaskCreate(BaseModel):
title: str
priority: str
assignee_id: Optional[int] = None
@app.post("/api/tasks")
async def create_task(task: TaskCreate, request: Request):
logger.info(json.dumps({
"event": "create_task.start",
"data": {
"title": task.title,
"priority": task.priority,
"assignee_id": task.assignee_id,
"client_ip": request.client.host
}
}))
if task.priority not in ["low", "medium", "high", "critical"]:
logger.warning(json.dumps({
"event": "create_task.invalid_priority",
"data": {"priority": task.priority}
}))
raise HTTPException(status_code=400, detail="Invalid priority")
logger.info(json.dumps({
"event": "create_task.success",
"data": {"title": task.title}
}))
return {"status": "created", "title": task.title}
Por qué los logs estructurados son mejores para Claude Code
Cuando le pasas logs estructurados, Claude Code puede:
- ✅ Parsear campos específicos (event, data, error)
- ✅ Correlacionar eventos por timestamp
- ✅ Identificar qué datos estaban disponibles en cada paso
- ✅ Detectar valores inesperados en campos específicos
Logging Efectivo para Debugging con AI
Para maximizar la utilidad de Claude Code en debugging, configura tu logging así:
Qué loggear en cada nivel
import logging
logger = logging.getLogger("app")
# DEBUG: datos internos útiles para debugging
logger.debug(f"Query result: {result}")
logger.debug(f"Cache hit: key={cache_key}")
# INFO: operaciones de negocio normales
logger.info(f"Task created: id={task.id}")
logger.info(f"User logged in: email={user.email}")
# WARNING: situaciones anómalas que no son errores
logger.warning(f"Slow query: {elapsed_ms}ms for {query_name}")
logger.warning(f"Rate limit approaching: {current}/{limit}")
# ERROR: errores que afectan la operación
logger.error(f"Failed to create task: {str(e)}")
logger.error(f"Database connection failed: {str(e)}")
# CRITICAL: errores que comprometen la aplicación
logger.critical(f"All database connections exhausted")
Configuración recomendada para desarrollo
import logging
import sys
def setup_dev_logging():
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s %(levelname)-8s %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
handlers=[logging.StreamHandler(sys.stdout)]
)
logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
logging.getLogger("sqlalchemy.engine").setLevel(logging.WARNING)
Esta configuración:
- ✅ Muestra DEBUG y superiores (máximo detalle)
- ✅ Incluye timestamps legibles
- ✅ Reduce ruido de uvicorn y SQLAlchemy
- ✅ Output a stdout (fácil de copiar)
Caso Práctico: Debugging de un Error de Autenticación
Escenario: tu endpoint protegido devuelve 401 para un usuario que debería tener acceso.
Los logs
2026-03-13 09:30:15 INFO POST /api/admin/reports
2026-03-13 09:30:15 DEBUG Auth middleware: extracting token
2026-03-13 09:30:15 DEBUG Token payload: {'sub': 'maria@company.com', 'role': 'editor', 'exp': 1741859415}
2026-03-13 09:30:15 DEBUG Required role for /api/admin/*: admin
2026-03-13 09:30:15 WARNING Access denied: user role 'editor' does not match required role 'admin'
2026-03-13 09:30:15 INFO Response: 401 Unauthorized
Prompt para Claude Code
Este usuario (maria@company.com) debería tener acceso al endpoint
POST /api/admin/reports. Es editora senior y tiene permisos de admin
según nuestro sistema de roles. Pero recibe 401.
Logs:
"""
[los logs de arriba]
"""
Contexto:
- Nuestro sistema de roles tiene: viewer, editor, admin
- Los editores senior deberían tener acceso a /api/admin/* endpoints
- El endpoint usa un middleware que verifica roles
Diagnóstico esperado de Claude Code
Claude Code debería identificar que:
- El token del usuario contiene
role: 'editor', norole: 'admin' - El middleware hace un match exacto:
role == 'admin' - Hay un disconnect entre "editores senior tienen acceso admin" (regla de negocio) y "el middleware solo acepta role=admin" (implementación)
El fix depende de la decisión de negocio:
- Opción A: Cambiar el token para que editores senior tengan
role: 'admin' - Opción B: Cambiar el middleware para aceptar una lista de roles:
['admin', 'editor'] - Opción C: Implementar un sistema de permisos más granular
Tu verificación
Abres el middleware y confirmas:
from fastapi import Request, HTTPException
from typing import Callable
async def require_admin(request: Request, call_next: Callable):
user = request.state.user
if user.get("role") != "admin":
raise HTTPException(status_code=401, detail="Unauthorized")
response = await call_next(request)
return response
Efectivamente, hace match exacto. El fix correcto depende del contexto de negocio — algo que Claude Code no puede decidir por ti.
Conexión con Proyecto
Cómo aplica al proyecto integrador (Módulo 8)
En el proyecto integrador, la aplicación FastAPI tiene logging configurado. Parte de tu trabajo será ejecutar endpoints, capturar logs cuando fallen, y usar Claude Code para diagnosticar los problemas. Las técnicas de esta cápsula — cuánto contexto dar, qué prompt usar, cómo evaluar el diagnóstico — son exactamente las que usarás.
La diferencia es que en el proyecto integrador, algunos bugs se manifiestan solo cuando interactúan con otros componentes. Necesitarás capturar logs más amplios y correlacionar eventos entre diferentes endpoints.
Troubleshooting
Problema 1: "Claude Code da diagnósticos genéricos"
Causa: Probablemente le estás pasando muy poco contexto — solo el error sin las líneas anteriores. Solución: Usa la regla de 5+5: al menos 5 líneas antes y 5 después del error. Incluye el tipo de request, los datos que se estaban procesando, y cualquier log de debug disponible.
Problema 2: "Los logs de mi aplicación no tienen suficiente detalle"
Causa: Tu logging está configurado en nivel INFO o superior, sin DEBUG.
Solución: Configura logging.basicConfig(level=logging.DEBUG) en desarrollo. Agrega logger.debug() en los puntos clave: entrada de funciones, datos recibidos, resultados de queries.
Problema 3: "Claude Code sugiere un fix pero no funciona"
Causa: El diagnóstico era parcialmente correcto pero el fix no consideraba todo el contexto. Solución: Antes de aplicar un fix sugerido por Claude Code, siempre abre el archivo y lee las líneas alrededor de donde sugiere el cambio. Verifica que el fix es compatible con el resto del código.
Problema 4: "Mis logs tienen información sensible"
Causa: Logging de tokens, passwords, o datos personales.
Solución: Antes de pasarle logs a Claude Code, sanitiza: reemplaza tokens con [TOKEN], passwords con [REDACTED], emails reales con user@example.com. El diagnóstico no necesita datos reales.
Problema 5: "Claude Code da 5 posibles causas sin priorizar"
Causa: Le diste información ambigua que permite múltiples interpretaciones. Solución: Incluye contexto de negocio: "Este endpoint funcionaba ayer", "El error solo aparece con usuarios nuevos", "Empezó después del último deploy". Cuanto más específico el contexto, más específico el diagnóstico.
Ejercicios
Ejercicio 1: Identificar qué logs pasar (Fácil)
Tienes estos logs. Tu endpoint GET /api/users/42 devuelve 500. ¿Cuáles líneas le pasarías a Claude Code para diagnóstico?
2026-03-13 08:00:01 INFO Health check: OK
2026-03-13 08:00:05 INFO GET /api/tasks 200
2026-03-13 08:00:12 INFO GET /api/users 200
2026-03-13 08:01:30 INFO Health check: OK
2026-03-13 08:02:15 INFO GET /api/users/42
2026-03-13 08:02:15 DEBUG UserService.get_user(id=42)
2026-03-13 08:02:15 DEBUG DB query: SELECT * FROM users WHERE id = 42
2026-03-13 08:02:15 DEBUG Query result: {'id': 42, 'name': 'Ana', 'email': 'ana@test.com', 'preferences': None}
2026-03-13 08:02:15 ERROR TypeError: argument of type 'NoneType' is not iterable
2026-03-13 08:02:15 ERROR Response: 500 Internal Server Error
2026-03-13 08:02:45 INFO GET /api/tasks 200
2026-03-13 08:03:00 INFO Health check: OK
Ver solución
Líneas a incluir (6-10):
2026-03-13 08:02:15 INFO GET /api/users/42
2026-03-13 08:02:15 DEBUG UserService.get_user(id=42)
2026-03-13 08:02:15 DEBUG DB query: SELECT * FROM users WHERE id = 42
2026-03-13 08:02:15 DEBUG Query result: {'id': 42, 'name': 'Ana', 'email': 'ana@test.com', 'preferences': None}
2026-03-13 08:02:15 ERROR TypeError: argument of type 'NoneType' is not iterable
2026-03-13 08:02:15 ERROR Response: 500 Internal Server Error
Por qué estas líneas:
- Muestran el request específico que falló
- Muestran los datos del query (donde
preferencesesNone— pista clave) - Muestran el error exacto (
NoneTypeis not iterable — alguien haceinsobrepreferencesque esNone) - El contexto antes y después (health checks, otros requests) no aporta información útil
Qué NO incluir:
- Health checks (ruido)
- Requests a otros endpoints que funcionaron (irrelevantes)
- Logs posteriores al error que no se relacionan
Ejercicio 2: Escribir el prompt correcto (Medio)
Tienes estos logs de un error en tu sistema de notificaciones. Escribe el prompt que le darías a Claude Code para obtener un buen diagnóstico.
2026-03-13 11:00:00 INFO NotificationService: processing batch of 15 notifications
2026-03-13 11:00:00 DEBUG Notification 1/15: type=email, user=user1@test.com, sent OK
2026-03-13 11:00:01 DEBUG Notification 2/15: type=email, user=user2@test.com, sent OK
2026-03-13 11:00:01 DEBUG Notification 3/15: type=sms, user=user3@test.com, sent OK
2026-03-13 11:00:02 DEBUG Notification 4/15: type=push, user=user4@test.com
2026-03-13 11:00:02 WARNING Push notification service returned 429 Too Many Requests
2026-03-13 11:00:02 DEBUG Notification 5/15: type=push, user=user5@test.com
2026-03-13 11:00:02 WARNING Push notification service returned 429 Too Many Requests
2026-03-13 11:00:02 ERROR Batch processing failed: max retries exceeded for push service
2026-03-13 11:00:02 ERROR Notifications 4-15 not sent
2026-03-13 11:00:02 ERROR Response: 500 Internal Server Error
Ver solución
Un buen prompt:
Mi sistema de notificaciones en FastAPI falla al procesar un batch.
Las primeras 3 notificaciones (email y SMS) se envían correctamente,
pero cuando llega a las notificaciones push (tipo=push), el servicio
externo de push devuelve 429 (rate limit) y todo el batch falla.
El problema: cuando una notificación push falla, las notificaciones
restantes (4-15) no se envían — incluso las que son de tipo email o SMS.
Logs:
"""
[los logs de arriba]
"""
Contexto:
- Usamos un servicio externo para push notifications
- El batch se procesa secuencialmente
- Las notificaciones son de 3 tipos: email, sms, push
- El error empezó hoy — probablemente estamos enviando más
push notifications de lo normal
Preguntas específicas:
1. ¿Por qué las notificaciones 6-15 (que podrían ser email/sms)
no se envían cuando push falla?
2. ¿Cómo debería manejar el rate limiting del servicio de push
sin bloquear el resto del batch?
Por qué este prompt es efectivo:
- ✅ Da contexto de negocio (3 tipos de notificaciones, servicio externo)
- ✅ Describe el comportamiento esperado vs el actual
- ✅ Incluye los logs completos
- ✅ Hace preguntas específicas (no solo "¿qué pasa?")
- ✅ Menciona que es un cambio reciente ("empezó hoy")
Ejercicio 3: Evaluar un diagnóstico (Medio)
Claude Code te da este diagnóstico basado en los logs del ejercicio 2. Evalúa si es correcto, parcialmente correcto, o incorrecto.
Diagnóstico de Claude Code:
El problema es que el batch processing no tiene manejo de errores por tipo de notificación. Cuando el servicio de push devuelve 429, el código lanza una excepción que detiene todo el loop de procesamiento.
Fix sugerido: Agregar un try/except dentro del loop para cada notificación individual, y acumular los errores en lugar de detener el batch. Para las notificaciones push específicamente, implementar un retry con exponential backoff.
Ver solución
Evaluación: Parcialmente correcto.
Lo que está bien:
- ✅ Identifica correctamente que una excepción en push detiene todo el batch
- ✅ La sugerencia de try/except individual es razonable
- ✅ Exponential backoff para rate limiting es una buena práctica
Lo que falta o podría ser incorrecto:
- ⚠️ No menciona que las notificaciones push podrían procesarse en un queue separado (mejor arquitectura)
- ⚠️ "Retry con exponential backoff" podría empeorar el rate limiting si el servicio de push ya está sobrecargado
- ⚠️ No sugiere separar el batch por tipo de notificación (procesar email/sms primero, push después)
- ⚠️ No menciona la necesidad de un circuit breaker para el servicio de push
Cómo verificarías:
- Abre el código del batch processor y confirma que efectivamente un error rompe el loop
- Verifica si ya hay retry logic (quizás el "max retries exceeded" indica que sí hay retries pero insuficientes)
- Revisa la documentación del servicio de push para conocer sus rate limits exactos
Acción correcta: Usar las partes buenas del diagnóstico (try/except individual) pero investigar más antes de implementar retry con backoff (podría empeorar el problema).
Ejercicio 4: Pasar logs con contexto correcto (Difícil)
Tu aplicación tiene un bug: el endpoint PATCH /api/tasks/{id} a veces actualiza la tarea correctamente y a veces devuelve la tarea sin los cambios aplicados. No hay error en los logs — siempre devuelve 200.
Estos son los logs de un request donde el bug se manifiesta:
2026-03-13 14:00:01 INFO PATCH /api/tasks/123
2026-03-13 14:00:01 DEBUG Request body: {"status": "completed", "priority": "high"}
2026-03-13 14:00:01 DEBUG TaskService.update_task(id=123)
2026-03-13 14:00:01 DEBUG Current task: id=123, status=pending, priority=medium
2026-03-13 14:00:01 DEBUG Updating fields: status=completed, priority=high
2026-03-13 14:00:01 DEBUG DB update executed
2026-03-13 14:00:01 DEBUG Returning task: id=123, status=pending, priority=medium
2026-03-13 14:00:01 INFO Response: 200 OK
Y estos son los logs de un request donde funciona correctamente:
2026-03-13 14:05:22 INFO PATCH /api/tasks/456
2026-03-13 14:05:22 DEBUG Request body: {"status": "in_progress"}
2026-03-13 14:05:22 DEBUG TaskService.update_task(id=456)
2026-03-13 14:05:22 DEBUG Current task: id=456, status=pending, priority=low
2026-03-13 14:05:22 DEBUG Updating fields: status=in_progress
2026-03-13 14:05:22 DEBUG DB update executed
2026-03-13 14:05:22 DEBUG Returning task: id=456, status=in_progress, priority=low
2026-03-13 14:05:22 INFO Response: 200 OK
- Escribe el prompt que le darías a Claude Code usando el template de comparación
- ¿Cuál crees que sería el diagnóstico?
- ¿Cómo lo verificarías?
Ver solución
1. Prompt:
Mi endpoint PATCH /api/tasks/{id} a veces actualiza correctamente
y a veces devuelve la tarea SIN los cambios aplicados (devuelve 200,
no hay error). Compara estos dos requests — uno donde falla
silenciosamente y otro donde funciona.
Request con bug (task 123 - no aplica cambios):
"""
2026-03-13 14:00:01 INFO PATCH /api/tasks/123
2026-03-13 14:00:01 DEBUG Request body: {"status": "completed", "priority": "high"}
2026-03-13 14:00:01 DEBUG TaskService.update_task(id=123)
2026-03-13 14:00:01 DEBUG Current task: id=123, status=pending, priority=medium
2026-03-13 14:00:01 DEBUG Updating fields: status=completed, priority=high
2026-03-13 14:00:01 DEBUG DB update executed
2026-03-13 14:00:01 DEBUG Returning task: id=123, status=pending, priority=medium
2026-03-13 14:00:01 INFO Response: 200 OK
"""
Request exitoso (task 456 - aplica cambios):
"""
2026-03-13 14:05:22 INFO PATCH /api/tasks/456
2026-03-13 14:05:22 DEBUG Request body: {"status": "in_progress"}
2026-03-13 14:05:22 DEBUG TaskService.update_task(id=456)
2026-03-13 14:05:22 DEBUG Current task: id=456, status=pending, priority=low
2026-03-13 14:05:22 DEBUG Updating fields: status=in_progress
2026-03-13 14:05:22 DEBUG DB update executed
2026-03-13 14:05:22 DEBUG Returning task: id=456, status=in_progress, priority=low
2026-03-13 14:05:22 INFO Response: 200 OK
"""
Contexto:
- La DB update se ejecuta en ambos casos
- La diferencia: task 123 actualiza 2 campos, task 456 actualiza 1 campo
- El "Returning task" muestra los valores ANTES del update en el caso buggy
2. Diagnóstico probable de Claude Code:
El problema es un issue de timing/caching: el "DB update executed" confirma que la actualización se escribió en la base de datos, pero "Returning task" muestra los valores anteriores. Esto sugiere que el código lee el objeto Task ANTES de hacer el update y lo devuelve sin refrescarlo.
La diferencia entre 1 campo y 2 campos podría ser coincidencia, pero probablemente el código:
- Lee el task de la DB (obtiene el objeto)
- Ejecuta el UPDATE en SQL
- Devuelve el objeto leído en paso 1 (que tiene los valores viejos)
El request exitoso podría ser coincidencia (los valores son los mismos antes y después) o podría haber un path diferente de código.
3. Cómo verificarías:
- Abrir
TaskService.update_task()y verificar si hacedb.refresh(task)después del update - Verificar si el método devuelve el objeto original o hace un nuevo query
- Probar: hacer PATCH y luego GET — si GET muestra los valores actualizados, confirma que la DB se actualizó pero el response no se refrescó
- Revisar si hay alguna diferencia en el path de código entre actualizar 1 campo vs 2 campos
Bug real probable:
def update_task(self, task_id: int, updates: dict):
task = self.db.query(Task).get(task_id) # lee el objeto
self.db.execute(
update(Task).where(Task.id == task_id).values(**updates)
)
self.db.commit()
return task # devuelve el objeto VIEJO, sin refresh
El fix: agregar self.db.refresh(task) antes del return, o usar el ORM para hacer el update directamente en el objeto.
Ejercicio 5: Sanitizar logs antes de compartir (Fácil)
Estos logs tienen información sensible. Sanitízalos antes de pasarlos a Claude Code, manteniendo la información necesaria para el diagnóstico.
2026-03-13 15:00:00 INFO POST /api/auth/login
2026-03-13 15:00:00 DEBUG Login attempt: email=maria.gonzalez@empresa-real.com, password=MyS3cur3P@ss!
2026-03-13 15:00:00 DEBUG DB query: SELECT * FROM users WHERE email='maria.gonzalez@empresa-real.com'
2026-03-13 15:00:00 DEBUG User found: id=42, name=María González, ssn=123-45-6789
2026-03-13 15:00:00 DEBUG Token generated: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiJ9.abc123
2026-03-13 15:00:00 DEBUG DB connection string: postgresql://admin:db_password_123@prod-db.empresa.com:5432/users
2026-03-13 15:00:00 ERROR Failed to save session: connection refused
Ver solución
2026-03-13 15:00:00 INFO POST /api/auth/login
2026-03-13 15:00:00 DEBUG Login attempt: email=user@example.com, password=[REDACTED]
2026-03-13 15:00:00 DEBUG DB query: SELECT * FROM users WHERE email='user@example.com'
2026-03-13 15:00:00 DEBUG User found: id=42, name=[REDACTED], ssn=[REDACTED]
2026-03-13 15:00:00 DEBUG Token generated: [JWT_TOKEN_REDACTED]
2026-03-13 15:00:00 DEBUG DB connection string: postgresql://[CREDENTIALS]@[HOST]:5432/users
2026-03-13 15:00:00 ERROR Failed to save session: connection refused
Qué se sanitizó:
- ✅ Email real →
user@example.com(mantiene el formato para diagnóstico) - ✅ Password →
[REDACTED](nunca debería estar en logs, pero si está, sanitiza) - ✅ Nombre real →
[REDACTED] - ✅ SSN →
[REDACTED](dato altamente sensible) - ✅ JWT token →
[JWT_TOKEN_REDACTED] - ✅ DB connection string → credenciales y host redactados
Qué se mantuvo:
- ✅ El endpoint y método HTTP
- ✅ La estructura del query (para diagnosticar si hay SQL issues)
- ✅ El ID del usuario (no es PII en sí mismo)
- ✅ El puerto de la DB (5432 = PostgreSQL, útil para diagnóstico)
- ✅ El nombre de la base de datos (
users) - ✅ El error exacto (
connection refused)
Nota importante: El hecho de que el password aparezca en los logs en texto plano es un bug de seguridad que deberías corregir, además de resolver el connection refused.
Resumen
En esta cápsula aprendiste:
- Log analysis es la habilidad #1 de debugging con Claude Code — resuelve el 70% de bugs más rápido
- La calidad del diagnóstico depende directamente de la calidad de los logs que proporcionas
- La regla de 5+5: incluye al menos 5 líneas antes y 5 después del error
- Usa prompt templates específicos para cada tipo de problema (general, comparación, intermitente, secuencia)
- Siempre evalúa el diagnóstico de Claude Code: verifica contra el código real antes de aplicar un fix
- Sanitiza logs antes de compartirlos: reemplaza tokens, passwords, PII con placeholders
- Logs estructurados dan diagnósticos más precisos que logs en texto libre
Próxima cápsula: Runtime Errors y Stack Traces — interpretar errores de Python con ayuda de Claude Code.
Recursos Adicionales
- Python Logging Cookbook - Recetas avanzadas de logging en Python
- Structured Logging with structlog - Librería de logging estructurado para Python
- FastAPI — Handling Errors - Manejo de errores y excepciones en FastAPI
- 12-Factor App — Logs - Principios de logging en aplicaciones modernas
- Anthropic — Claude Code Documentation - Mejores prácticas oficiales de Claude Code
- OWASP Logging Cheat Sheet - Qué loggear y qué nunca loggear (seguridad)
Debugging & Code Review with Claude Code — Módulo 6, Cápsula 02 Claude Code Agentic Development Path — Guía #6 de 11