Логирование: ревью всего кода и рефакторинг в соответствии с конвенциями
This commit is contained in:
+37
-15
@@ -20,11 +20,21 @@ import (
|
||||
"time"
|
||||
|
||||
"git.vakhrushev.me/av/jellybit/internal/layout"
|
||||
"git.vakhrushev.me/av/jellybit/internal/logctx"
|
||||
"git.vakhrushev.me/av/jellybit/internal/qbt"
|
||||
"git.vakhrushev.me/av/jellybit/internal/recognize"
|
||||
"git.vakhrushev.me/av/jellybit/internal/store"
|
||||
)
|
||||
|
||||
// Стадии (capability) — адресуют запись к подсистеме при корреляции по
|
||||
// download_id. Уровень логов от стадии не зависит.
|
||||
const (
|
||||
capIngest = "ingest" // приём/скачивание (поллинг, reconcile, переходы)
|
||||
capRecognize = "recognition" // распознавание фильма/сериала
|
||||
capFileLayout = "file-layout" // раскладка хардлинками
|
||||
capReview = "review" // ручные команды ревью
|
||||
)
|
||||
|
||||
// Store — нужная worker часть хранилища.
|
||||
type Store interface {
|
||||
ListDownloadsByState(ctx context.Context, states ...store.State) ([]store.Download, error)
|
||||
@@ -147,6 +157,17 @@ func defaultBatchID() string {
|
||||
return fmt.Sprintf("b-%d", time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// scoped кладёт в ctx scoped-логгер загрузки (capability + download_id
|
||||
// [+ infohash]); стадии и внешние клиенты достают его из ctx и дописывают эти
|
||||
// ключи на каждую запись сами — без ручного доклеивания download_id.
|
||||
func (w *Worker) scoped(ctx context.Context, capability string, id int64, infohash string) context.Context {
|
||||
log := w.log.With("capability", capability, "download_id", id)
|
||||
if infohash != "" {
|
||||
log = log.With("infohash", infohash)
|
||||
}
|
||||
return logctx.With(ctx, log)
|
||||
}
|
||||
|
||||
// Run крутит цикл поллинга до отмены ctx.
|
||||
func (w *Worker) Run(ctx context.Context) {
|
||||
w.log.Info("worker started", "poll_interval", w.cfg.PollInterval, "category", w.cfg.Category)
|
||||
@@ -167,7 +188,7 @@ func (w *Worker) Run(ctx context.Context) {
|
||||
|
||||
func (w *Worker) pollOnce(ctx context.Context) {
|
||||
if err := w.Poll(ctx); err != nil {
|
||||
w.log.Warn("poll failed", "err", err)
|
||||
w.log.Warn("poll failed", "error", err)
|
||||
}
|
||||
// Ф3: распознаём завершённые загрузки (и перезапускаем по подсказке).
|
||||
if w.recognizer != nil {
|
||||
@@ -209,7 +230,7 @@ func (w *Worker) Poll(ctx context.Context) error {
|
||||
t, ok := byHash[strings.ToLower(d.Infohash.String)]
|
||||
if !ok {
|
||||
w.log.Warn("active download not found in qbittorrent",
|
||||
"download_id", d.ID, "infohash", d.Infohash.String)
|
||||
"capability", capIngest, "download_id", d.ID, "infohash", d.Infohash.String)
|
||||
continue
|
||||
}
|
||||
w.reconcile(ctx, d, t)
|
||||
@@ -220,6 +241,7 @@ func (w *Worker) Poll(ctx context.Context) error {
|
||||
// reconcile двигает одну задачу по состоянию её торрента. Вызывается под
|
||||
// w.mu.
|
||||
func (w *Worker) reconcile(ctx context.Context, d store.Download, t qbt.Torrent) {
|
||||
ctx = w.scoped(ctx, capIngest, d.ID, d.Infohash.String)
|
||||
switch classify(t.State) {
|
||||
case classReady:
|
||||
w.transition(ctx, d, store.StateCompleted, "", "")
|
||||
@@ -237,7 +259,7 @@ func (w *Worker) reconcile(ctx context.Context, d store.Download, t qbt.Torrent)
|
||||
func (w *Worker) checkTimeouts(ctx context.Context, d store.Download, t qbt.Torrent) {
|
||||
created, err := d.CreatedTime()
|
||||
if err != nil {
|
||||
w.log.Warn("cannot parse created_at", "download_id", d.ID, "value", d.CreatedAt, "err", err)
|
||||
logctx.From(ctx).Warn("cannot parse created_at", "value", d.CreatedAt, "error", err)
|
||||
return
|
||||
}
|
||||
age := w.now().Sub(created)
|
||||
@@ -254,13 +276,14 @@ func (w *Worker) checkTimeouts(ctx context.Context, d store.Download, t qbt.Torr
|
||||
|
||||
// transition пишет новое состояние и логирует переход.
|
||||
func (w *Worker) transition(ctx context.Context, d store.Download, state store.State, code, msg string) {
|
||||
// FromOr, а не From: если вызывающий не завёл scoped-логгер, падаем на
|
||||
// w.log (настроенный), а не на slog.Default().
|
||||
log := logctx.FromOr(ctx, w.log)
|
||||
if err := w.store.SetDownloadState(ctx, d.ID, state, code, msg); err != nil {
|
||||
w.log.Error("state transition failed",
|
||||
"download_id", d.ID, "from", d.State, "to", state, "err", err)
|
||||
log.Error("state transition failed", "from", d.State, "to", state, "error", err)
|
||||
return
|
||||
}
|
||||
w.log.Info("state transition",
|
||||
"download_id", d.ID, "from", d.State, "to", state, "code", code)
|
||||
log.Info("state transition", "from", d.State, "to", state, "code", code)
|
||||
|
||||
// Пинги — неблокирующе и в отдельном контексте: вызов уходит в сеть, а
|
||||
// мы под w.mu (Notify читает состояние уже после освобождения замка).
|
||||
@@ -277,12 +300,11 @@ func (w *Worker) transition(ctx context.Context, d store.Download, state store.S
|
||||
// новые файлы быстрее появились в проигрывателе. Тоже неблокирующе и вне
|
||||
// w.mu; недоступность Jellyfin не влияет на состояние задачи.
|
||||
if w.scanner != nil && state == store.StateDone {
|
||||
id := d.ID
|
||||
go func() {
|
||||
if err := w.scanner.RefreshLibraries(context.Background()); err != nil {
|
||||
w.log.Warn("jellyfin: library refresh failed", "download_id", id, "err", err)
|
||||
}
|
||||
}()
|
||||
// Скан Jellyfin — неблокирующе и вне w.mu, в фоновом ctx со scoped-логгером
|
||||
// (download_id для корреляции ext.*-записи клиента). Недоступность Jellyfin
|
||||
// на задачу не влияет; ошибку вызова логирует сам клиент (ext.*), здесь гасим.
|
||||
gctx := w.scoped(context.Background(), capFileLayout, d.ID, d.Infohash.String)
|
||||
go func() { _ = w.scanner.RefreshLibraries(gctx) }()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,7 +324,7 @@ func (w *Worker) Cancel(ctx context.Context, id int64) error {
|
||||
if err := w.store.SetDownloadState(ctx, id, store.StateCancelled, "", ""); err != nil {
|
||||
return fmt.Errorf("cancel: %w", err)
|
||||
}
|
||||
w.log.Info("download cancelled", "download_id", id, "from", d.State)
|
||||
logctx.From(w.scoped(ctx, capReview, id, d.Infohash.String)).Info("download cancelled", "from", d.State)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -331,7 +353,7 @@ func (w *Worker) Retry(ctx context.Context, id int64) error {
|
||||
if err := w.store.SetDownloadState(ctx, id, store.StateDownloading, "", ""); err != nil {
|
||||
return fmt.Errorf("retry: %w", err)
|
||||
}
|
||||
w.log.Info("download retried", "download_id", id, "from", d.State)
|
||||
logctx.From(w.scoped(ctx, capReview, id, d.Infohash.String)).Info("download retried", "from", d.State)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user