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:
- Kirim task CAPTCHA ke
in.php. - Simpan
task_idyang dikembalikan. - Polling
res.phpsampai hasil siap. - 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 (
RotatingFileHandlerataulogrotate) 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-3Jakarta), 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:
- Membangun dashboard monitoring pemakaian
- Structured logging untuk operasi CAPTCHA
- Cek saldo dan auto-refill otomatis