Tutorials

Building a CaptchaAI Debug Logger for Development

When CAPTCHA solves fail in production, generic logs like "request failed" don't help. A purpose-built debug logger captures the full context — request parameters, response data, timing, poll attempts, and error details — so you can diagnose issues without reproducing them.

What the Logger Captures

Data Point Why It Matters
Request parameters Verify sitekey, pageurl, method are correct
Task ID Track a specific solve through its lifecycle
Poll count and timing Detect excessive polling or premature timeouts
Response status and body See exact errors returned by the API
Total solve duration Identify slow solves vs. network issues
Error classification Distinguish API errors from network failures

Python Debug Logger

import logging
import time
import json
import requests
from datetime import datetime, timezone

class CaptchaAIDebugLogger:
    def __init__(self, api_key, log_file="captchaai_debug.log", log_level=logging.DEBUG):
        self.api_key = api_key
        self.submit_url = "https://ocr.captchaai.com/in.php"
        self.result_url = "https://ocr.captchaai.com/res.php"

        self.logger = logging.getLogger("captchaai")
        self.logger.setLevel(log_level)

        # File handler with structured format
        file_handler = logging.FileHandler(log_file)
        file_handler.setFormatter(logging.Formatter(
            "%(asctime)s | %(levelname)-8s | %(message)s",
            datefmt="%Y-%m-%d %H:%M:%S"
        ))
        self.logger.addHandler(file_handler)

        # Console handler for development
        console_handler = logging.StreamHandler()
        console_handler.setFormatter(logging.Formatter(
            "%(asctime)s | %(levelname)-8s | %(message)s",
            datefmt="%H:%M:%S"
        ))
        self.logger.addHandler(console_handler)

    def solve_recaptcha(self, sitekey, pageurl, **kwargs):
        solve_id = datetime.now(timezone.utc).strftime("%Y%m%d%H%M%S%f")[:18]
        self.logger.info(f"[{solve_id}] Starting reCAPTCHA solve")
        self.logger.debug(f"[{solve_id}] sitekey={sitekey}")
        self.logger.debug(f"[{solve_id}] pageurl={pageurl}")

        if kwargs:
            self.logger.debug(f"[{solve_id}] extra_params={json.dumps(kwargs)}")

        # Submit task
        submit_start = time.monotonic()
        params = {
            "key": self.api_key,
            "method": "userrecaptcha",
            "googlekey": sitekey,
            "pageurl": pageurl,
            "json": 1,
            **kwargs,
        }

        try:
            response = requests.post(self.submit_url, data=params, timeout=30)
            submit_duration = time.monotonic() - submit_start
            result = response.json()

            self.logger.debug(
                f"[{solve_id}] Submit response: status={response.status_code} "
                f"body={json.dumps(result)} duration={submit_duration:.3f}s"
            )

            if result.get("status") != 1:
                self.logger.error(
                    f"[{solve_id}] Submit failed: {result.get('request', 'unknown error')}"
                )
                return None

            task_id = result["request"]
            self.logger.info(f"[{solve_id}] Task submitted: task_id={task_id}")

        except requests.RequestException as e:
            self.logger.error(f"[{solve_id}] Submit network error: {e}")
            return None

        # Poll for result
        return self._poll_result(solve_id, task_id)

    def _poll_result(self, solve_id, task_id, max_polls=60, poll_interval=5):
        self.logger.info(f"[{solve_id}] Polling for result (task_id={task_id})")
        poll_start = time.monotonic()

        for attempt in range(1, max_polls + 1):
            time.sleep(poll_interval)

            try:
                response = requests.get(
                    self.result_url,
                    params={
                        "key": self.api_key,
                        "action": "get",
                        "id": task_id,
                        "json": 1,
                    },
                    timeout=15,
                )
                result = response.json()
                elapsed = time.monotonic() - poll_start

                if result.get("request") == "CAPCHA_NOT_READY":
                    self.logger.debug(
                        f"[{solve_id}] Poll #{attempt}: not ready "
                        f"(elapsed={elapsed:.1f}s)"
                    )
                    continue

                if result.get("status") == 1:
                    token = result["request"]
                    self.logger.info(
                        f"[{solve_id}] Solved in {elapsed:.1f}s "
                        f"(polls={attempt}, token_length={len(token)})"
                    )
                    return token

                # Error response
                self.logger.error(
                    f"[{solve_id}] Poll error: {result.get('request', 'unknown')} "
                    f"(attempt={attempt}, elapsed={elapsed:.1f}s)"
                )
                return None

            except requests.RequestException as e:
                self.logger.warning(
                    f"[{solve_id}] Poll #{attempt} network error: {e}"
                )
                continue

        total_time = time.monotonic() - poll_start
        self.logger.error(
            f"[{solve_id}] Timeout after {max_polls} polls ({total_time:.1f}s)"
        )
        return None

    def check_balance(self):
        self.logger.debug("Checking balance")
        try:
            response = requests.get(
                self.result_url,
                params={
                    "key": self.api_key,
                    "action": "getbalance",
                    "json": 1,
                },
                timeout=10,
            )
            result = response.json()
            balance = result.get("request", "unknown")
            self.logger.info(f"Balance: ${balance}")
            return float(balance) if balance != "unknown" else None
        except (requests.RequestException, ValueError) as e:
            self.logger.error(f"Balance check failed: {e}")
            return None


