CAPTCHA işlem hattınızın nerede yavaşladığını görmek istiyorsanız doğru araç grep değil, çözüm loglarını ELK Stack'e (Elasticsearch, Logstash, Kibana) akıtmaktır. Binlerce çözüm işleyen bir e-ticaret QA ekibinde TIMEOUT dalgasını düz metin loglarda aramak dakikalar alır — Kibana'da aynı sorgu saniyeler sürer.
Fark, ölçekte iyice belirginleşir. Onlarca thread eşzamanlı çalışırken her worker'ın ayrı bir dosyaya yazdığı düz metin, hangi sitekey'in yavaşladığını ya da hangi hata kodunun arttığını söylemez. Çözüm loglarını tek bir yerde toplayıp yapılandırdığınızda ise "son bir saatte reCAPTCHA v2 çözüm süresi neden yükseldi?" sorusu tek bir Kibana sorgusuna dönüşür. Aşağıda, worker loglarını uçtan uca ELK'ye taşıyan üretime hazır bir kurulum adım adım anlatılıyor.
Veri akışı: worker'dan Kibana'ya
Loglar tek yönde akar ve her aşama tek bir işi yapar. Her worker satır başına JSON kaydı üretir; Filebeat dosyaları izleyip iletir, Logstash alanları ayrıştırıp zenginleştirir, Elasticsearch indeksler ve saklar, Kibana sorgular ve görselleştirir. Bu ayrım, sonradan bir bileşeni (örneğin Logstash yerine doğrudan Elasticsearch'e yazma) değiştirmenizi kolaylaştırır:
[CAPTCHA Workers] → JSON logs → [Filebeat] → [Logstash] → [Elasticsearch]
↓
[Kibana]
Yapılandırılmış loglama (structured logging)
İlk kural: satır başına tek bir JSON nesnesi yazın ki Kibana alanları filtreleyebilsin. Her log satırında en az şu alanlar bulunmalı:
captcha_id— bir görevi gönderimden çözüme izler; en kritik alandırcaptcha_type— türe göre kırılım (reCAPTCHA v2, Turnstile, GeeTest v3…)solve_timevepoll_count— gecikme ve sorgulama maliyetierror_code— başarısızlıkları gruplamak için
Çözüm metnini ise asla loglamayın (nedeni için SSS'ye bakın).
Python: JSON log çıktısı
Formatter, logging kaydına eklediğiniz alanları otomatik JSON'a çevirir:
import os
import json
import time
import logging
import sys
import requests
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
class JSONFormatter(logging.Formatter):
def format(self, record):
log_entry = {
"timestamp": self.formatTime(record),
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
}
# Add extra fields
if hasattr(record, "captcha_id"):
log_entry["captcha_id"] = record.captcha_id
if hasattr(record, "captcha_type"):
log_entry["captcha_type"] = record.captcha_type
if hasattr(record, "solve_time"):
log_entry["solve_time"] = record.solve_time
if hasattr(record, "error_code"):
log_entry["error_code"] = record.error_code
if hasattr(record, "target_url"):
log_entry["target_url"] = record.target_url
if hasattr(record, "poll_count"):
log_entry["poll_count"] = record.poll_count
return json.dumps(log_entry)
# Configure logger
logger = logging.getLogger("captchaai")
logger.setLevel(logging.INFO)
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(JSONFormatter())
logger.addHandler(handler)
session = requests.Session()
def solve_captcha(sitekey, pageurl, captcha_type="recaptcha_v2"):
extra = {"captcha_type": captcha_type, "target_url": pageurl}
# Submit
resp = session.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": pageurl,
"json": 1
})
data = resp.json()
if data.get("status") != 1:
logger.error("Submit failed", extra={
**extra, "error_code": data.get("request")
})
return {"error": data.get("request")}
captcha_id = data["request"]
extra["captcha_id"] = captcha_id
logger.info("Task submitted", extra=extra)
# Poll
start = time.time()
poll_count = 0
for _ in range(60):
time.sleep(5)
poll_count += 1
result = session.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": captcha_id, "json": 1
}).json()
if result.get("status") == 1:
elapsed = round(time.time() - start, 2)
logger.info("Solve success", extra={
**extra,
"solve_time": elapsed,
"poll_count": poll_count
})
return {"solution": result["request"]}
if result.get("request") != "CAPCHA_NOT_READY":
logger.error("Solve failed", extra={
**extra,
"error_code": result.get("request"),
"poll_count": poll_count
})
return {"error": result.get("request")}
logger.error("Solve timeout", extra={
**extra,
"error_code": "TIMEOUT",
"poll_count": poll_count
})
return {"error": "TIMEOUT"}
JavaScript: yapılandırılmış log çıktısı
Node.js worker'larında aynı deseni axios ile kurun; her olay tek satırlık JSON olur:
const axios = require("axios");
const API_KEY = process.env.CAPTCHAAI_API_KEY;
function log(level, message, fields = {}) {
const entry = {
timestamp: new Date().toISOString(),
level,
message,
service: "captcha-worker",
...fields,
};
console.log(JSON.stringify(entry));
}
async function solveCaptcha(sitekey, pageurl, captchaType = "recaptcha_v2") {
const fields = { captchaType, targetUrl: pageurl };
const submitResp = await axios.post("https://ocr.captchaai.com/in.php", null, {
params: {
key: API_KEY, method: "userrecaptcha",
googlekey: sitekey, pageurl, json: 1,
},
});
if (submitResp.data.status !== 1) {
log("error", "Submit failed", { ...fields, errorCode: submitResp.data.request });
return { error: submitResp.data.request };
}
const captchaId = submitResp.data.request;
fields.captchaId = captchaId;
log("info", "Task submitted", fields);
const startTime = Date.now();
let pollCount = 0;
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
pollCount++;
const pollResp = await axios.get("https://ocr.captchaai.com/res.php", {
params: { key: API_KEY, action: "get", id: captchaId, json: 1 },
});
if (pollResp.data.status === 1) {
const solveTime = ((Date.now() - startTime) / 1000).toFixed(2);
log("info", "Solve success", { ...fields, solveTime: parseFloat(solveTime), pollCount });
return { solution: pollResp.data.request };
}
if (pollResp.data.request !== "CAPCHA_NOT_READY") {
log("error", "Solve failed", { ...fields, errorCode: pollResp.data.request, pollCount });
return { error: pollResp.data.request };
}
}
log("error", "Solve timeout", { ...fields, errorCode: "TIMEOUT", pollCount });
return { error: "TIMEOUT" };
}
module.exports = { solveCaptcha };
Filebeat yapılandırması
Filebeat, worker loglarını izleyip Logstash'e iletir; keys_under_root ile JSON alanları kök seviyeye çıkar:
# filebeat.yml
filebeat.inputs:
- type: log
paths:
- /var/log/captcha-worker/*.log
json:
keys_under_root: true
add_error_key: true
message_key: message
output.logstash:
hosts: ["logstash:5044"]
Logstash pipeline'ı
Logstash her satırı ayrıştırır ve türetilmiş alanlar ekler. Aşağıdaki filtre solve_time'a göre bir solve_time_bucket (fast/medium/slow) hesaplar; böylece gecikme dağılımını tek bakışta görürsünüz:
# logstash-captcha.conf
input {
beats {
port => 5044
}
}
filter {
# Parse JSON logs
json {
source => "message"
target => "captcha"
}
# Add computed fields
if [captcha][solve_time] {
mutate {
add_field => {
"solve_time_bucket" => "fast"
}
}
if [captcha][solve_time] > 30 {
mutate { update => { "solve_time_bucket" => "medium" } }
}
if [captcha][solve_time] > 90 {
mutate { update => { "solve_time_bucket" => "slow" } }
}
}
# Extract date
date {
match => ["[captcha][timestamp]", "ISO8601"]
target => "@timestamp"
}
}
output {
elasticsearch {
hosts => ["elasticsearch:9200"]
index => "captcha-logs-%{+YYYY.MM.dd}"
}
}
Elasticsearch index şablonu
Filtreleyeceğiniz alanları keyword, sayısal alanları (solve_time, poll_count) ise uygun sayısal türle eşleyin — yanlış eşlemeyi sonradan düzeltmek pahalıdır:
{
"index_patterns": ["captcha-logs-*"],
"template": {
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0
},
"mappings": {
"properties": {
"captcha_type": { "type": "keyword" },
"captcha_id": { "type": "keyword" },
"error_code": { "type": "keyword" },
"solve_time": { "type": "float" },
"poll_count": { "type": "integer" },
"target_url": { "type": "keyword" },
"level": { "type": "keyword" },
"message": { "type": "text" }
}
}
}
}
Kibana panelleri
Altı panel, çözüm operasyonunuzun tamamını kapsar:
İpucu: panel adlarını sorgularınızla tutarlı tutun; altı ay sonra hangi panelin neyi ölçtüğünü hatırlamak çok daha kolay olur.
| Panel | Görselleştirme | Sorgu |
|---|---|---|
| Çözüm başarı oranı | Metrik | level:info AND message:"Solve success" / toplam |
| Hata dağılımı | Pasta grafiği | error_code'a göre gruplanmış level:error |
| Gecikme eğrisi | Çizgi grafiği | Zaman içinde ortalama solve_time |
| Zaman içinde hatalar | Çubuk grafik | 5 dakikalık kova başına level:error sayısı |
| En yavaş çözümler | Veri tablosu | solve_time'a göre azalan ilk 10 |
| Kuyruk etkinliği | Alan grafiği | message'a göre sayım ("Task submitted" ↔ "Solve success") |
İşe yarayan Kibana sorguları
# All errors in the last hour
level:error AND @timestamp:[now-1h TO now]
# Timeout errors for reCAPTCHA
error_code:TIMEOUT AND captcha_type:recaptcha_v2
# Slow solves (> 60 seconds)
solve_time:>60
# Errors for a specific target URL
level:error AND target_url:"example.com"
# Specific CAPTCHA ID investigation
captcha_id:"73519847"
Uyarı kurma: hata oranı yükseldiğinde haber alın
Panolar canlıyken kimse ekrana bakmıyorsa değer üretmez; asıl kazanç, bir eşik aşıldığında otomatik uyarı almaktır. Kibana'nın uyarı özelliği (ya da ElastAlert gibi bir alternatif) sabit aralıklarla bir sorgu çalıştırır ve sonuç eşiği aştığında Slack, e-posta veya bir webhook tetikler.
Pratikte iki uyarı çoğu ekip için yeterlidir:
- Hata oranı sıçraması: son 5 dakikada
level:errorsayısı normal taban çizginizin belirgin biçimde üzerine çıktığında. AniERROR_WRONG_GOOGLEKEYya daTIMEOUTdalgaları geneldesitekeydeğişikliğini veya hedef sayfadaki bir bozulmayı işaret eder. - Gecikme kayması: ortalama
solve_timeseçtiğiniz eşiği (örneğin 30 saniye) sürekli aştığında. Bu, worker havuzunuzun darboğaza girdiğinin erken sinyalidir.
Uyarı eşiklerini gözlemlenen taban çizginize göre belirleyin; sabit bir "doğru" değer yoktur ve CAPTCHA türüne göre değişir.
Ölçek ve maliyet: log hacmini thread sayısıyla ilişkilendirin
Log hacminiz eşzamanlı thread sayınıza bağlıdır. CaptchaAI thread bazlı faturalandırır ve her planda çözüm sınırsızdır; bu da TL oynaklığına karşı öngörülebilir bir aylık USD maliyeti sağlar. BASIC ($15/ay, 5 thread) günde birkaç bin satır üretirken, ADVANCE ($90/ay, 50 thread) seviyesinde index'iniz çok daha hızlı büyür — gün başına index desenini ve ILM saklama süresini buna göre planlayın. KVKK açısından target_url gibi alanlarda kişisel veri sızdırmayın; yalnızca yol (path) yazın.
Sorun giderme
| Sorun | Neden | Çözüm |
|---|---|---|
| Loglar Kibana'da görünmüyor | Filebeat log göndermiyor | Filebeat loglarını kontrol edin; yol (path) deseninin eşleştiğini doğrulayın |
| JSON ayrıştırma hataları | Log dosyasında JSON olmayan satırlar | Filebeat'e keys_under_root ekleyin; logger çıktısını düzeltin |
| Çok fazla index | ILM'siz günlük (daily) index | 30 günlük saklamayla Index Lifecycle Management kurun |
| Yavaş sorgular | Eksik keyword eşlemesi |
Filtrelenen alanlarda text değil keyword kullanın |
Sık sorulan sorular
CAPTCHA çözüm token'ını log'a yazmalı mıyım?
Hayır. Çözümler tek kullanımlık token'lardır; tanısal değerleri yoktur, depolama maliyetini artırır ve KVKK açısından risk yaratır. Yalnızca meta veriyi loglayın: captcha_id, captcha_type, solve_time, error_code.
solve_time alanını neden keyword değil sayısal tür olarak eşlemeliyim?
float olarak eşlerseniz Kibana'da ortalama, yüzdelik dilim ve solve_time:>60 gibi aralık sorguları çalışır; keyword olarak eşlenmiş bir sayıda bu toplamalar imkânsızdır.
Log hacmi büyüyünce hangi CaptchaAI planına yükselmeliyim?
Karar log satırına değil, ihtiyaç duyduğunuz eşzamanlı thread sayısına bağlıdır. Faturalandırma thread bazlıdır ve her planda çözüm sınırsızdır; darboğazda BASIC'ten ($15/ay, 5 thread) STANDARD'a ($30/ay, 15 thread) ya da ADVANCE'e ($90/ay, 50 thread) çıkın. Shard ve ILM ayarlarını da buna göre güncelleyin.
Kibana'da "çözüm başarı oranı" panelini nasıl kurarım?
Bir Metric görselleştirmesi oluşturun, level:info AND message:"Solve success" sorgusuyla başarılı çözümleri sayın ve toplam görev sayısına bölün. "Task submitted" ve "Solve success" olaylarını ayrı saydığınız için oran, yeni loglar geldikçe anlık güncellenir.
Farklı CAPTCHA türlerinin loglarını aynı index'te tutabilir miyim?
Evet. Her kaydın captcha_type alanı olduğu sürece reCAPTCHA v2/v3, Cloudflare Turnstile, GeeTest v3 ve BLS gibi tüm türleri tek bir captcha-logs-* index'inde toplayabilir, panelleri captcha_type'a göre filtreleyebilirsiniz. Ayrı index gerekmez; türe göre kırılım, tek bir alanla sağlanır.
Sonraki adımlar
Çözüm loglarınızı analiz etmeye başlayın: CaptchaAI API anahtarınızı alın ve ELK'yi bugün ayağa kaldırın.
İlgili rehberler: