Add per-source status tracking, /health sources block and /sources
Collectors record the result of every source (schema v3, table source_status); /health reports failing sources (failures in a row >= source_failure_threshold) and turns degraded; GET /sources shows the full state; runs end with a summary log line instead of "data saved". Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ab89c87321
commit
cb935fef0d
8 files changed
+351
-13
No files matched your search
@@ -0,0 +1,47 @@
|
||||
# План: наблюдаемость (сбои источников, здоровье, логи)
|
||||
|
||||
Источник: `docs/analysis-2026-09-21.md` (наблюдения 2 и 5, риск «сбои RIPE/DNS не видны»), пункт 2 рекомендуемого порядка. **Метрики Prometheus (`/metrics`) в этот план не входят** (решение пользователя); всё, что описано ниже, доступно через `/health`, `/sources`, лог и базу.
|
||||
|
||||
## Проблема
|
||||
Недоступный RIPEstat или DNS пишется только в лог: задание считается успешным, `/health` показывает `ok`, а данные источника не обновляются (до истечения TTL - 90 дней). Сбой одного из шести ASN невидим, пока адреса не начнут пропадать. В логе после каждого запуска стоит «data saved» даже без изменений, а итог запуска по источникам не сводится.
|
||||
|
||||
## Дизайн
|
||||
|
||||
### 1. Состояние источников (SQLite, схема версии 3)
|
||||
- Таблица `source_status(kind, source, last_attempt, last_success, failures, error_kind)`, ключ `(kind, source)`. Хранится в базе, а не в `status.json`: переживает перезапуск демона, пишется в той же транзакции, что и данные, попадает в резервные копии. Миграция 2 -> 3 автоматическая (как 1 -> 2), данные не затрагиваются.
|
||||
- Сборщик записывает результат по каждому источнику при каждом запуске: успех - `last_success = last_attempt = сейчас`, `failures = 0`, `error_kind = NULL`; сбой - `last_attempt = сейчас`, `failures + 1`, `error_kind`; `last_success` не меняется.
|
||||
- `error_kind` - короткая категория без деталей: `timeout`, `connection`, `http_429`, `http_4xx`, `http_5xx`, `invalid_response` (нечитаемый ответ), `dns`, `no_global_addresses` (имя разрешилось, но все адреса отфильтрованы), `unknown`. Полный текст ошибки остаётся в логе; наружу он не отдаётся (как и пути в находке 6 ревью).
|
||||
- Пустой, но корректный ответ RIPEstat считается успехом (число адресов видно в `/sources`).
|
||||
- Строки состояния источников, которых уже нет в конфигурации, удаляются в очередном запуске сбора и при `purge`.
|
||||
|
||||
### 2. `/health`
|
||||
- В ответ добавляется блок `sources`: по типам (`asn`, `fqdn`) число источников (`total`) и список **только неисправных** (`failing`): `source`, `failures`, `last_success`, `error_kind`. Компактно и без лишних деталей.
|
||||
- Источник считается неисправным при `failures >= source_failure_threshold` (ключ в `config.json`, по умолчанию 3): один сбой RIPEstat не «краснит» систему. При наличии неисправных источников `status` = `degraded`.
|
||||
- Существующие поля и поведение (`collector_alive`, `jobs`, `db_recreated`, `last_restore`) не меняются.
|
||||
|
||||
### 3. `GET /sources` (чтение открыто, как `/asns`)
|
||||
Полная картина по всем источникам: `kind`, `source`, `addresses` (сколько значений в базе), `last_attempt`, `last_success`, `failures`, `error_kind`. Позволяет увидеть источник, который «успешно» вернул ноль адресов или давно не обновлялся. Существующие `/asns` и `/fqdns` не меняются.
|
||||
|
||||
### 4. Логи
|
||||
- Итог запуска одной строкой: «ASN collection finished: 6 sources, 5 ok, 1 failed, +12/-3 prefixes» (для FQDN так же).
|
||||
- Сообщение «... data saved to ...» после каждой транзакции убирается (наблюдение 5 анализа); подробности по источникам остаются на уровне INFO.
|
||||
|
||||
## Изменения
|
||||
1. `db.py`: схема v3 и миграция, `record_source_result`, `source_statuses`, очистка состояния (`sweep`, `purge`).
|
||||
2. `cidr_collector.py`: классификация ошибок (`error_kind`), запись результата по каждому источнику в обоих `run_collection`, итоговая строка лога, ключ `source_failure_threshold`.
|
||||
3. `api_server.py`: блок `sources` и статус в `/health`, `GET /sources`.
|
||||
4. `README.md`: `/health`, `/sources`, ключ конфигурации, лог, схема базы.
|
||||
5. Тесты (2 новых, всего 30): сборщик (успех и сбой источников записываются, категории ошибок, сброс `failures` при успехе, очистка удалённого источника); API (`/health` `ok` до порога и `degraded` после, ответ без текста ошибок, `/sources` с числом адресов).
|
||||
|
||||
## Не входит
|
||||
- Метрики Prometheus и `/metrics` (исключены по решению пользователя).
|
||||
- Уведомления (webhook, Telegram) и алерты: `/health` и `/sources` пригодны для внешних проверок, сами уведомления - отдельная доработка.
|
||||
- Свежесть резервных копий как отдельный признак здоровья, команда CLI `status`, ограничение частоты `POST /collect`.
|
||||
- Проверка `docker healthcheck` не меняется: `degraded` по-прежнему HTTP 200, контейнер из-за внешнего сбоя перезапускать нечего.
|
||||
|
||||
## Проверка
|
||||
Тесты в контейнере. Вручную: миграция базы версии 2 (созданной кодом до изменения) с сохранением данных; локальный «RIPEstat» (как при проверке повторов) отвечает 503: после трёх запусков `/health` показывает `degraded` и источник в `failing`, после восстановления сервера `ok`; сравнение ответов существующих эндпоинтов до и после; Docker Compose (демон и API, ручной сбор).
|
||||
|
||||
## Риски и откат
|
||||
- Схема базы v3: таблица добавляется, существующие таблицы не меняются; откат кода оставляет лишнюю таблицу без последствий (старый код её не читает, `user_version` 3 у старого кода вызовет только пропуск миграции).
|
||||
- Порог 3 на редких расписаниях (сбор FQDN раз в сутки) означает три дня до `degraded`; порог настраивается.
|
||||
@@ -0,0 +1,24 @@
|
||||
# Итоги: наблюдаемость (сбои источников, здоровье, логи)
|
||||
|
||||
План: `docs/plan-observability.md`. Закрывает наблюдения 2 и 5 анализа (`analysis-2026-09-21.md`). **Метрики Prometheus не входили** (решение пользователя).
|
||||
|
||||
## Сделано
|
||||
- **Состояние источников (`db.py`):** схема версии 3, таблица `source_status(kind, source, last_attempt, last_success, failures, error_kind)`; автоматическая миграция 2 -> 3. Функции `record_source_result`, `forget_unconfigured`, `source_statuses`, `address_counts`; `purge_source` удаляет и состояние. Запись идёт в той же транзакции, что и данные.
|
||||
- **Сборщики (`cidr_collector.py`):** результат по каждому источнику при каждом запуске (успех обнуляет `failures`, сбой увеличивает, `last_success` при сбое не меняется); `classify_error` даёт категорию без деталей (`timeout`, `connection`, `http_429/4xx/5xx`, `invalid_response`, `dns`, `no_global_addresses`, `unknown`); состояние удалённых из конфигурации источников очищается при очередном сборе; порог `failure_threshold` (`source_failure_threshold`, по умолчанию 3).
|
||||
- **`/health` (`api_server.py`):** блок `sources` (по типам: `total` и только неисправные), `degraded` при неисправных источниках; остальные поля не менялись. **`GET /sources`:** все настроенные источники с числом адресов, временем попытки и успеха, сбоями и категорией.
|
||||
- **Логи:** итоговая строка запуска («ASN collection finished: 6 sources, 5 ok, 1 failed, +12/-3 prefixes», для FQDN так же); сообщения «data saved» убраны (наблюдение 5).
|
||||
- **README:** `/health`, `/sources`, ключ `source_failure_threshold`, схема базы, лог.
|
||||
- **Тесты:** 2 новых (сборщики: запись успеха и сбоев, рост и сброс счётчика, очистка удалённого источника, категории ошибок и миграция с версии 2; API: `ok` до порога и `degraded` после, ответ без текста ошибки, `/sources`, настройка порога). Всего 30, в контейнере 30 passed.
|
||||
|
||||
## Проверка
|
||||
- **Миграция:** база версии 2, созданная кодом до изменения (`SCHEMA_VERSION = 2` в предыдущем коммите), открыта новым кодом: `user_version` 3, все адреса и журнал на месте, состояние источников пусто.
|
||||
- **Отдельные процессы:** демон с подменённым адресом RIPEstat (локальный сервер отвечает 503) и API: после первых двух запусков `/health` `ok`, после третьего `degraded` с `{"source": "62041", "failures": 3, "error_kind": "http_5xx"}`; `/sources` показывает 0 адресов и 3 сбоя; после «починки» сервера `ok`, 1 адрес, `failures: 0`. В логе итоговые строки запусков, сообщений «data saved» нет.
|
||||
- **Docker Compose** (отдельный проект, порт 18000, стенд убран): FQDN `example.com` и `nonexistent.invalid`; после трёх ручных сборов `degraded`, неисправный `nonexistent.invalid` с `error_kind: dns`, `example.com` с 4 адресами; оба сервиса `healthy`.
|
||||
- В первом запуске проверки миграции сценарий упал на моей опечатке (обращение к закрытой сессии после записи), сама миграция прошла; версию «до» подтвердил по коду предыдущего коммита.
|
||||
|
||||
## Замечания
|
||||
- Порог 3 при суточном расписании FQDN означает три дня до `degraded`; порог настраивается (`source_failure_threshold`).
|
||||
- Пустой корректный ответ RIPEstat считается успехом: такой источник виден в `/sources` по `addresses: 0`, но `degraded` не вызывает.
|
||||
- Категория `no_global_addresses` считается сбоем: имя, разрешившееся только в частные адреса при выключенном `allow_non_global_ips`, через три запуска даст `degraded`.
|
||||
- `/health` по-прежнему отвечает HTTP 200 при `degraded`; Docker healthcheck из-за внешних сбоев контейнеры не перезапускает.
|
||||
- Не вошло: Prometheus, уведомления и алерты, свежесть резервных копий как признак здоровья, команда CLI `status`.
|
||||
Reference in new issue
Block a user