# Usage
solver = CaptchaAIDebugLogger("YOUR_API_KEY")
solver.check_balance()
token = solver.solve_recaptcha(
    sitekey="6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI",
    pageurl="https://www.google.com/recaptcha/api2/demo"
)

JavaScript Debug Logger

const fs = require("fs");
const path = require("path");

class CaptchaAIDebugLogger {
  constructor(apiKey, options = {}) {
    this.apiKey = apiKey;
    this.submitUrl = "https://ocr.captchaai.com/in.php";
    this.resultUrl = "https://ocr.captchaai.com/res.php";
    this.logFile = options.logFile || "captchaai_debug.log";
    this.logLevel = options.logLevel || "debug";
    this.levels = { debug: 0, info: 1, warn: 2, error: 3 };
  }

  _log(level, message) {
    if (this.levels[level] < this.levels[this.logLevel]) return;

    const timestamp = new Date().toISOString().replace("T", " ").slice(0, 19);
    const entry = `${timestamp} | ${level.toUpperCase().padEnd(8)} | ${message}\n`;

    process.stdout.write(entry);
    fs.appendFileSync(this.logFile, entry);
  }

  async solveRecaptcha(sitekey, pageurl, extraParams = {}) {
    const solveId = Date.now().toString(36);
    this._log("info", `[${solveId}] Starting reCAPTCHA solve`);
    this._log("debug", `[${solveId}] sitekey=${sitekey}`);
    this._log("debug", `[${solveId}] pageurl=${pageurl}`);

    if (Object.keys(extraParams).length > 0) {
      this._log("debug", `[${solveId}] extra_params=${JSON.stringify(extraParams)}`);
    }

    // Submit task
    const submitStart = performance.now();
    const params = new URLSearchParams({
      key: this.apiKey,
      method: "userrecaptcha",
      googlekey: sitekey,
      pageurl,
      json: 1,
      ...extraParams,
    });

    try {
      const response = await fetch(this.submitUrl, {
        method: "POST",
        body: params,
      });
      const result = await response.json();
      const submitMs = (performance.now() - submitStart).toFixed(0);

      this._log(
        "debug",
        `[${solveId}] Submit response: status=${response.status} ` +
        `body=${JSON.stringify(result)} duration=${submitMs}ms`
      );

      if (result.status !== 1) {
        this._log("error", `[${solveId}] Submit failed: ${result.request || "unknown"}`);
        return null;
      }

      const taskId = result.request;
      this._log("info", `[${solveId}] Task submitted: task_id=${taskId}`);
      return this._pollResult(solveId, taskId);
    } catch (err) {
      this._log("error", `[${solveId}] Submit network error: ${err.message}`);
      return null;
    }
  }

  async _pollResult(solveId, taskId, maxPolls = 60, intervalMs = 5000) {
    this._log("info", `[${solveId}] Polling for result (task_id=${taskId})`);
    const pollStart = performance.now();

    for (let attempt = 1; attempt <= maxPolls; attempt++) {
      await new Promise((r) => setTimeout(r, intervalMs));

      try {
        const url = new URL(this.resultUrl);
        url.searchParams.set("key", this.apiKey);
        url.searchParams.set("action", "get");
        url.searchParams.set("id", taskId);
        url.searchParams.set("json", "1");

        const response = await fetch(url);
        const result = await response.json();
        const elapsedSec = ((performance.now() - pollStart) / 1000).toFixed(1);

        if (result.request === "CAPCHA_NOT_READY") {
          this._log("debug", `[${solveId}] Poll #${attempt}: not ready (elapsed=${elapsedSec}s)`);
          continue;
        }

        if (result.status === 1) {
          const token = result.request;
          this._log(
            "info",
            `[${solveId}] Solved in ${elapsedSec}s (polls=${attempt}, token_length=${token.length})`
          );
          return token;
        }

        this._log(
          "error",
          `[${solveId}] Poll error: ${result.request || "unknown"} (attempt=${attempt})`
        );
        return null;
      } catch (err) {
        this._log("warn", `[${solveId}] Poll #${attempt} network error: ${err.message}`);
        continue;
      }
    }

    const totalSec = ((performance.now() - pollStart) / 1000).toFixed(1);
    this._log("error", `[${solveId}] Timeout after ${maxPolls} polls (${totalSec}s)`);
    return null;
  }

