Tutorials

Audit Log Solve CAPTCHA: Pelacakan Request untuk Compliance

Audit log solve CAPTCHA adalah catatan terstruktur dari setiap request. Ketika auditor atau klien menanyakan rincian 40.000 solve bulan lalu, catatan inilah yang membuat Anda bisa menjawab dalam hitungan menit alih-alih menyisir log mentah berhari-hari. Jejak audit yang baik menjawab pertanyaan seperti:

  • Siapa atau proses apa yang memicu solve ini?
  • Untuk situs mana request dijalankan?
  • Berapa lama penyelesaiannya dan apa hasil akhirnya?
  • Berapa perkiraan biayanya?

Panduan ini menunjukkan field yang perlu dicatat dan cara menerapkannya pada operasi CaptchaAI dengan Python dan Node.js.

Kenapa audit log penting untuk compliance

Jejak audit adalah bukti akuntabilitas — dan bagi sebagian tim, sebuah keharusan:

  • Agensi scraping dan price-monitoring perlu menunjukkan ke klien apa yang dijalankan atas nama mereka.
  • Tim yang mengejar SOC 2, ISO 27001, atau GDPR harus membuktikan siapa melakukan apa dan kapan.
  • Integrator berbasis kontrak butuh catatan untuk rekonsiliasi biaya dan penyelesaian sengketa.

Di Indonesia, UU Pelindungan Data Pribadi (UU 27/2022) mempertegas prinsip yang sama: hanya proses data yang memang Anda berwenang mengaksesnya, pisahkan audit log dari log aplikasi, dan jangan simpan data pribadi di dalamnya. Ini bukan nasihat hukum; sesuaikan dengan penasihat compliance Anda.

Field yang wajib dicatat di setiap solve

Setiap solve CAPTCHA sebaiknya merekam field berikut:

Field Tujuan Contoh
timestamp Kapan request dibuat 2026-04-04T14:30:00Z
request_id Identifier unik untuk solve ini uuid4()
captcha_type Metode CAPTCHA yang dipakai userrecaptcha
target_site URL halaman yang di-solve https://staging.example.com/qa-login
task_id ID task CaptchaAI 73829451
status Hasil akhir solved, failed, timeout
solve_time_ms Waktu dari submit hingga hasil 18432
error_code Kode error jika gagal ERROR_CAPTCHA_UNSOLVABLE
initiator Siapa atau apa yang memicu solve scraper-job-42
cost Estimasi biaya 0.003

Sebaliknya, beberapa hal justru tidak boleh masuk ke audit log:

  • API key — bersifat rahasia; jangan pernah ditulis ke log.
  • Token CAPTCHA — sementara dan tanpa nilai audit.
  • Data pribadi dari situs target — menambah risiko compliance, bukan menguranginya.

Field cost di sini adalah alokasi internal untuk chargeback: CaptchaAI menagih per thread konkuren dengan solve tanpa batas per thread, bukan per solve, jadi angka ini hanya untuk pembagian biaya di tim Anda.

Alur solve yang dicatat

Semua contoh di bawah mengikuti alur standar CaptchaAI yang sama dan menulis satu record audit di setiap tahap:

  1. Kirim task CAPTCHA ke in.php.
  2. Simpan task_id yang dikembalikan.
  3. Polling res.php sampai hasil siap.
  4. Pakai token, lalu catat status akhir beserta waktunya.

Dengan menempatkan penulisan log di setiap cabang — sukses, gagal, timeout, maupun error — tidak ada solve yang lolos tanpa jejak.

Implementasi Python

Fungsi berikut membungkus alur di atas dan menulis record audit di tiap cabang hasil:

# audit_solver.py
import os
import uuid
import time
import json
import logging
from datetime import datetime, timezone
import requests

API_KEY = os.environ.get("CAPTCHAAI_KEY", "YOUR_API_KEY")

# Configure audit logger — separate from application logs
audit_logger = logging.getLogger("captcha_audit")
audit_logger.setLevel(logging.INFO)

# File handler with rotation
from logging.handlers import RotatingFileHandler
handler = RotatingFileHandler(
    "captcha_audit.jsonl",
    maxBytes=50_000_000,  # 50 MB per file
    backupCount=10,
)
handler.setFormatter(logging.Formatter("%(message)s"))
audit_logger.addHandler(handler)

def log_audit(record):
    """Write a structured audit record."""
    audit_logger.info(json.dumps(record, default=str))

