Un log estructurado convierte cada resolución de CAPTCHA en un evento JSON que puedes filtrar, buscar y usar para disparar alertas por task_id, tipo de CAPTCHA o código de error. Esa es la diferencia entre saber qué falló y quedarte mirando una línea como "Error solving captcha" sin ningún contexto. Cuando un pipeline nocturno empieza a devolver tokens vacíos, un registro estructurado te da el sitio, el intento de sondeo y el tiempo de resolución exactos; el texto plano solo te confirma que algo se rompió.
En esta guía vas a montar, paso a paso:
- Un logger JSON en Python con
structlogy en Node.js conpino. - El registro del ciclo de vida completo de una resolución: envío, sondeo y resultado.
- Filtros con
jqy una alerta automática cuando sube la tasa de errores. - Qué campos conviene registrar y cuáles no debes tocar nunca.
Qué gana el registro estructurado frente al texto plano
La ventaja no es estética: puedes consultar los eventos como una base de datos, porque cada línea es un objeto con campos nombrados y no texto que parsear con regex frágiles. Cada evento lleva su propio contexto, así que agrupar, contar o alertar deja de depender de scripts de parsing a medida.
| Texto sin formato | JSON estructurado |
|---|---|
Captcha solved in 12.3s |
{"event":"captcha_solved","task_id":"abc123","type":"recaptcha_v2","solve_time_ms":12300} |
| Difícil de analizar | Legible por máquina |
| Búsqueda solo con grep | Filtrar por cualquier campo |
| Sin correlación | El task_id enlaza envío → sondeo → inyección |
Piensa en una agencia que corre QA de checkout y monitoreo de precios sobre marketplaces regionales (MercadoLibre, Amazon.es) de madrugada. Si la tasa de resolución cae, nadie quiere leer miles de líneas: filtras por event:"captcha_solve_failed" de la última hora y ves al instante qué tipo de CAPTCHA y qué sitio concentran los errores. Con costo mensual predecible en USD y logs que se consultan como datos, esa depuración pasa de horas a segundos.
Qué campos registrar en cada evento
Antes de escribir el logger, decide la forma del evento. Estos son los campos que hacen accionable cada línea: con ellos reconstruyes toda la vida de una resolución sin abrir el código.
| Campo | Tipo | Descripción |
|---|---|---|
event |
cadena | Nombre del evento: captcha_submitted, captcha_solved, etc. |
task_id |
cadena | ID de tarea de CaptchaAI para la correlación |
captcha_type |
cadena | recaptcha_v2, turnstile, image, etc. |
site_url |
cadena | URL de la página de destino |
solve_time_ms |
entero | Tiempo total desde el envío hasta la resolución |
poll_attempts |
entero | Número de solicitudes de sondeo realizadas |
error |
cadena | Código de error de CaptchaAI |
token_length |
entero | Longitud del token devuelto |
Python: structlog
structlog produce JSON estructurado con muy poca configuración. Añade un timestamp ISO, el nivel de log y el renderizador JSON, y ya tienes un logger listo para producción.
import structlog
import time
structlog.configure(
processors=[
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.add_log_level,
structlog.processors.JSONRenderer(),
],
logger_factory=structlog.PrintLoggerFactory(),
)
log = structlog.get_logger()
Registrar el ciclo de vida de la resolución
Enlaza el contexto común una sola vez con log.bind() y añade el task_id en cuanto la API te lo devuelve. Así cada evento posterior —envío, sondeo, resultado— arrastra los mismos campos y queda correlacionado sin esfuerzo.
import requests
API_KEY = "YOUR_API_KEY"
def solve_captcha(captcha_type, sitekey, page_url, proxy=None):
solve_log = log.bind(
captcha_type=captcha_type,
site_url=page_url,
sitekey=sitekey[:12] + "...",
)
# Submit
start = time.time()
solve_log.info("captcha_submit_start")
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"json": "1",
}).json()
if resp["status"] != 1:
solve_log.error("captcha_submit_failed", error=resp["request"])
return None
task_id = resp["request"]
submit_ms = int((time.time() - start) * 1000)
solve_log = solve_log.bind(task_id=task_id)
solve_log.info("captcha_submitted", submit_ms=submit_ms)
# Poll
for attempt in range(24):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id, "json": "1"
}).json()
if result["status"] == 1:
solve_ms = int((time.time() - start) * 1000)
solve_log.info(
"captcha_solved",
solve_time_ms=solve_ms,
poll_attempts=attempt + 1,
token_length=len(result["request"]),
)
return result["request"]
if result["request"] != "CAPCHA_NOT_READY":
solve_log.error(
"captcha_solve_failed",
error=result["request"],
poll_attempts=attempt + 1,
)
return None
solve_log.warning("captcha_solve_timeout", poll_attempts=24)
return None
Cada llamada deja una traza JSON limpia como esta, lista para indexar en tu stack de logs:
{"event":"captcha_submit_start","captcha_type":"recaptcha_v2","site_url":"https://example.com","sitekey":"6Le-wvkSAAAA...","timestamp":"2025-07-15T10:30:00Z","level":"info"}
{"event":"captcha_submitted","task_id":"71845302","submit_ms":245,"timestamp":"2025-07-15T10:30:00Z","level":"info"}
{"event":"captcha_solved","task_id":"71845302","solve_time_ms":18230,"poll_attempts":4,"token_length":580,"timestamp":"2025-07-15T10:30:18Z","level":"info"}
Node.js: pino
En el lado de JavaScript, pino es el logger estándar por su bajo overhead. Con isoTime obtienes marcas de tiempo comparables a las de structlog.
const pino = require('pino');
const log = pino({
level: 'info',
timestamp: pino.stdTimeFunctions.isoTime,
});
El mismo ciclo de vida en JavaScript
pino usa log.child() en lugar de log.bind(), pero el patrón es idéntico: un logger hijo con el contexto de la tarea, derivado de nuevo cuando llega el task_id.
const axios = require('axios');
const API_KEY = 'YOUR_API_KEY';
async function solveCaptcha(captchaType, sitekey, pageUrl) {
const taskLog = log.child({
captchaType,
siteUrl: pageUrl,
sitekey: sitekey.substring(0, 12) + '...',
});
const start = Date.now();
taskLog.info('captcha_submit_start');
const submit = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: {
key: API_KEY, method: 'userrecaptcha',
googlekey: sitekey, pageurl: pageUrl, json: 1,
},
});
if (submit.data.status !== 1) {
taskLog.error({ error: submit.data.request }, 'captcha_submit_failed');
return null;
}
const taskId = submit.data.request;
const boundLog = taskLog.child({ taskId });
boundLog.info({ submitMs: Date.now() - start }, 'captcha_submitted');
for (let attempt = 1; attempt <= 24; attempt++) {
await new Promise(r => setTimeout(r, 5000));
const poll = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: API_KEY, action: 'get', id: taskId, json: 1 },
});
if (poll.data.status === 1) {
boundLog.info({
solveTimeMs: Date.now() - start,
pollAttempts: attempt,
tokenLength: poll.data.request.length,
}, 'captcha_solved');
return poll.data.request;
}
if (poll.data.request !== 'CAPCHA_NOT_READY') {
boundLog.error({ error: poll.data.request, pollAttempts: attempt }, 'captcha_solve_failed');
return null;
}
}
boundLog.warn({ pollAttempts: 24 }, 'captcha_solve_timeout');
return null;
}
Filtrar y alertar sobre los eventos
Con los eventos ya en JSON, cualquier herramienta que entienda ese formato se convierte en tu tablero de diagnóstico.
Encuentra todos los fallos de la última hora
# With jq
cat captcha.log | jq 'select(.level == "error" and .event == "captcha_solve_failed")'
Alerta cuando sube la tasa de errores
Calcula la tasa de fallos sobre una ventana móvil y emite un evento de aviso en cuanto supera tu umbral. Ese evento se conecta directo a tu sistema de alertas, sin scripts intermedios.
# Count errors vs successes in a rolling window
from collections import deque
class ErrorRateMonitor:
def __init__(self, window_size=100, threshold=0.2):
self.results = deque(maxlen=window_size)
self.threshold = threshold
def record(self, success):
self.results.append(success)
if len(self.results) >= 50:
error_rate = 1 - sum(self.results) / len(self.results)
if error_rate > self.threshold:
log.warning(
"captcha_error_rate_high",
error_rate=round(error_rate, 3),
window=len(self.results),
)
Qué registrar y qué evitar
Un buen log es tan útil por lo que omite como por lo que incluye. Como regla:
- Registra solo tres eventos de negocio por resolución:
captcha_submitted,captcha_solvedycaptcha_solve_failed. - Enlaza el
task_idlo antes posible para que toda la traza sea correlacionable. - Guarda métricas, no secretos:
solve_time_ms,poll_attemptsytoken_lengthcuentan la historia sin exponer nada sensible. - Nunca registres la API key completa ni el token; trunca los sitekeys como en los ejemplos.
- No registres cada intento de sondeo:
CAPCHA_NOT_READYes un estado esperado y solo genera ruido.
Problemas frecuentes y cómo resolverlos
| Problema | Causa | Solución |
|---|---|---|
| Logs demasiado ruidosos | Registrar cada intento de sondeo | Registra solo los eventos de envío, resolución y fallo |
| No puedes correlacionar eventos | Falta el task_id |
Enlaza task_id pronto con log.bind() o log.child() |
| Logs no consultables | Formato de texto plano | Cambia a JSON con structlog o pino |
| Datos sensibles en los logs | Registrar la API key completa | Nunca registres claves API; trunca los sitekeys |
Preguntas frecuentes
¿Cómo correlaciono el envío, el sondeo y el resultado de un mismo CAPTCHA?
Vincula el task_id con log.bind() (structlog) o log.child() (pino) en cuanto la API te lo devuelve: todos los eventos posteriores lo arrastran y basta filtrar por ese ID para reconstruir la traza completa.
¿Debo guardar el token del CAPTCHA en el log?
No. Registra solo su longitud con token_length, nunca el token completo: un token puede superar los 500 caracteres, infla el volumen de logs y no aporta nada para depurar.
¿Cómo detecto una caída en la tasa de resolución desde los logs?
Cuenta éxitos y fallos sobre una ventana móvil, como en ErrorRateMonitor, y emite un evento captcha_error_rate_high cuando supera tu umbral; ese evento se conecta directo a tu sistema de alertas.
¿El registro estructurado afecta el rendimiento del worker?
Casi nada si eres selectivo. Loguear tres eventos por resolución y evitar el ruido de CAPCHA_NOT_READY mantiene el volumen bajo; tanto structlog como pino están pensados para escribir JSON con un overhead mínimo.
Crea flujos de resolución de CAPTCHA observables con CaptchaAI
Obtén tu clave API en captchaai.com y registra cada resolución con contexto completo desde la primera petición.