  async checkBalance() {
    this._log("debug", "Checking balance");
    try {
      const url = new URL(this.resultUrl);
      url.searchParams.set("key", this.apiKey);
      url.searchParams.set("action", "getbalance");
      url.searchParams.set("json", "1");

      const response = await fetch(url);
      const result = await response.json();
      this._log("info", `Balance: $${result.request}`);
      return parseFloat(result.request);
    } catch (err) {
      this._log("error", `Balance check failed: ${err.message}`);
      return null;
    }
  }
}

// Usage
const solver = new CaptchaAIDebugLogger("YOUR_API_KEY");
(async () => {
  await solver.checkBalance();
  const token = await solver.solveRecaptcha(
    "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI",
    "https://www.google.com/recaptcha/api2/demo"
  );
})();

Log Output Example

14:32:01 | INFO     | [20260404143201] Starting reCAPTCHA solve
14:32:01 | DEBUG    | [20260404143201] sitekey=6LeIxAcTAAAAA...
14:32:01 | DEBUG    | [20260404143201] pageurl=https://www.google.com/recaptcha/api2/demo
14:32:01 | DEBUG    | [20260404143201] Submit response: status=200 body={"status":1,"request":"73948572"} duration=0.342s
14:32:01 | INFO     | [20260404143201] Task submitted: task_id=73948572
14:32:01 | INFO     | [20260404143201] Polling for result (task_id=73948572)
14:32:06 | DEBUG    | [20260404143201] Poll #1: not ready (elapsed=5.1s)
14:32:11 | DEBUG    | [20260404143201] Poll #2: not ready (elapsed=10.2s)
14:32:16 | INFO     | [20260404143201] Solved in 15.3s (polls=3, token_length=574)

Log Levels and When to Use Them

Level What to Log When to Enable
DEBUG All parameters, raw responses, poll details Development and debugging
INFO Task lifecycle events (submit, solved, timeout) Staging and early production
WARN Retryable errors (network timeouts, temporary failures) Production
ERROR Non-retryable failures (bad key, zero balance, unsolvable) Always

Set log_level=logging.INFO (Python) or logLevel: "info" (JavaScript) in production to reduce log volume while keeping important events.

Extending the Logger

Add Structured JSON Logging

For log aggregation systems (ELK, Datadog, CloudWatch):

import json

def _structured_log(self, level, solve_id, event, **data):
    entry = {
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "level": level,
        "solve_id": solve_id,
        "event": event,
        **data,
    }
    self.logger.log(
        getattr(logging, level.upper()),
        json.dumps(entry)
    )

Add Metrics Collection

Track solve statistics for dashboards:

class SolveMetrics:
    def __init__(self):
        self.total_solves = 0
        self.successful = 0
        self.failed = 0
        self.total_duration = 0.0

    def record(self, success, duration):
        self.total_solves += 1
        if success:
            self.successful += 1
        else:
            self.failed += 1
        self.total_duration += duration

    def summary(self):
        avg = self.total_duration / max(self.total_solves, 1)
        rate = self.successful / max(self.total_solves, 1) * 100
        return f"solves={self.total_solves} success={rate:.1f}% avg_time={avg:.1f}s"

Troubleshooting

Issue Cause Fix
Log file not created Permission issue or invalid path Use absolute path; check write permissions
Duplicate log lines Multiple handlers attached to same logger Check for duplicate addHandler calls; use logger.handlers to verify
Logs missing for failed requests Exception caught before logging Add logging in the except block
Log file grows too large Long-running process with DEBUG level Use RotatingFileHandler (Python) or rotate logs with logrotate
Timestamps in wrong timezone System timezone vs UTC Use datetime.now(timezone.utc) (Python) or new Date().toISOString() (JS)

FAQ

Should I log the API key?

Never log the full API key. If you need to identify which key is being used, log the last 4 characters: key=...{api_key[-4:]}.

How much logging overhead does this add?

Negligible compared to CAPTCHA solve times. File I/O for a log entry takes microseconds; CAPTCHA solves take 10–60 seconds. The overhead is unmeasurable in practice.

