Штатные конфликты и промахи ввода возвращались голым fmt.Errorf, поэтому
classifyErr отправлял их в 500 «внутренняя ошибка» вместо 409/400 (и logCmd
писал ERROR вместо DEBUG). Продолжение f8fb4fa (Tier A), по итогам ревью Fable.
Классификация ошибок:
- новый sentinel worker.ErrInvalidInput → 400 для валидации ввода команд
(refine/set type/ignore/add source/set provider/choose candidate);
- обёртки %w ErrConflict в Cancel/Retry/Defer/Undo (штатный конфликт состояния);
- classifyErr: ErrInvalidInput→400, layout.ErrCollision→409 (коллизия цели
штатно уводит в review); ветка ErrCollision в tgbot (сообщение + refreshCard);
- logCmd относит ErrInvalidInput и ErrCollision в DEBUG «command rejected».
Конвенции (docs/conventions):
- logging.md: публичные команды воркера = доменная граница (лог один раз,
logCmd); таблица уровней доменных отказов (граница команды vs асинхронная
стадия); правило про *url.Error/секреты в URL; канон категории
state transition; уровень повторяющихся сбоев фоновых циклов;
- errors.md: таблица маппинга ошибка→статус; развилка «транзиентный ответ vs
персистентная диагностика» решена как (а) — error_msg/reasons на review-экране
и tg-карточке = операторская поверхность владельца (сырой текст ок, секреты
запрещены; аудит подтвердил, что секреты туда не текут).
Унификация категории лога state transition: cancel/retry/relink/recovery
переведены с семантических msg на общий state transition (from/to) — весь
жизненный цикл собирается одним jq-фильтром.
Мелочи: reason-коды linkPlan в const-блок; httpapi лог-поля id→download_id и
msg «… failed»; комментарий «почему» у parseIgnored; preview build failure в
ReviewData DEBUG→WARN.
Беклог: задача сведена к остатку (ext.* ERROR-шторм при недоступном qBittorrent
+ эскалация устойчивого сбоя тика), понижена в приоритете.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
55 lines
4.1 KiB
Markdown
55 lines
4.1 KiB
Markdown
# Шум ERROR фоновых циклов при недоступной зависимости
|
||
|
||
**Приоритет:** низкий · **Теги:** review-fable, logging, reliability
|
||
|
||
Остаток от задачи «классификация доменных ошибок + конвенции логирования»
|
||
(основное реализовано, см. ниже). Здесь — два смежных пункта про уровень
|
||
повторяющихся сбоев фоновых циклов, каждый требует небольшого решения, а не
|
||
только правки.
|
||
|
||
## Что уже сделано (не переоткрывать)
|
||
|
||
Коммит `f8fb4fa` (Tier A) + коммит этой задачи закрыли:
|
||
|
||
- **Классификация доменных ошибок:** sentinel `worker.ErrInvalidInput`→400;
|
||
обёртки `ErrConflict` в Cancel/Retry/Defer/Undo; `layout.ErrCollision`→409 в
|
||
`classifyErr` и ветка в tgbot; `logCmd` относит новые классы в DEBUG.
|
||
- **Конвенции:** `logging.md` — команды воркера = доменная граница, таблица
|
||
уровней доменных отказов (граница команды vs асинхронная стадия), правило про
|
||
`*url.Error`/секреты в URL, канон категории `state transition` (унифицированы
|
||
cancel/retry/relink/recovery). `errors.md` — таблица маппинга ошибка→статус,
|
||
развилка «транзиентный ответ vs персистентная диагностика» решена как (а):
|
||
`error_msg`/`reasons` — операторская поверхность владельца (сырой текст ок,
|
||
секреты запрещены; аудит показал, что секреты туда не текут).
|
||
- **Мелочи:** reason-коды const-блок; лог-поля `id`→`download_id`; preview
|
||
WARN; комментарий у `parseIgnored`.
|
||
|
||
## Остаток
|
||
|
||
### ERROR-шторм при недоступном qBittorrent
|
||
|
||
Клиент `qbt` логирует `ext.*` `Failure` → **ERROR** на каждом тике поллинга
|
||
(`torrents/info`, `internal/qbt/qbt.go`), пока qBittorrent недоступен (рестарт
|
||
демона, сеть). Домен уже пишет `poll failed` = WARN (по новой конвенции), но
|
||
транспортная `ext.*`-запись остаётся ERROR по правилу ext-конвенции («сервис
|
||
недоступен → ERROR»). При частом поллинге это шумит.
|
||
|
||
Развилка (решить до правки):
|
||
|
||
- (а) Ввести у `logging.ExtCall` вариант с пониженным уровнем для рутинно-частых
|
||
вызовов (симметрично `SuccessDebug`) — поллинг-вызовы (`torrents/info`) на
|
||
транзиентном сбое пишут WARN, не ERROR;
|
||
- (б) Дедуп/circuit-breaker: первый ERROR, дальше тишина до восстановления;
|
||
- (в) Оставить как есть, признав `ext.*` ERROR легитимным сигналом «зависимость
|
||
лежит» (тогда шум гасить уровнем сбора, а не кодом).
|
||
|
||
### Эскалация устойчивого сбоя тика
|
||
|
||
Сейчас транзиентный сбой тика = WARN всегда. Договорённость на будущее
|
||
(`logging.md`): устойчивый сбой N тиков подряд эскалировать в ERROR (реальная
|
||
деградация, а не разовый промах). Не реализовано — нужен счётчик подряд-сбоев по
|
||
циклу и порог в конфиге.
|
||
|
||
Вердикт: мелкая надёжностная полировка, не блокер. Делать вместе (обе про
|
||
уровень сбоев фоновых циклов) или отдельной строкой.
|