def solve_with_audit(sitekey, pageurl, captcha_type="userrecaptcha",
                      initiator="unknown"):
    """Solve a CAPTCHA with full audit logging."""
    request_id = str(uuid.uuid4())
    start = time.time()

    audit_record = {
        "request_id": request_id,
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "captcha_type": captcha_type,
        "target_site": pageurl,
        "initiator": initiator,
        "status": "submitted",
    }

    session = requests.Session()

    try:
        # Submit
        resp = session.get("https://ocr.captchaai.com/in.php", params={
            "key": API_KEY,
            "method": captcha_type,
            "googlekey": sitekey,
            "pageurl": pageurl,
            "json": "1",
        })
        result = resp.json()

        if result.get("status") != 1:
            audit_record.update({
                "status": "submit_failed",
                "error_code": result.get("request"),
                "solve_time_ms": int((time.time() - start) * 1000),
            })
            log_audit(audit_record)
            return None

        task_id = result["request"]
        audit_record["task_id"] = task_id

        # Poll
        time.sleep(15)
        for _ in range(25):
            poll = session.get("https://ocr.captchaai.com/res.php", params={
                "key": API_KEY, "action": "get",
                "id": task_id, "json": "1",
            })
            poll_result = poll.json()

            if poll_result.get("status") == 1:
                solve_time = int((time.time() - start) * 1000)
                audit_record.update({
                    "status": "solved",
                    "solve_time_ms": solve_time,
                    "cost_estimate": 0.003,  # Adjust per your rate
                })
                log_audit(audit_record)
                return poll_result["request"]

            if poll_result.get("request") != "CAPCHA_NOT_READY":
                audit_record.update({
                    "status": "failed",
                    "error_code": poll_result.get("request"),
                    "solve_time_ms": int((time.time() - start) * 1000),
                })
                log_audit(audit_record)
                return None

            time.sleep(5)

        audit_record.update({
            "status": "timeout",
            "solve_time_ms": int((time.time() - start) * 1000),
        })
        log_audit(audit_record)
        return None

    except Exception as e:
        audit_record.update({
            "status": "error",
            "error_code": str(e)[:200],
            "solve_time_ms": int((time.time() - start) * 1000),
        })
        log_audit(audit_record)
        raise

# Usage
token = solve_with_audit(
    sitekey="6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
    pageurl="https://www.google.com/recaptcha/api2/demo",
    initiator="price-scraper-v2",
)

Contoh keluaran log (format JSONL)

Satu solve menghasilkan satu baris JSON — mudah di-grep, di-tail, atau dikirim ke agregator log:

{"request_id":"a1b2c3d4-...","timestamp":"2026-04-04T14:30:00+00:00","captcha_type":"userrecaptcha","target_site":"https://www.google.com/recaptcha/api2/demo","initiator":"price-scraper-v2","status":"solved","task_id":"73829451","solve_time_ms":18432,"cost_estimate":0.003}

Implementasi JavaScript (Node.js)

Versi Node.js mengikuti pola yang sama dan menulis record dengan fs.appendFileSync, sehingga setiap request tercatat begitu hasilnya diketahui:

// audit_solver.js
const fs = require('fs');
const { v4: uuidv4 } = require('uuid');
const axios = require('axios');

const API_KEY = process.env.CAPTCHAAI_KEY || 'YOUR_API_KEY';
const AUDIT_FILE = 'captcha_audit.jsonl';

function logAudit(record) {
  fs.appendFileSync(AUDIT_FILE, JSON.stringify(record) + '\n');
}

async function solveWithAudit(sitekey, pageurl, initiator = 'unknown') {
  const requestId = uuidv4();
  const start = Date.now();
  const record = {
    request_id: requestId,
    timestamp: new Date().toISOString(),
    captcha_type: 'userrecaptcha',
    target_site: pageurl,
    initiator,
    status: 'submitted',
  };

  try {
    const submit = await axios.get('https://ocr.captchaai.com/in.php', {
      params: {
        key: API_KEY, method: 'userrecaptcha',
        googlekey: sitekey, pageurl, json: '1',
      },
    });

    if (submit.data.status !== 1) {
      record.status = 'submit_failed';
      record.error_code = submit.data.request;
      record.solve_time_ms = Date.now() - start;
      logAudit(record);
      return null;
    }

    record.task_id = submit.data.request;
    await new Promise(r => setTimeout(r, 15000));

    for (let i = 0; i < 25; i++) {
      const poll = await axios.get('https://ocr.captchaai.com/res.php', {
        params: { key: API_KEY, action: 'get', id: submit.data.request, json: '1' },
      });

      if (poll.data.status === 1) {
        record.status = 'solved';
        record.solve_time_ms = Date.now() - start;
        record.cost_estimate = 0.003;
        logAudit(record);
        return poll.data.request;
      }
      if (poll.data.request !== 'CAPCHA_NOT_READY') {
        record.status = 'failed';
        record.error_code = poll.data.request;
        record.solve_time_ms = Date.now() - start;
        logAudit(record);
        return null;
      }
      await new Promise(r => setTimeout(r, 5000));
    }

    record.status = 'timeout';
    record.solve_time_ms = Date.now() - start;
    logAudit(record);
    return null;
  } catch (e) {
    record.status = 'error';
    record.error_code = e.message.slice(0, 200);
    record.solve_time_ms = Date.now() - start;
    logAudit(record);
    throw e;
  }
}

