Пайплайн решения CAPTCHA обычно ломается тихо: баланс API незаметно уходит в ноль, воркер зависает, задержка растёт — а вы узнаёте об этом из отчёта поддержки, а не из алерта. Datadog закрывает этот разрыв: семь метрик пайплайна на одном дашборде и оповещения, которые срабатывают раньше, чем клиенты успевают пожаловаться.
Какие метрики CaptchaAI стоит собирать
| Метрика | Тип | Зачем нужна |
|---|---|---|
captcha.solve.count |
Счётчик | Сколько задач отправлено всего |
captcha.solve.success |
Счётчик | Сколько решений получено успешно |
captcha.solve.error |
Счётчик | Сколько решений упало, в разбивке по типу ошибки |
captcha.solve.latency |
Гистограмма | Время от отправки задачи до готового решения |
captcha.queue.depth |
Датчик | Сколько задач сейчас ждёт в очереди |
captcha.balance |
Датчик | Остаток баланса API |
captcha.worker.active |
Датчик | Сколько воркеров активно прямо сейчас |
Счётчики (Counter) считают события нарастающим итогом, датчики (Gauge) показывают текущее значение на момент снятия, а гистограмма (Histogram) даёт распределение задержки — из неё Datadog строит перцентили p50/p95/p99 для дашборда ниже.
Python: интеграция через DogStatsD
Ниже — декоратор, который оборачивает вызов решения reCAPTCHA v2 и сам отправляет счётчики и гистограмму задержки в локальный агент DogStatsD:
import os
import time
import functools
import requests
from datadog import initialize, statsd
# Initialize Datadog
initialize(
statsd_host=os.environ.get("DD_AGENT_HOST", "localhost"),
statsd_port=int(os.environ.get("DD_DOGSTATSD_PORT", "8125"))
)
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
session = requests.Session()
def track_captcha_metrics(captcha_type="recaptcha_v2"):
"""Decorator to track solve metrics."""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
tags = [f"captcha_type:{captcha_type}"]
statsd.increment("captcha.solve.count", tags=tags)
start = time.time()
try:
result = func(*args, **kwargs)
elapsed = time.time() - start
if "solution" in result:
statsd.increment("captcha.solve.success", tags=tags)
statsd.histogram("captcha.solve.latency", elapsed, tags=tags)
else:
error = result.get("error", "unknown")
statsd.increment(
"captcha.solve.error",
tags=tags + [f"error:{error}"]
)
return result
except Exception as e:
statsd.increment(
"captcha.solve.error",
tags=tags + [f"error:{type(e).__name__}"]
)
raise
return wrapper
return decorator
@track_captcha_metrics(captcha_type="recaptcha_v2")
def solve_recaptcha(sitekey, pageurl):
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:
return {"error": data.get("request")}
captcha_id = data["request"]
for _ in range(60):
time.sleep(5)
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:
return {"solution": result["request"]}
if result.get("request") != "CAPCHA_NOT_READY":
return {"error": result.get("request")}
return {"error": "TIMEOUT"}
def report_balance():
"""Send balance as a gauge metric."""
resp = session.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "getbalance", "json": 1
})
data = resp.json()
if data.get("status") == 1:
balance = float(data["request"])
statsd.gauge("captcha.balance", balance)
return balance
return None
def report_queue_depth(depth):
"""Report current queue depth."""
statsd.gauge("captcha.queue.depth", depth)
def report_worker_count(active, total):
"""Report worker health."""
statsd.gauge("captcha.worker.active", active)
statsd.gauge("captcha.worker.total", total)
JavaScript: та же схема через hot-shots
Та же логика на Node.js — обёртка solveCaptchaWithMetrics шлёт счётчики и гистограмму через клиент hot-shots, а фоновый таймер каждую минуту репортит баланс:
const { StatsD } = require("hot-shots");
const axios = require("axios");
const API_KEY = process.env.CAPTCHAAI_API_KEY;
const dogstatsd = new StatsD({
host: process.env.DD_AGENT_HOST || "localhost",
port: parseInt(process.env.DD_DOGSTATSD_PORT || "8125", 10),
prefix: "captcha.",
globalTags: [`env:${process.env.NODE_ENV || "development"}`],
});
async function solveCaptchaWithMetrics(sitekey, pageurl, captchaType = "recaptcha_v2") {
const tags = [`captcha_type:${captchaType}`];
dogstatsd.increment("solve.count", 1, tags);
const startTime = Date.now();
try {
const result = await solveCaptcha(sitekey, pageurl);
const elapsed = (Date.now() - startTime) / 1000;
if (result.solution) {
dogstatsd.increment("solve.success", 1, tags);
dogstatsd.histogram("solve.latency", elapsed, tags);
} else {
dogstatsd.increment("solve.error", 1, [...tags, `error:${result.error}`]);
}
return result;
} catch (err) {
dogstatsd.increment("solve.error", 1, [...tags, `error:${err.message}`]);
throw err;
}
}
async function solveCaptcha(sitekey, pageurl) {
const submitResp = await axios.post("https://ocr.captchaai.com/in.php", null, {
params: {
key: API_KEY,
method: "userrecaptcha",
googlekey: sitekey,
pageurl: pageurl,
json: 1,
},
});
if (submitResp.data.status !== 1) {
return { error: submitResp.data.request };
}
const captchaId = submitResp.data.request;
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
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) return { solution: pollResp.data.request };
if (pollResp.data.request !== "CAPCHA_NOT_READY") {
return { error: pollResp.data.request };
}
}
return { error: "TIMEOUT" };
}
async function reportBalance() {
try {
const resp = await axios.get("https://ocr.captchaai.com/res.php", {
params: { key: API_KEY, action: "getbalance", json: 1 },
});
if (resp.data.status === 1) {
const balance = parseFloat(resp.data.request);
dogstatsd.gauge("balance", balance);
return balance;
}
} catch (err) {
console.error("Balance check failed:", err.message);
}
return null;
}
// Report balance every minute
setInterval(reportBalance, 60000);
module.exports = { solveCaptchaWithMetrics, reportBalance };
Готовый дашборд Datadog в JSON
Не собирайте виджеты вручную — импортируйте этот JSON как есть, и панель мониторинга CAPTCHA появится за минуту:
{
"title": "CaptchaAI Pipeline",
"widgets": [
{
"definition": {
"type": "timeseries",
"title": "Solve Rate (Success vs Error)",
"requests": [
{"q": "sum:captcha.solve.success{*}.as_count()"},
{"q": "sum:captcha.solve.error{*}.as_count()"}
]
}
},
{
"definition": {
"type": "timeseries",
"title": "Solve Latency (p50, p95, p99)",
"requests": [
{"q": "avg:captcha.solve.latency{*}"},
{"q": "percentile:captcha.solve.latency{*},0.95"},
{"q": "percentile:captcha.solve.latency{*},0.99"}
]
}
},
{
"definition": {
"type": "query_value",
"title": "API Balance",
"requests": [{"q": "avg:captcha.balance{*}"}]
}
},
{
"definition": {
"type": "timeseries",
"title": "Queue Depth",
"requests": [{"q": "avg:captcha.queue.depth{*}"}]
}
}
]
}
Четыре виджета покрывают минимум для дежурного: график успехов и ошибок в динамике, перцентили задержки p50/p95/p99, текущий баланс API одним числом и глубина очереди — этого достаточно, чтобы за пять секунд понять, деградирует пайплайн или нет, не открывая логи.
Какие алерты настроить в первую очередь
Алертов больше шести заводить не стоит — дежурный перестанет на них реагировать. Ниже — минимальный набор, который ловит почти все реальные инциденты пайплайна: закончившийся баланс, упавший воркер, рост очереди и всплеск ошибок или задержки.
| Алерт | Условие | Серьёзность |
|---|---|---|
| Низкий баланс | captcha.balance < 10 |
Предупреждение |
| Критический баланс | captcha.balance < 2 |
Критический |
| Высокая доля ошибок | Доля ошибок > 10% за 5 минут | Предупреждение |
| Скачок задержки | Задержка p95 > 120 с в течение 10 минут | Предупреждение |
| Очередь растёт | Глубина очереди > 100 и продолжает расти 5 минут подряд | Предупреждение |
| Воркер недоступен | captcha.worker.active == 0 |
Критический |
# Datadog monitor definition (API create)
- type: metric alert
name: "CaptchaAI Low Balance"
query: "avg(last_5m):avg:captcha.balance{*} < 10"
message: "CaptchaAI balance is low: {{value}}. Top up to avoid solve failures."
tags:
- team:scraping
- service:captcha
Устранение неполадок
Прежде чем разбираться с каждой метрикой отдельно, проверьте базовые вещи — почти все проблемы мониторинга сводятся к четырём причинам ниже.
| Проблема | Причина | Решение |
|---|---|---|
| Метрики не появляются в Datadog | Агент DogStatsD не запущен | Проверьте переменную DD_AGENT_HOST; убедитесь, что контейнер агента виден в docker ps |
| Гистограмма задержки пустая | Ни одно успешное решение не было зафиксировано | Проверьте, что statsd.histogram() вызывается именно в ветке успеха, а не только при ошибке |
| Теги не отображаются | Неверный формат тега | Используйте формат key:value, без пробелов внутри тега |
| Метрики дублируются | Одновременно работает несколько репортеров | Оставьте один процесс, который отправляет баланс, на весь деплой — иначе гистограммы и суммы задвоятся |
Частые вопросы
Нужен ли DogStatsD-агент на каждом воркере?
Нет, достаточно одного агента на хост. Все воркеры этого хоста отправляют метрики на локальный агент (DD_AGENT_HOST), а он уже сам агрегирует и пересылает их в Datadog.
Как выбрать порог алерта на баланс, чтобы не поймать простой в пиковую нагрузку?
Считайте порог от расхода в час, а не от произвольного числа. Если у вас, например, ADVANCE (50 потоков) и вечерний пик трафика из СНГ после 19:00 по МСК, порог captcha.balance < 10 должен покрывать хотя бы час работы на этой скорости — иначе алерт придёт, когда пополнить баланс уже не успеть до конца пика.
Можно ли одновременно собирать метрики CaptchaAI в Datadog и в Prometheus/Grafana?
Да, конфликта нет: statsd.increment() и statsd.gauge() из примеров выше просто шлют метрики в DogStatsD, а Prometheus-экспортер можно повесить рядом на том же коде — источник данных один, разница только в том, куда его отправлять и в какой системе строить графики.
Нужно ли исключать персональные данные из тегов метрик?
Да. В тегах captcha_type, error, env держите только технические значения — не кладите туда IP-адреса, cookies или другие данные пользователей. Это не только гигиена мониторинга, но и разумная предосторожность, если ваша команда обрабатывает персональные данные по 152-ФЗ или GDPR: собирайте и логируйте только то, что действительно нужно для эксплуатации.
С чего начать, если раньше в проекте вообще не было метрик?
Не пытайтесь сразу покрыть всё. На первой итерации хватит четырёх счётчиков (solve.count, solve.success, solve.error, balance) и одного алерта на низкий баланс — этого уже достаточно, чтобы не пропустить самый частый инцидент: пайплайн молча встал, потому что закончились потоки. Гистограмму задержки, дашборд и алерт на очередь добавляйте вторым шагом, когда появится история данных за пару недель и будет с чем сравнивать всплески.