DevOps và Mở Rộng

Ngăn xếp ELK để phân tích nhật ký giải CAPTCHA

Khi quy trình CAPTCHA của bạn xử lý hàng nghìn tác vụ, grep sẽ không mở rộng quy mô. ELK Stack (Elasticsearch, Logstash, Kibana) cho phép bạn tìm kiếm, tổng hợp và trực quan hóa nhật ký giải quyết - tìm mẫu lỗi, theo dõi xu hướng độ trễ và chẩn đoán sự cố trong vài giây.

Kiến trúc

[CAPTCHA Workers] → JSON logs → [Filebeat] → [Logstash] → [Elasticsearch]
                                                                ↓
                                                           [Kibana]

Ghi nhật ký có cấu trúc

Python - Đầu ra nhật ký JSON

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 – Ghi nhật ký có cấu trúc

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 };

Cấu hình filebeat

# 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"]

Đường ống đăng nhập

# 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}"
  }
}

Mẫu chỉ mục Elaticsearch

{
  "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" }
      }
    }
  }
}

Bảng điều khiển Kibana

bảng điều khiển Trực quan hóa Truy vấn
Giải quyết tỷ lệ thành công Số liệu level:info AND message:"Solve success" / tổng cộng
Phân tích lỗi Biểu đồ hình tròn level:error được nhóm theo error_code
Độ trễ theo thời gian Biểu đồ đường solve_time trung bình theo thời gian
Lỗi theo thời gian Biểu đồ thanh Đếm level:error mỗi nhóm 5 phút
Giải quyết chậm nhất Bảng dữ liệu Top 10 của solve_time giảm dần
Hoạt động xếp hàng Biểu đồ vùng Tính theo message ("Nhiệm vụ đã gửi" vs "Giải quyết thành công")

Truy vấn hữu ích

# 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"

Khắc phục sự cố

Vấn đề Nguyên nhân Cách xử lý
Nhật ký không xuất hiện ở Kibana Filebeat không gửi nhật ký Kiểm tra nhật ký Filebeat; xác minh sự trùng khớp của mẫu đường dẫn
Lỗi phân tích cú pháp JSON Các dòng không phải JSON trong tệp nhật ký Thêm json.keys_under_root vào Filebeat; sửa đầu ra logger
Quá nhiều chỉ số Chỉ số hàng ngày không có ILM Thiết lập Quản lý vòng đời chỉ mục với khả năng lưu giữ trong 30 ngày
Truy vấn chậm Thiếu ánh xạ từ khóa Sử dụng loại keyword cho các trường có thể lọc, không phải text

Câu hỏi thường gặp

Tôi nên giữ lại nhật ký CAPTCHA trong bao lâu?

30 ngày đối với nhật ký hoạt động. 90 ngày nếu bạn cần phân tích xu hướng. Sử dụng Elaticsearch ILM để tự động xóa các chỉ mục cũ.

Tôi có thể sử dụng OpenSearch thay vì Elaticsearch không?

Vâng. OpenSearch tương thích API với Elaticsearch. Plugin đầu ra Logstash, các lựa chọn thay thế Filebeat và Kibana (Bảng điều khiển OpenSearch) hoạt động theo cách tương tự.

Tôi có nên ghi lại văn bản giải pháp CAPTCHA không?

Không. Giải pháp là các mã thông báo sử dụng một lần không có giá trị chẩn đoán. Việc ghi nhật ký chúng sẽ làm tăng thêm chi phí lưu trữ và có thể tạo ra các vấn đề bảo mật. Chỉ ghi nhật ký siêu dữ liệu (ID, loại, độ trễ, trạng thái).

Các bước tiếp theo

Tìm kiếm và phân tích nhật ký CAPTCHA của bạn —lấy khóa API CaptchaAI của bạnvà thiết lập ELK.

Hướng dẫn liên quan:

Os comentários estão desativados para este artigo.