Query dan ringkasan harian

Karena tiap baris adalah JSON valid, laporan cukup membaca file baris demi baris. Ringkasan harian berikut menghitung jumlah per status, total estimasi biaya, dan waktu penyelesaian median — cukup untuk laporan operasional maupun lampiran ke klien:

import json
from collections import Counter
from datetime import date

def daily_summary(log_file, target_date=None):
    """Generate a daily summary from audit logs."""
    target = target_date or date.today().isoformat()
    statuses = Counter()
    total_cost = 0
    solve_times = []

    with open(log_file) as f:
        for line in f:
            record = json.loads(line)
            if record["timestamp"].startswith(target):
                statuses[record["status"]] += 1
                total_cost += record.get("cost_estimate", 0)
                if record.get("solve_time_ms"):
                    solve_times.append(record["solve_time_ms"])

    print(f"Date: {target}")
    print(f"Total requests: {sum(statuses.values())}")
    print(f"Statuses: {dict(statuses)}")
    print(f"Estimated cost: ${total_cost:.2f}")
    if solve_times:
        print(f"Median solve time: {sorted(solve_times)[len(solve_times)//2]}ms")

daily_summary("captcha_audit.jsonl")

Retensi dan penyimpanan log

Kapan file lokal cukup dan kapan Anda perlu logging terpusat bergantung pada volume harian:

Volume Ukuran log harian Storage bulanan Rekomendasi
100 solve/hari ~30 KB ~1 MB File lokal
1.000 solve/hari ~300 KB ~10 MB File lokal + rotasi
10.000 solve/hari ~3 MB ~100 MB Kirim ke log aggregator
100.000 solve/hari ~30 MB ~1 GB Centralized logging (ELK, Datadog)

Beberapa praktik yang membantu di volume tinggi:

  • Aktifkan rotasi file sejak awal (RotatingFileHandler atau logrotate) agar log tidak membengkak.
  • Simpan minimal 90 hari untuk kebutuhan operasional, lebih lama jika compliance menuntutnya.
  • Untuk tim yang deploy dekat pengguna Indonesia (region ap-southeast-3 Jakarta), kirim log ke agregator begitu volume menembus puluhan ribu solve per hari.

Mengatasi masalah audit logging

Masalah Penyebab Solusi
File log terlalu besar Rotasi tidak dikonfigurasi Gunakan RotatingFileHandler atau logrotate
Record audit hilang Exception sebelum logging Tulis log di blok finally
Penulisan lambat pada volume tinggi File I/O synchronous Pakai async file write atau buffer
Timestamp tidak konsisten System clock drift Gunakan NTP; catat dalam UTC

Pertanyaan umum

Apakah audit log CAPTCHA cukup untuk memenuhi SOC 2 atau UU PDP?

Audit log adalah bukti pendukung, bukan sertifikasi. Ia menunjukkan akuntabilitas dan riwayat akses, tetapi SOC 2 dan UU PDP tetap menuntut kontrol lain seperti kebijakan, kontrol akses, dan enkripsi. Jadikan audit log bagian dari paket kontrol, bukan satu-satunya.

Bagaimana cara mencocokkan audit log dengan tagihan CaptchaAI?

Bandingkan jumlah solve di audit log dengan pemakaian di dashboard CaptchaAI. Karena penagihan berbasis thread (bukan per solve), pakai audit log untuk memverifikasi volume dan pola pemakaian, bukan untuk menghitung biaya per CAPTCHA.

Berapa lama audit log sebaiknya disimpan?

90 hari adalah patokan umum untuk log operasional. Untuk kebutuhan compliance, sesuaikan periode retensi dan kebijakan penghapusan dengan persyaratan industri Anda (SOC 2, GDPR, HIPAA).

Bagaimana memisahkan audit log dari log aplikasi biasa?

Pakai logger dan file terpisah, seperti contoh Python yang memakai logger captcha_audit sendiri. Pemisahan ini menjaga jejak audit tetap bersih, mudah diarsipkan, dan bebas dari noise log debug aplikasi.

Artikel terkait

Langkah selanjutnya

Tambahkan akuntabilitas pada setiap solve CAPTCHA — dapatkan kunci API CaptchaAI Anda.

Panduan terkait:

Komentar dinonaktifkan untuk artikel ini.