Bir CAPTCHA çözümü düştüğünde tek bir sorunun yanıtı gerekir: hangi görev, hangi türde, kaç milisaniye sonra ve hangi hata koduyla başarısız oldu? "Error solving captcha" satırı bunların hiçbirini söylemez. Yapılandırılmış loglama, her çözümü task_id, captcha_type, solve_time_ms ve error alanlarını taşıyan tek satırlık bir JSON kaydına çevirir; metin taramak yerine alana göre filtreler, yanıtı saniyeler içinde bulursunuz.
Aşağıdaki yapılandırmayı Python (structlog) ve Node.js (pino) için doğrudan mevcut çözüm fonksiyonunuza yerleştirebilirsiniz.
Önce log alan şemasını sabitleyin
Kütüphane seçmeden önce hangi alanları yazacağınıza karar verin. Şema baştan sabit değilse iki servis farklı anahtar adları kullanır ve merkezî sorgularınız ilk haftada bozulur. Aşağıdaki küme gereken minimumdur.
| Alan | Tür | Açıklama |
|---|---|---|
event |
dize | Etkinlik adı: captcha_submitted, captcha_solved, vb. |
task_id |
dize | Korelasyon için CaptchaAI görev kimliği |
captcha_type |
dize | recaptcha_v2, turnstile, image, vb. |
site_url |
dize | Hedef sayfa URL'si |
solve_time_ms |
tamsayı | Gönderimden çözüme kadar geçen toplam süre |
poll_attempts |
tamsayı | Yapılan sorgulama isteği sayısı |
error |
dize | CaptchaAI'den gelen hata kodu |
token_length |
tamsayı | Dönen token'ın uzunluğu |
Bu alanların en kritiği task_id'dir: gönderim, sorgulama ve token enjeksiyonunu tek bir çözüme bağlayan anahtar odur. Diğerleri olmadan da hata ayıklarsınız; task_id olmadan elinizde kopuk satırlar kalır.
Düz metin loglar neden aynı işi görmez
Düz metin bir satır insan gözü için okunabilir ama makine için işe yaramaz: alana göre filtreleyemez, iki olayı ilişkilendiremez, otomatik uyarı üretemezsiniz. JSON ise her bilgiyi ayrı bir anahtara çevirir.
| Düz metin | Yapılandırılmış JSON |
|---|---|
Captcha solved in 12.3s |
{"event":"captcha_solved","task_id":"abc123","type":"recaptcha_v2","solve_time_ms":12300} |
| Ayrıştırılması zor | Makine tarafından okunabilir |
| Yalnızca Grep araması | Herhangi bir alana göre filtrele |
| Korelasyon yok | task_id; gönderme → sorgulama → enjeksiyon adımlarını birbirine bağlar |
Python tarafı: structlog yapılandırması
structlog'u üç işlemciyle kurmak yeterli: ISO zaman damgası, seviye alanı ve JSON renderer. Ayrı formatlayıcıya gerek kalmaz, çıktı doğrudan stdout'a aranabilir JSON olarak düşer.
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()
Çözüm yaşam döngüsünü loglayın
log.bind() bağlam alanlarını — CAPTCHA türü, hedef URL, kısaltılmış sitekey — bir kez bağlar; sonraki her satır bunları otomatik taşır. Görev kimliği yanıtla döner dönmez ikinci bir bind() ile onu da ekleyin.
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
Çıktı:
{"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"}
Üç satırın da aynı task_id değerini taşıdığına dikkat edin. task_id="71845302" sorgusu, tek bir çözümün tüm yaşam döngüsünü sırayla önünüze getirir.
Node.js tarafı: pino ile aynı şema
pino, düşük ek yükle aynı JSON çıktısını üretir. Kritik nokta kütüphane değil disiplin: olay adlarını (captcha_submitted, captcha_solved, captcha_solve_failed) Python tarafıyla birebir aynı tutun ki iki dildeki servisleriniz tek şemada toplansın.
const pino = require('pino');
const log = pino({
level: 'info',
timestamp: pino.stdTimeFunctions.isoTime,
});
Bağlamı log.child() ile bağlayın
log.child(), structlog'daki log.bind() ile aynı işi görür: bağlam alanlarını türetilmiş bir logger'a bağlar; görev kimliği geldiğinde ikinci bir child ekleyin.
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;
}
Logları filtreleyin ve uyarı kurun
JSON loglar bir kez oluştuğunda yerelde jq ile ya da merkezî bir toplayıcıda (ELK, Grafana Loki) alana göre sorgulayabilirsiniz. Standart çıktıya yazmak, altyapıdan bağımsız kalmanın en pratik yoludur.
Son bir saatteki başarısız çözümleri bulun
# With jq
cat captcha.log | jq 'select(.level == "error" and .event == "captcha_solve_failed")'
Hata oranı yükselince ayrı bir olay yazın
Kayan bir pencerede başarı/başarısızlık oranını takip edin; eşik aşıldığında captcha_error_rate_high olayını yazın. Uyarı sisteminiz ham log satırlarını değil bu tek olay adını dinler — kuralın bakımı çok daha kolaydır.
# 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),
)
Çözüm sürelerini thread planlamasına bağlayın
Loglar yalnızca sorun giderme aracı değil, aynı zamanda kapasite verisidir. Plan kararını iki alanla verin:
solve_time_ms→ p50 ve p95, çözümün gerçekte ne kadar sürdüğünü söyler.- Eşzamanlı açık görev sayısı → kaç thread'e ihtiyacınız olduğunu söyler.
Belirleyici olan ikincisidir. CaptchaAI thread bazlı faturalandırır, çözüm başına değil: her plan thread başına sınırsız çözüm içerir, sınırı koyan eşzamanlılıktır.
Ölçümden plana: bir senaryo
İstanbul'daki bir e-ticaret müşterisi için ödeme adımı regresyon testleri yazdığınızı düşünün. Test paketi Europe/Istanbul saatiyle 03:00'te koşuyor ve iki haftalık loglar şunu gösteriyor:
| Logdan gelen ölçüm | Değer | Plan kararına etkisi |
|---|---|---|
| p50 çözüm süresi | 9 sn | Test paketi süresini planlamaya yeter |
| p95 çözüm süresi | 18 sn | Zaman aşımı eşiğini buna göre kurun |
| Tepe eşzamanlılık | 4 görev | Gereken thread sayısını doğrudan verir |
Bu profil için BASIC ($15/ay, 5 thread) rahatça yeter. Müşteri kampanya öncesi test hacmini katlarsa, eşzamanlılık eğrisi sizi kampanya haftasına girmeden ADVANCE ($90/ay, 50 thread) gibi bir plana yönlendirir. Fiyatlar USD üzerinden sabit olduğu için TL kur oynaklığından etkilenmeyen, öngörülebilir bir aylık maliyet elde edersiniz.
Aynı log akışını Prometheus ve Grafana ile çözüm oranı izlemeye besleyebilir, uzun vadeli analiz için çözüm sonuçlarını PostgreSQL'de saklayabilir veya kullanım panelinizi doğrudan bu alanların üzerine kurabilirsiniz.
Sorun giderme
| Sorun | Sebep | Düzeltme |
|---|---|---|
| Loglar çok ayrıntılı | Her sorgulama girişiminin loglanması | Yalnızca gönderilen, çözülen ve başarısız olayları loglayın |
| Olaylar ilişkilendirilemiyor | Eksik görev kimliği | task_id'yi log.bind() veya log.child() ile erken bağlayın |
| Loglar aranamıyor | Düz metin formatı | structlog veya pino ile JSON'a geçin |
| İki servisin logları birleşmiyor | Farklı olay adları | Olay adlarını ve alan şemasını iki dilde tek listede sabitleyin |
| Loglardaki hassas veriler | Tam API anahtarının loglanması | API anahtarlarını asla loglamayın; sitekey'leri kısaltın |
Loglara ne yazmamalısınız
Tablonun son satırı özellikle önemli: loglarda tam API anahtarı, e-posta veya başka kişisel veri tutmak yalnızca güvenlik değil, KVKK açısından da risktir. Üç kural yeter:
- API anahtarlarını ve token'ların tamamını hiçbir seviyede yazmayın; token için yalnızca
token_lengthtutun. sitekeygibi uzun tanımlayıcıları ilk 12 karaktere kısaltın — hata ayıklamaya yeter.- Log saklama sürenizi yetkili QA ve veri toplama akışlarının ihtiyacıyla sınırlayın, süresi dolanı otomatik silin.
Sık sorulan sorular
Yapılandırılmış loglama üretim performansını yavaşlatır mı?
Pratikte hayır. Hem structlog hem pino satır başına birkaç mikrosaniyelik ek yük getirir; asıl maliyet log hacmindedir. Sorgulama döngüsünün her turunu değil yalnızca gönderim, çözüm ve hata olaylarını yazarsanız çözüm başına üç satırda kalırsınız.
Mevcut logging kütüphanemi değiştirmek zorunda mıyım?
Hayır. Python'un standart logging modülüne bir JSON formatlayıcı takarak da aynı şemayı üretebilirsiniz; structlog'un kazandırdığı bind() ile bağlam taşımaktır. Belirleyici olan kütüphane değil, alan adlarının tutarlılığıdır.
task_id'yi loglara ne zaman bağlamalıyım?
Gönderim yanıtıyla döner dönmez, sorgulama döngüsüne girmeden önce. Erken bağlarsanız tüm satırlar aynı task_id ile filtrelenir; geç bağlarsanız hatanın oluştuğu satırda kimlik bulunmaz.
Log satırlarında token'ın tamamını tutabilir miyim?
Tutmayın. Yalnızca uzunluğunu (token_length) yazın. Token'lar 500 karakteri aşabilir; tamamını loglamak gereksiz hacim ve sızıntı riski yaratır. CAPCHA_NOT_READY gibi beklenen ara durumları da yazmayın, yalnızca nihai sonucu loglayın.
Bu şema hangi CAPTCHA türlerinde çalışır?
Alan şeması türden bağımsızdır: yalnızca captcha_type değeri değişir. Aynı loglama katmanını reCAPTCHA v2, Cloudflare Turnstile, GeeTest v3 ve görüntü/OCR akışlarında değiştirmeden kullanabilirsiniz. Tek koşul, olay adlarını her entegrasyonda aynı tutmanız.
Gözlemlenebilir bir CAPTCHA akışıyla başlayın
Ücretsiz hesabınızı captchaai.com üzerinden açın, API anahtarınızı alın ve ilk çözümünüzü daha bugün task_id ile uçtan uca izleyin.