Логирование: доменная граница ошибок + защита секретов в логах
Приём торрента через Telegram молча падал без записи в логах. Разобрали цепочку и починили логирование/обработку ошибок по конвенции logging.md (логирует граница домена один раз, транспорты — нет). Доменная граница логирует исход: - ingest.Ingest: сбой БД → ERROR, невалидный источник → DEBUG; - команды воркера (Apply/Cancel/Retry/Refine/…) — единый чокпоинт logCmd (ERROR для инфраструктурного сбоя; DEBUG для conflict/not-ready/not-found), закрывает и Telegram-, и HTTP-путь; дублирующие ERROR-логи в tgbot сняты; - внутренний логгер tgbotapi заведён в slog: сбои long-poll getUpdates больше не уходят в stdlib log мимо структурированных логов; - тихое закрытие канала обновлений бота → ERROR. Защита секретов (инвариант «секреты не в логи»): - общий logging.SanitizeErr убирает URL из *url.Error; - закрыты утечки токена бота (getMe на старте, getFile, Send/Request) и api_key TMDB (query-параметр, попадавший в *url.Error на ERROR); - покрыто тестом internal/logging/sanitize_test.go. Ревью двумя сабагентами (fable): инфраструктурные и доменные ошибки. Отложено (не в scope этого коммита): обёртка ErrConflict в Cancel/Defer/Retry и классификация 500→409/400, обновление docs/conventions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -775,9 +775,33 @@ func (w *Worker) shouldNotifyFail(id string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// logCmd — единый чокпоинт логирования исхода команды воркера
|
||||
// (apply/cancel/retry/…), вызываемой транспортами. Конвенция (logging.md,
|
||||
// раздел «Ошибки»): доменную ошибку логирует граница домена ровно один раз, а
|
||||
// транспорты (HTTP/web/Telegram) — нет. Команды воркера и есть эта граница.
|
||||
//
|
||||
// Уровень — по адресату: штатный отказ по состоянию/наличию
|
||||
// (ErrConflict/ErrNotReady/ErrNotFound) адресован пользователю, он уже получил
|
||||
// ответ на поверхности — DEBUG; всё прочее (сбой БД/ФС/зависимости) адресовано
|
||||
// команде — ERROR. Успех (err == nil) — молча. Ставится в defer при именованном
|
||||
// возврате, поэтому видит финальную ошибку и scoped-логгер, накопленный в ctx.
|
||||
func (w *Worker) logCmd(ctx context.Context, cmd, id string, err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
log := logctx.FromOr(ctx, w.log)
|
||||
switch {
|
||||
case errors.Is(err, ErrConflict), errors.Is(err, ErrNotReady), errors.Is(err, store.ErrNotFound):
|
||||
log.Debug("command rejected", "command", cmd, "download_id", id, "error", err)
|
||||
default:
|
||||
log.Error("command failed", "command", cmd, "download_id", id, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel отклоняет задачу. Торрент в qBittorrent не трогаем — он продолжает
|
||||
// раздачу (источник неприкосновенен).
|
||||
func (w *Worker) Cancel(ctx context.Context, id string) error {
|
||||
func (w *Worker) Cancel(ctx context.Context, id string) (err error) {
|
||||
defer func() { w.logCmd(ctx, "cancel", id, err) }()
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
@@ -797,7 +821,8 @@ func (w *Worker) Cancel(ctx context.Context, id string) error {
|
||||
|
||||
// Retry повторяет застрявшую/упавшую задачу: заново отдаёт источник в
|
||||
// qBittorrent и возвращает в downloading.
|
||||
func (w *Worker) Retry(ctx context.Context, id string) error {
|
||||
func (w *Worker) Retry(ctx context.Context, id string) (err error) {
|
||||
defer func() { w.logCmd(ctx, "retry", id, err) }()
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user