Can I use this logger in production?

Yes, but set the log level to INFO or WARN. DEBUG logging in production generates high volume and may capture sensitive data. Use structured JSON logging for production log aggregation.

Next Steps

Build debug logging into your CaptchaAI integration from the start — get your API key and add the logger to your first integration.

Related guides:

Discussions (0)

No comments yet.

Related Posts

DevOps & Scaling Ansible Playbooks for CaptchaAI Worker Deployment
Deploy and manage Captcha AI workers with Ansible — playbooks for provisioning, configuration, rolling updates, and health checks across your server fleet.

Deploy and manage Captcha AI workers with Ansible — playbooks for provisioning, configuration, rolling updates...

Automation Python All CAPTCHA Types
Apr 07, 2026
DevOps & Scaling Blue-Green Deployment for CAPTCHA Solving Infrastructure
Implement blue-green deployments for CAPTCHA solving infrastructure — zero-downtime upgrades, traffic switching, and rollback strategies with Captcha AI.

Implement blue-green deployments for CAPTCHA solving infrastructure — zero-downtime upgrades, traffic switchin...

Automation Python All CAPTCHA Types
Apr 07, 2026
Troubleshooting CaptchaAI API Error Handling: Complete Decision Tree
Complete decision tree for every Captcha AI API error.

Complete decision tree for every Captcha AI API error. Learn which errors are retryable, which need parameter...

Automation Python All CAPTCHA Types
Mar 17, 2026
Tutorials Using Fiddler to Inspect CaptchaAI API Traffic
How to use Fiddler Everywhere and Fiddler Classic to capture, inspect, and debug Captcha AI API requests and responses — filters, breakpoints, and replay for tr...

How to use Fiddler Everywhere and Fiddler Classic to capture, inspect, and debug Captcha AI API requests and r...

Automation Python All CAPTCHA Types
Mar 05, 2026
Tutorials CAPTCHA Handling in Mobile Apps with Appium
Handle CAPTCHAs in mobile app automation using Appium and Captcha AI — extract Web sitekeys, solve, and inject tokens on Android and i OS.

Handle CAPTCHAs in mobile app automation using Appium and Captcha AI — extract Web View sitekeys, solve, and i...

Automation Python All CAPTCHA Types
Feb 13, 2026
Tutorials Streaming Batch Results: Processing CAPTCHA Solutions as They Arrive
Process CAPTCHA solutions the moment they arrive instead of waiting for tasks to complete — use async generators, event emitters, and callback patterns for stre...

Process CAPTCHA solutions the moment they arrive instead of waiting for all tasks to complete — use async gene...

Automation Python All CAPTCHA Types
Apr 07, 2026
Reference CaptchaAI CLI Tool: Command-Line CAPTCHA Solving and Testing
A reference for building and using a Captcha AI command-line tool — solve CAPTCHAs, check balance, test parameters, and integrate with shell scripts and CI/CD p...

A reference for building and using a Captcha AI command-line tool — solve CAPTCHAs, check balance, test parame...

Automation Python All CAPTCHA Types
Feb 26, 2026
DevOps & Scaling Auto-Scaling CAPTCHA Solving Workers
Build auto-scaling CAPTCHA solving workers that adjust capacity based on queue depth, balance, and solve rates.

Build auto-scaling CAPTCHA solving workers that adjust capacity based on queue depth, balance, and solve rates...

Automation Python All CAPTCHA Types
Mar 23, 2026
DevOps & Scaling CaptchaAI Monitoring with Datadog: Metrics and Alerts
Monitor Captcha AI performance with Datadog — custom metrics, dashboards, anomaly detection alerts, and solve rate tracking for CAPTCHA solving pipelines.

Monitor Captcha AI performance with Datadog — custom metrics, dashboards, anomaly detection alerts, and solve...

Automation Python All CAPTCHA Types
Feb 19, 2026
Tutorials Pytest Fixtures for CaptchaAI API Testing
Build reusable pytest fixtures to test CAPTCHA-solving workflows with Captcha AI.

Build reusable pytest fixtures to test CAPTCHA-solving workflows with Captcha AI. Covers mocking, live integra...

Automation Python reCAPTCHA v2
Apr 08, 2026
Tutorials GeeTest Token Injection in Browser Automation Frameworks
how to inject Gee Test v 3 solution tokens into Playwright, Puppeteer, and Selenium — including the three-value response, callback triggering, and form submissi...

Learn how to inject Gee Test v 3 solution tokens into Playwright, Puppeteer, and Selenium — including the thre...

Automation Python Testing
Jan 18, 2026