From 0e88117702929d5ae4910ecf6a235de251ee3a84 Mon Sep 17 00:00:00 2001 From: Ludvig Lundgren Date: Tue, 5 Jul 2022 13:31:44 +0200 Subject: [PATCH] feat(logging); improve messages and errors (#336) * feat(logger): add module context * feat(logger): change errors package * feat(logger): update tests --- cmd/autobrr/main.go | 2 +- cmd/autobrrctl/main.go | 9 +- go.mod | 2 +- go.sum | 4 +- internal/action/exec_test.go | 5 +- internal/action/lidarr.go | 1 + internal/action/qbittorrent.go | 5 +- internal/action/radarr.go | 1 + internal/action/service.go | 18 ++- internal/action/sonarr.go | 6 +- internal/action/whisparr.go | 1 + internal/announce/announce.go | 15 ++- internal/auth/service.go | 10 +- internal/database/action.go | 83 +++++------- internal/database/database.go | 12 +- internal/database/download_client.go | 57 ++++----- internal/database/feed.go | 54 +++----- internal/database/feed_cache.go | 27 ++-- internal/database/filter.go | 98 +++++--------- internal/database/indexer.go | 53 +++----- internal/database/irc.go | 110 ++++++---------- internal/database/notification.go | 55 +++----- internal/database/postgres.go | 24 ++-- internal/database/release.go | 59 ++++----- internal/database/sqlite.go | 24 ++-- internal/database/user.go | 36 +++--- internal/domain/client.go | 1 - internal/domain/release.go | 10 +- internal/domain/releasetags.go | 4 +- internal/download_client/connection.go | 23 ++-- internal/download_client/service.go | 58 +++++++-- internal/events/subscribers.go | 7 +- internal/feed/service.go | 74 +++++++---- internal/feed/torznab.go | 22 +++- internal/filter/service.go | 8 +- internal/indexer/api.go | 28 ++-- internal/indexer/service.go | 43 +++++-- internal/irc/handler.go | 6 +- internal/irc/service.go | 23 ++-- internal/mock/indexer_api.go | 5 +- internal/notification/discord.go | 26 ++-- internal/notification/service.go | 26 +++- internal/notification/telegram.go | 20 +-- internal/release/service.go | 32 +++-- internal/scheduler/service.go | 10 +- internal/server/server.go | 6 +- internal/user/service.go | 2 +- pkg/btn/btn.go | 11 +- pkg/btn/client.go | 13 +- pkg/errors/errors.go | 171 +++++++++++++++++++++++++ pkg/ggn/ggn.go | 26 ++-- pkg/jsonrpc/jsonrpc.go | 18 +-- pkg/lidarr/client.go | 30 +++-- pkg/lidarr/lidarr.go | 50 ++++++-- pkg/lidarr/lidarr_test.go | 25 ++-- pkg/ptp/ptp.go | 24 ++-- pkg/qbittorrent/client.go | 82 ++++++------ pkg/qbittorrent/methods.go | 91 +++++-------- pkg/radarr/client.go | 41 +++--- pkg/radarr/radarr.go | 55 ++++++-- pkg/radarr/radarr_test.go | 39 +++--- pkg/red/red.go | 27 ++-- pkg/red/red_test.go | 16 +-- pkg/sonarr/client.go | 40 +++--- pkg/sonarr/sonarr.go | 55 ++++++-- pkg/sonarr/sonarr_test.go | 28 ++-- pkg/torznab/client.go | 26 ++-- pkg/whisparr/client.go | 20 +-- pkg/whisparr/whisparr.go | 36 +++--- 69 files changed, 1172 insertions(+), 957 deletions(-) create mode 100644 pkg/errors/errors.go diff --git a/cmd/autobrr/main.go b/cmd/autobrr/main.go index 033876c..4f0b0e5 100644 --- a/cmd/autobrr/main.go +++ b/cmd/autobrr/main.go @@ -91,7 +91,7 @@ func main() { schedulingService = scheduler.NewService(log) apiService = indexer.NewAPIService(log) userService = user.NewService(userRepo) - authService = auth.NewService(userService) + authService = auth.NewService(log, userService) downloadClientService = download_client.NewService(log, downloadClientRepo) actionService = action.NewService(log, actionRepo, downloadClientService, bus) indexerService = indexer.NewService(log, cfg.Config, indexerRepo, apiService, schedulingService) diff --git a/cmd/autobrrctl/main.go b/cmd/autobrrctl/main.go index 75caba2..d2c57d2 100644 --- a/cmd/autobrrctl/main.go +++ b/cmd/autobrrctl/main.go @@ -8,12 +8,13 @@ import ( "log" "os" - "golang.org/x/crypto/ssh/terminal" - _ "modernc.org/sqlite" - "github.com/autobrr/autobrr/internal/database" "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/pkg/argon2id" + "github.com/autobrr/autobrr/pkg/errors" + + "golang.org/x/crypto/ssh/terminal" + _ "modernc.org/sqlite" ) const usage = `usage: autobrrctl --config path @@ -131,7 +132,7 @@ func readPassword() ([]byte, error) { password = scanner.Bytes() if len(password) == 0 { - return nil, fmt.Errorf("zero length password") + return nil, errors.New("zero length password") } } diff --git a/go.mod b/go.mod index ee3d348..ba8ff58 100644 --- a/go.mod +++ b/go.mod @@ -64,7 +64,7 @@ require ( github.com/subosito/gotenv v1.3.0 // indirect golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect - golang.org/x/sys v0.0.0-20220614162138-6c1b26c55098 // indirect + golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/tools v0.1.10 // indirect diff --git a/go.sum b/go.sum index b887c0f..17934eb 100644 --- a/go.sum +++ b/go.sum @@ -615,8 +615,8 @@ golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220614162138-6c1b26c55098 h1:PgOr27OhUx2IRqGJ2RxAWI4dJQ7bi9cSrB82uzFzfUA= -golang.org/x/sys v0.0.0-20220614162138-6c1b26c55098/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e h1:CsOuNlbOuf0mzxJIefr6Q4uAUetRUwZE4qt7VfzP+xo= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/internal/action/exec_test.go b/internal/action/exec_test.go index 98eb11c..32f1641 100644 --- a/internal/action/exec_test.go +++ b/internal/action/exec_test.go @@ -5,6 +5,7 @@ import ( "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/logger" + "github.com/stretchr/testify/assert" ) @@ -63,7 +64,7 @@ func Test_service_parseExecArgs(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := &service{ - log: logger.Mock(), + log: logger.Mock().With().Logger(), repo: nil, clientSvc: nil, bus: nil, @@ -102,7 +103,7 @@ func Test_service_execCmd(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := &service{ - log: logger.Mock(), + log: logger.Mock().With().Logger(), repo: nil, clientSvc: nil, bus: nil, diff --git a/internal/action/lidarr.go b/internal/action/lidarr.go index ba8125a..0d9f20c 100644 --- a/internal/action/lidarr.go +++ b/internal/action/lidarr.go @@ -30,6 +30,7 @@ func (s *service) lidarr(release domain.Release, action domain.Action) ([]string cfg := lidarr.Config{ Hostname: client.Host, APIKey: client.Settings.APIKey, + Log: s.subLogger, } // only set basic auth if enabled diff --git a/internal/action/qbittorrent.go b/internal/action/qbittorrent.go index 0d43e44..6de637d 100644 --- a/internal/action/qbittorrent.go +++ b/internal/action/qbittorrent.go @@ -56,10 +56,10 @@ func (s *service) qbittorrent(qbt *qbittorrent.Client, action domain.Action, rel options["tags"] = tagsArgs } if action.LimitUploadSpeed > 0 { - options["upLimit"] = strconv.FormatInt(action.LimitUploadSpeed * 1000, 10) + options["upLimit"] = strconv.FormatInt(action.LimitUploadSpeed*1000, 10) } if action.LimitDownloadSpeed > 0 { - options["dlLimit"] = strconv.FormatInt(action.LimitDownloadSpeed * 1000, 10) + options["dlLimit"] = strconv.FormatInt(action.LimitDownloadSpeed*1000, 10) } if action.LimitRatio > 0 { options["ratioLimit"] = strconv.FormatFloat(action.LimitRatio, 'r', 2, 64) @@ -109,6 +109,7 @@ func (s *service) qbittorrentCheckRulesCanDownload(action domain.Action) (bool, Password: client.Password, TLS: client.TLS, TLSSkipVerify: client.TLSSkipVerify, + Log: s.subLogger, } // only set basic auth if enabled diff --git a/internal/action/radarr.go b/internal/action/radarr.go index 0515d24..b7b6acb 100644 --- a/internal/action/radarr.go +++ b/internal/action/radarr.go @@ -29,6 +29,7 @@ func (s *service) radarr(release domain.Release, action domain.Action) ([]string cfg := radarr.Config{ Hostname: client.Host, APIKey: client.Settings.APIKey, + Log: s.subLogger, } // only set basic auth if enabled diff --git a/internal/action/service.go b/internal/action/service.go index 95be8a7..cade767 100644 --- a/internal/action/service.go +++ b/internal/action/service.go @@ -2,12 +2,15 @@ package action import ( "context" - - "github.com/asaskevich/EventBus" + "log" "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/download_client" "github.com/autobrr/autobrr/internal/logger" + + "github.com/asaskevich/EventBus" + "github.com/dcarbone/zadapters/zstdlog" + "github.com/rs/zerolog" ) type Service interface { @@ -22,19 +25,24 @@ type Service interface { } type service struct { - log logger.Logger + log zerolog.Logger + subLogger *log.Logger repo domain.ActionRepo clientSvc download_client.Service bus EventBus.Bus } func NewService(log logger.Logger, repo domain.ActionRepo, clientSvc download_client.Service, bus EventBus.Bus) Service { - return &service{ - log: log, + s := &service{ + log: log.With().Str("module", "action").Logger(), repo: repo, clientSvc: clientSvc, bus: bus, } + + s.subLogger = zstdlog.NewStdLoggerWithLevel(s.log.With().Logger(), zerolog.TraceLevel) + + return s } func (s *service) Store(ctx context.Context, action domain.Action) (*domain.Action, error) { diff --git a/internal/action/sonarr.go b/internal/action/sonarr.go index 3979613..9c45a56 100644 --- a/internal/action/sonarr.go +++ b/internal/action/sonarr.go @@ -5,6 +5,7 @@ import ( "time" "github.com/autobrr/autobrr/internal/domain" + "github.com/autobrr/autobrr/pkg/errors" "github.com/autobrr/autobrr/pkg/sonarr" ) @@ -17,18 +18,19 @@ func (s *service) sonarr(release domain.Release, action domain.Action) ([]string client, err := s.clientSvc.FindByID(context.TODO(), action.ClientID) if err != nil { s.log.Error().Err(err).Msgf("sonarr: error finding client: %v", action.ClientID) - return nil, err + return nil, errors.Wrap(err, "sonarr could not find client: %v", action.ClientID) } // return early if no client found if client == nil { - return nil, err + return nil, errors.New("no client found") } // initial config cfg := sonarr.Config{ Hostname: client.Host, APIKey: client.Settings.APIKey, + Log: s.subLogger, } // only set basic auth if enabled diff --git a/internal/action/whisparr.go b/internal/action/whisparr.go index 0438db5..7380095 100644 --- a/internal/action/whisparr.go +++ b/internal/action/whisparr.go @@ -29,6 +29,7 @@ func (s *service) whisparr(release domain.Release, action domain.Action) ([]stri cfg := whisparr.Config{ Hostname: client.Host, APIKey: client.Settings.APIKey, + Log: s.subLogger, } // only set basic auth if enabled diff --git a/internal/announce/announce.go b/internal/announce/announce.go index d6cc2a9..2fbb042 100644 --- a/internal/announce/announce.go +++ b/internal/announce/announce.go @@ -2,17 +2,16 @@ package announce import ( "bytes" - "errors" - "fmt" "net/url" "regexp" "strings" "text/template" - "github.com/rs/zerolog" - "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/release" + "github.com/autobrr/autobrr/pkg/errors" + + "github.com/rs/zerolog" ) type Processor interface { @@ -30,7 +29,7 @@ type announceProcessor struct { func NewAnnounceProcessor(log zerolog.Logger, releaseSvc release.Service, indexer *domain.IndexerDefinition) Processor { ap := &announceProcessor{ - log: log, + log: log.With().Str("module", "announce_processor").Logger(), releaseSvc: releaseSvc, indexer: indexer, } @@ -129,7 +128,7 @@ func (a *announceProcessor) AddLineToQueue(channel string, line string) error { channel = strings.ToLower(channel) queue, ok := a.queues[channel] if !ok { - return fmt.Errorf("no queue for channel (%v) found", channel) + return errors.New("no queue for channel (%v) found", channel) } queue <- line @@ -226,6 +225,8 @@ func (a *announceProcessor) processTorrentUrl(match string, vars map[string]stri return "", err } + a.log.Trace().Msg("torrenturl processed") + return b.String(), nil } @@ -234,7 +235,7 @@ func removeElement(s []string, i int) ([]string, error) { // perform bounds checking first to prevent a panic! if i >= len(s) || i < 0 { - return nil, fmt.Errorf("Index is out of range. Index is %d with slice length %d", i, len(s)) + return nil, errors.New("Index is out of range. Index is %d with slice length %d", i, len(s)) } // This creates a new slice by creating 2 slices from the original: diff --git a/internal/auth/service.go b/internal/auth/service.go index 4f7e761..bfb8407 100644 --- a/internal/auth/service.go +++ b/internal/auth/service.go @@ -4,6 +4,9 @@ import ( "context" "errors" + "github.com/autobrr/autobrr/internal/logger" + "github.com/rs/zerolog" + "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/user" "github.com/autobrr/autobrr/pkg/argon2id" @@ -16,11 +19,13 @@ type Service interface { } type service struct { + log zerolog.Logger userSvc user.Service } -func NewService(userSvc user.Service) Service { +func NewService(log logger.Logger, userSvc user.Service) Service { return &service{ + log: log.With().Str("module", "auth").Logger(), userSvc: userSvc, } } @@ -37,6 +42,7 @@ func (s *service) Login(ctx context.Context, username, password string) (*domain // find user u, err := s.userSvc.FindByUsername(ctx, username) if err != nil { + s.log.Error().Err(err).Msgf("could not find user by username: %v", username) return nil, err } @@ -51,6 +57,7 @@ func (s *service) Login(ctx context.Context, username, password string) (*domain } if !match { + s.log.Error().Msg("bad credentials") return nil, errors.New("bad credentials") } @@ -81,6 +88,7 @@ func (s *service) CreateUser(ctx context.Context, username, password string) err Password: hashed, } if err := s.userSvc.CreateUser(context.Background(), newUser); err != nil { + s.log.Error().Err(err).Msgf("could not create user: %v", username) return errors.New("failed to create new user") } diff --git a/internal/database/action.go b/internal/database/action.go index 72fab31..54d3f9b 100644 --- a/internal/database/action.go +++ b/internal/database/action.go @@ -7,19 +7,21 @@ import ( "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/logger" + "github.com/autobrr/autobrr/pkg/errors" sq "github.com/Masterminds/squirrel" + "github.com/rs/zerolog" ) type ActionRepo struct { - log logger.Logger + log zerolog.Logger db *DB clientRepo domain.DownloadClientRepo } func NewActionRepo(log logger.Logger, db *DB, clientRepo domain.DownloadClientRepo) domain.ActionRepo { return &ActionRepo{ - log: log, + log: log.With().Str("repo", "action").Logger(), db: db, clientRepo: clientRepo, } @@ -87,14 +89,12 @@ func (r *ActionRepo) findByFilterID(ctx context.Context, tx *Tx, filterID int) ( query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("action.findByFilterID: error building query") - return nil, err + return nil, errors.Wrap(err, "error building query") } rows, err := tx.QueryContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("action.findByFilterID: query error") - return nil, err + return nil, errors.Wrap(err, "error executing query") } defer rows.Close() @@ -112,8 +112,7 @@ func (r *ActionRepo) findByFilterID(ctx context.Context, tx *Tx, filterID int) ( var paused, ignoreRules sql.NullBool if err := rows.Scan(&a.ID, &a.Name, &a.Type, &a.Enabled, &execCmd, &execArgs, &watchFolder, &category, &tags, &label, &savePath, &paused, &ignoreRules, &limitDl, &limitUl, &limitRatio, &limitSeedTime, &a.ReAnnounceSkip, &a.ReAnnounceDelete, &a.ReAnnounceInterval, &a.ReAnnounceMaxAttempts, &webhookHost, &webhookType, &webhookMethod, &webhookData, &clientID); err != nil { - r.log.Error().Stack().Err(err).Msg("action.findByFilterID: error scanning row") - return nil, err + return nil, errors.Wrap(err, "error scanning row") } a.ExecCmd = execCmd.String @@ -141,8 +140,7 @@ func (r *ActionRepo) findByFilterID(ctx context.Context, tx *Tx, filterID int) ( actions = append(actions, &a) } if err := rows.Err(); err != nil { - r.log.Error().Stack().Err(err).Msg("action.findByFilterID: row error") - return nil, err + return nil, errors.Wrap(err, "row error") } return actions, nil @@ -168,28 +166,24 @@ func (r *ActionRepo) attachDownloadClient(ctx context.Context, tx *Tx, clientID query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("action.attachDownloadClient: error building query") - return nil, err + return nil, errors.Wrap(err, "error building query") } row := tx.QueryRowContext(ctx, query, args...) if err := row.Err(); err != nil { - r.log.Error().Stack().Err(err).Msg("action.attachDownloadClient: error query row") - return nil, err + return nil, errors.Wrap(err, "error executing query") } var client domain.DownloadClient var settingsJsonStr string if err := row.Scan(&client.ID, &client.Name, &client.Type, &client.Enabled, &client.Host, &client.Port, &client.TLS, &client.TLSSkipVerify, &client.Username, &client.Password, &settingsJsonStr); err != nil { - r.log.Error().Stack().Err(err).Msg("action.attachDownloadClient: error scanning row") - return nil, err + return nil, errors.Wrap(err, "error scanning row") } if settingsJsonStr != "" { if err := json.Unmarshal([]byte(settingsJsonStr), &client.Settings); err != nil { - r.log.Error().Stack().Err(err).Msgf("action.attachDownloadClient: could not marshal download client settings %v", settingsJsonStr) - return nil, err + return nil, errors.Wrap(err, "could not unmarshal download client settings: %v", settingsJsonStr) } } @@ -230,14 +224,12 @@ func (r *ActionRepo) List(ctx context.Context) ([]domain.Action, error) { query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("action.list: error building query") - return nil, err + return nil, errors.Wrap(err, "error building query") } rows, err := r.db.handler.QueryContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("action.list: error executing query") - return nil, err + return nil, errors.Wrap(err, "error executing query") } defer rows.Close() @@ -253,8 +245,7 @@ func (r *ActionRepo) List(ctx context.Context) ([]domain.Action, error) { var paused, ignoreRules sql.NullBool if err := rows.Scan(&a.ID, &a.Name, &a.Type, &a.Enabled, &execCmd, &execArgs, &watchFolder, &category, &tags, &label, &savePath, &paused, &ignoreRules, &limitDl, &limitUl, &limitRatio, &limitSeedTime, &a.ReAnnounceSkip, &a.ReAnnounceDelete, &a.ReAnnounceInterval, &a.ReAnnounceMaxAttempts, &webhookHost, &webhookType, &webhookMethod, &webhookData, &clientID); err != nil { - r.log.Error().Stack().Err(err).Msg("action.list: error scanning row") - return nil, err + return nil, errors.Wrap(err, "error scanning row") } a.Category = category.String @@ -279,8 +270,7 @@ func (r *ActionRepo) List(ctx context.Context) ([]domain.Action, error) { actions = append(actions, a) } if err := rows.Err(); err != nil { - r.log.Error().Stack().Err(err).Msg("action.list: row error") - return nil, err + return nil, errors.Wrap(err, "rows error") } return actions, nil @@ -293,14 +283,12 @@ func (r *ActionRepo) Delete(actionID int) error { query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("action.delete: error building query") - return err + return errors.Wrap(err, "error building query") } _, err = r.db.handler.Exec(query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("action.delete: error executing query") - return err + return errors.Wrap(err, "error executing query") } r.log.Debug().Msgf("action.delete: %v", actionID) @@ -315,14 +303,12 @@ func (r *ActionRepo) DeleteByFilterID(ctx context.Context, filterID int) error { query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("action.deleteByFilterID: error building query") - return err + return errors.Wrap(err, "error building query") } _, err = r.db.handler.ExecContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("action.deleteByFilterID: error executing query") - return err + return errors.Wrap(err, "error executing query") } r.log.Debug().Msgf("action.deleteByFilterID: %v", filterID) @@ -415,8 +401,7 @@ func (r *ActionRepo) Store(ctx context.Context, action domain.Action) (*domain.A err := queryBuilder.QueryRowContext(ctx).Scan(&retID) if err != nil { - r.log.Error().Stack().Err(err).Msg("action.store: error executing query") - return nil, err + return nil, errors.Wrap(err, "error executing query") } r.log.Debug().Msgf("action.store: added new %v", retID) @@ -480,14 +465,12 @@ func (r *ActionRepo) Update(ctx context.Context, action domain.Action) (*domain. query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("action.update: error building query") - return nil, err + return nil, errors.Wrap(err, "error building query") } _, err = r.db.handler.ExecContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("action.update: error executing query") - return nil, err + return nil, errors.Wrap(err, "error executing query") } r.log.Debug().Msgf("action.update: %v", action.ID) @@ -498,7 +481,7 @@ func (r *ActionRepo) Update(ctx context.Context, action domain.Action) (*domain. func (r *ActionRepo) StoreFilterActions(ctx context.Context, actions []*domain.Action, filterID int64) ([]*domain.Action, error) { tx, err := r.db.handler.BeginTx(ctx, nil) if err != nil { - return nil, err + return nil, errors.Wrap(err, "error begin transaction") } defer tx.Rollback() @@ -509,13 +492,11 @@ func (r *ActionRepo) StoreFilterActions(ctx context.Context, actions []*domain.A deleteQuery, deleteArgs, err := deleteQueryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("action.StoreFilterActions: error building query") - return nil, err + return nil, errors.Wrap(err, "error building query") } _, err = tx.ExecContext(ctx, deleteQuery, deleteArgs...) if err != nil { - r.log.Error().Stack().Err(err).Msg("action.StoreFilterActions: error executing query") - return nil, err + return nil, errors.Wrap(err, "error executing query") } for _, action := range actions { @@ -602,8 +583,7 @@ func (r *ActionRepo) StoreFilterActions(ctx context.Context, actions []*domain.A err = queryBuilder.QueryRowContext(ctx).Scan(&retID) if err != nil { - r.log.Error().Stack().Err(err).Msg("action.StoreFilterActions: error executing query") - return nil, err + return nil, errors.Wrap(err, "error executing query") } action.ID = retID @@ -613,8 +593,7 @@ func (r *ActionRepo) StoreFilterActions(ctx context.Context, actions []*domain.A err = tx.Commit() if err != nil { - r.log.Error().Stack().Err(err).Msg("action.StoreFilterActions: error updating actions") - return nil, err + return nil, errors.Wrap(err, "error updating filter actions") } @@ -631,14 +610,12 @@ func (r *ActionRepo) ToggleEnabled(actionID int) error { query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("action.toggleEnabled: error building query") - return err + return errors.Wrap(err, "error building query") } _, err = r.db.handler.Exec(query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("action.toggleEnabled: error executing query") - return err + return errors.Wrap(err, "error executing query") } r.log.Debug().Msgf("action.toggleEnabled: %v", actionID) diff --git a/internal/database/database.go b/internal/database/database.go index 80869f3..33c0654 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -8,12 +8,14 @@ import ( "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/logger" + "github.com/autobrr/autobrr/pkg/errors" sq "github.com/Masterminds/squirrel" + "github.com/rs/zerolog" ) type DB struct { - log logger.Logger + log zerolog.Logger handler *sql.DB lock sync.RWMutex ctx context.Context @@ -29,7 +31,7 @@ func NewDB(cfg *domain.Config, log logger.Logger) (*DB, error) { db := &DB{ // set default placeholder for squirrel to support both sqlite and postgres squirrel: sq.StatementBuilder.PlaceholderFormat(sq.Dollar), - log: log, + log: log.With().Str("module", "database").Logger(), } db.ctx, db.cancel = context.WithCancel(context.Background()) @@ -39,12 +41,12 @@ func NewDB(cfg *domain.Config, log logger.Logger) (*DB, error) { db.DSN = dataSourceName(cfg.ConfigPath, "autobrr.db") case "postgres": if cfg.PostgresHost == "" || cfg.PostgresPort == 0 || cfg.PostgresDatabase == "" { - return nil, fmt.Errorf("postgres: bad variables") + return nil, errors.New("postgres: bad variables") } db.DSN = fmt.Sprintf("postgres://%v:%v@%v:%d/%v?sslmode=disable", cfg.PostgresUser, cfg.PostgresPass, cfg.PostgresHost, cfg.PostgresPort, cfg.PostgresDatabase) db.Driver = "postgres" default: - return nil, fmt.Errorf("unsupported databse: %v", cfg.DatabaseType) + return nil, errors.New("unsupported databse: %v", cfg.DatabaseType) } return db, nil @@ -52,7 +54,7 @@ func NewDB(cfg *domain.Config, log logger.Logger) (*DB, error) { func (db *DB) Open() error { if db.DSN == "" { - return fmt.Errorf("DSN required") + return errors.New("DSN required") } var err error diff --git a/internal/database/download_client.go b/internal/database/download_client.go index 62697e2..be91113 100644 --- a/internal/database/download_client.go +++ b/internal/database/download_client.go @@ -7,10 +7,13 @@ import ( "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/logger" + "github.com/autobrr/autobrr/pkg/errors" + + "github.com/rs/zerolog" ) type DownloadClientRepo struct { - log logger.Logger + log zerolog.Logger db *DB cache *clientCache } @@ -50,7 +53,7 @@ func (c *clientCache) Pop(id int) { func NewDownloadClientRepo(log logger.Logger, db *DB) domain.DownloadClientRepo { return &DownloadClientRepo{ - log: log, + log: log.With().Str("repo", "action").Logger(), db: db, cache: NewClientCache(), } @@ -77,14 +80,12 @@ func (r *DownloadClientRepo) List(ctx context.Context) ([]domain.DownloadClient, query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("download_client.list: error building query") - return nil, err + return nil, errors.Wrap(err, "error building query") } rows, err := r.db.handler.QueryContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("download_client.list: error executing query") - return nil, err + return nil, errors.Wrap(err, "error executing query") } defer rows.Close() @@ -94,22 +95,19 @@ func (r *DownloadClientRepo) List(ctx context.Context) ([]domain.DownloadClient, var settingsJsonStr string if err := rows.Scan(&f.ID, &f.Name, &f.Type, &f.Enabled, &f.Host, &f.Port, &f.TLS, &f.TLSSkipVerify, &f.Username, &f.Password, &settingsJsonStr); err != nil { - r.log.Error().Stack().Err(err).Msg("download_client.list: error scanning row") - return clients, err + return clients, errors.Wrap(err, "error scanning row") } if settingsJsonStr != "" { if err := json.Unmarshal([]byte(settingsJsonStr), &f.Settings); err != nil { - r.log.Error().Stack().Err(err).Msgf("could not marshal download client settings %v", settingsJsonStr) - return clients, err + return clients, errors.Wrap(err, "could not unmarshal download client settings: %v", settingsJsonStr) } } clients = append(clients, f) } if err := rows.Err(); err != nil { - r.log.Error().Stack().Err(err).Msg("download_client.list: row error") - return clients, err + return clients, errors.Wrap(err, "rows error") } return clients, nil @@ -141,28 +139,24 @@ func (r *DownloadClientRepo) FindByID(ctx context.Context, id int32) (*domain.Do query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("download_client.findByID: error building query") - return nil, err + return nil, errors.Wrap(err, "error building query") } row := r.db.handler.QueryRowContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("download_client.findByID: error executing query") - return nil, err + return nil, errors.Wrap(err, "error executing query") } var client domain.DownloadClient var settingsJsonStr string if err := row.Scan(&client.ID, &client.Name, &client.Type, &client.Enabled, &client.Host, &client.Port, &client.TLS, &client.TLSSkipVerify, &client.Username, &client.Password, &settingsJsonStr); err != nil { - r.log.Error().Stack().Err(err).Msg("download_client.findByID: error scanning row") - return nil, err + return nil, errors.Wrap(err, "error scanning row") } if settingsJsonStr != "" { if err := json.Unmarshal([]byte(settingsJsonStr), &client.Settings); err != nil { - r.log.Error().Stack().Err(err).Msgf("could not marshal download client settings %v", settingsJsonStr) - return nil, err + return nil, errors.Wrap(err, "could not unmarshal download client settings: %v", settingsJsonStr) } } @@ -180,8 +174,7 @@ func (r *DownloadClientRepo) Store(ctx context.Context, client domain.DownloadCl settingsJson, err := json.Marshal(&settings) if err != nil { - r.log.Error().Stack().Err(err).Msgf("could not marshal download client settings %v", settings) - return nil, err + return nil, errors.Wrap(err, "error marshal download client settings %+v", settings) } queryBuilder := r.db.squirrel. @@ -195,8 +188,7 @@ func (r *DownloadClientRepo) Store(ctx context.Context, client domain.DownloadCl err = queryBuilder.QueryRowContext(ctx).Scan(&retID) if err != nil { - r.log.Error().Stack().Err(err).Msg("download_client.store: error executing query") - return nil, err + return nil, errors.Wrap(err, "error executing query") } client.ID = retID @@ -220,8 +212,7 @@ func (r *DownloadClientRepo) Update(ctx context.Context, client domain.DownloadC settingsJson, err := json.Marshal(&settings) if err != nil { - r.log.Error().Stack().Err(err).Msgf("could not marshal download client settings %v", settings) - return nil, err + return nil, errors.Wrap(err, "error marshal download client settings %+v", settings) } queryBuilder := r.db.squirrel. @@ -240,14 +231,12 @@ func (r *DownloadClientRepo) Update(ctx context.Context, client domain.DownloadC query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("download_client.update: error building query") - return nil, err + return nil, errors.Wrap(err, "error building query") } _, err = r.db.handler.ExecContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("download_client.update: error querying data") - return nil, err + return nil, errors.Wrap(err, "error executing query") } r.log.Debug().Msgf("download_client.update: %d", client.ID) @@ -265,14 +254,12 @@ func (r *DownloadClientRepo) Delete(ctx context.Context, clientID int) error { query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("download_client.delete: error building query") - return err + return errors.Wrap(err, "error building query") } res, err := r.db.handler.ExecContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("download_client.delete: error query data") - return err + return errors.Wrap(err, "error executing query") } // remove from cache @@ -280,7 +267,7 @@ func (r *DownloadClientRepo) Delete(ctx context.Context, clientID int) error { rows, _ := res.RowsAffected() if rows == 0 { - return err + return errors.New("no rows affected") } r.log.Info().Msgf("delete download client: %d", clientID) diff --git a/internal/database/feed.go b/internal/database/feed.go index dadd7fc..49fe0b7 100644 --- a/internal/database/feed.go +++ b/internal/database/feed.go @@ -6,19 +6,21 @@ import ( "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/logger" + "github.com/autobrr/autobrr/pkg/errors" sq "github.com/Masterminds/squirrel" + "github.com/rs/zerolog" ) func NewFeedRepo(log logger.Logger, db *DB) domain.FeedRepo { return &FeedRepo{ - log: log, + log: log.With().Str("repo", "feed").Logger(), db: db, } } type FeedRepo struct { - log logger.Logger + log zerolog.Logger db *DB } @@ -41,14 +43,12 @@ func (r *FeedRepo) FindByID(ctx context.Context, id int) (*domain.Feed, error) { query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("feed.FindById: error building query") - return nil, err + return nil, errors.Wrap(err, "error building query") } row := r.db.handler.QueryRowContext(ctx, query, args...) if err := row.Err(); err != nil { - r.log.Error().Stack().Err(err).Msg("feed.FindById: error executing query") - return nil, err + return nil, errors.Wrap(err, "error executing query") } var f domain.Feed @@ -56,8 +56,7 @@ func (r *FeedRepo) FindByID(ctx context.Context, id int) (*domain.Feed, error) { var apiKey sql.NullString if err := row.Scan(&f.ID, &f.Indexer, &f.Name, &f.Type, &f.Enabled, &f.URL, &f.Interval, &apiKey, &f.CreatedAt, &f.UpdatedAt); err != nil { - r.log.Error().Stack().Err(err).Msg("feed.FindById: error scanning row") - return nil, err + return nil, errors.Wrap(err, "error scanning row") } @@ -85,14 +84,12 @@ func (r *FeedRepo) FindByIndexerIdentifier(ctx context.Context, indexer string) query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("feed.FindByIndexerIdentifier: error building query") - return nil, err + return nil, errors.Wrap(err, "error building query") } row := r.db.handler.QueryRowContext(ctx, query, args...) if err := row.Err(); err != nil { - r.log.Error().Stack().Err(err).Msg("feed.FindByIndexerIdentifier: error executing query") - return nil, err + return nil, errors.Wrap(err, "error executing query") } var f domain.Feed @@ -100,8 +97,7 @@ func (r *FeedRepo) FindByIndexerIdentifier(ctx context.Context, indexer string) var apiKey sql.NullString if err := row.Scan(&f.ID, &f.Indexer, &f.Name, &f.Type, &f.Enabled, &f.URL, &f.Interval, &apiKey, &f.CreatedAt, &f.UpdatedAt); err != nil { - r.log.Error().Stack().Err(err).Msg("feed.FindByIndexerIdentifier: error scanning row") - return nil, err + return nil, errors.Wrap(err, "error scanning row") } @@ -129,14 +125,12 @@ func (r *FeedRepo) Find(ctx context.Context) ([]domain.Feed, error) { query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("feed.Find: error building query") - return nil, err + return nil, errors.Wrap(err, "error building query") } rows, err := r.db.handler.QueryContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("feed.Find: error executing query") - return nil, err + return nil, errors.Wrap(err, "error executing query") } defer rows.Close() @@ -148,8 +142,7 @@ func (r *FeedRepo) Find(ctx context.Context) ([]domain.Feed, error) { var apiKey sql.NullString if err := rows.Scan(&f.ID, &f.Indexer, &f.Name, &f.Type, &f.Enabled, &f.URL, &f.Interval, &apiKey, &f.CreatedAt, &f.UpdatedAt); err != nil { - r.log.Error().Stack().Err(err).Msg("feed.Find: error scanning row") - return nil, err + return nil, errors.Wrap(err, "error scanning row") } @@ -189,8 +182,7 @@ func (r *FeedRepo) Store(ctx context.Context, feed *domain.Feed) error { var retID int if err := queryBuilder.QueryRowContext(ctx).Scan(&retID); err != nil { - r.log.Error().Stack().Err(err).Msg("feed.Store: error executing query") - return err + return errors.Wrap(err, "error executing query") } feed.ID = retID @@ -212,14 +204,12 @@ func (r *FeedRepo) Update(ctx context.Context, feed *domain.Feed) error { query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("feed.Update: error building query") - return err + return errors.Wrap(err, "error building query") } _, err = r.db.handler.ExecContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("feed.Update: error executing query") - return err + return errors.Wrap(err, "error executing query") } return nil @@ -236,13 +226,11 @@ func (r *FeedRepo) ToggleEnabled(ctx context.Context, id int, enabled bool) erro query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("feed.ToggleEnabled: error building query") - return err + return errors.Wrap(err, "error building query") } _, err = r.db.handler.ExecContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("feed.ToggleEnabled: error executing query") - return err + return errors.Wrap(err, "error executing query") } return nil @@ -255,14 +243,12 @@ func (r *FeedRepo) Delete(ctx context.Context, id int) error { query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("feed.delete: error building query") - return err + return errors.Wrap(err, "error building query") } _, err = r.db.handler.ExecContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("feed.delete: error executing query") - return err + return errors.Wrap(err, "error executing query") } r.log.Info().Msgf("feed.delete: successfully deleted: %v", id) diff --git a/internal/database/feed_cache.go b/internal/database/feed_cache.go index ddca5ef..cd7aefa 100644 --- a/internal/database/feed_cache.go +++ b/internal/database/feed_cache.go @@ -6,16 +6,19 @@ import ( "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/logger" + "github.com/autobrr/autobrr/pkg/errors" + + "github.com/rs/zerolog" ) type FeedCacheRepo struct { - log logger.Logger + log zerolog.Logger db *DB } func NewFeedCacheRepo(log logger.Logger, db *DB) domain.FeedCacheRepo { return &FeedCacheRepo{ - log: log, + log: log.With().Str("repo", "feed_cache").Logger(), db: db, } } @@ -33,22 +36,19 @@ func (r *FeedCacheRepo) Get(bucket string, key string) ([]byte, error) { query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("feedCache.Get: error building query") - return nil, err + return nil, errors.Wrap(err, "error building query") } row := r.db.handler.QueryRow(query, args...) if err := row.Err(); err != nil { - r.log.Error().Stack().Err(err).Msg("feedCache.Get: query error") - return nil, err + return nil, errors.Wrap(err, "error executing query") } var value []byte var ttl time.Duration if err := row.Scan(&value, &ttl); err != nil && err != sql.ErrNoRows { - r.log.Error().Stack().Err(err).Msg("feedCache.Get: error scanning row") - return nil, err + return nil, errors.Wrap(err, "error scanning row") } return value, nil @@ -65,14 +65,13 @@ func (r *FeedCacheRepo) Exists(bucket string, key string) (bool, error) { query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("feedCache.Exists: error building query") - return false, err + return false, errors.Wrap(err, "error building query") } var exists bool err = r.db.handler.QueryRow(query, args...).Scan(&exists) if err != nil && err != sql.ErrNoRows { - r.log.Error().Stack().Err(err).Msg("feedCache.Exists: query error") + return false, errors.Wrap(err, "error query") } return exists, nil @@ -86,13 +85,11 @@ func (r *FeedCacheRepo) Put(bucket string, key string, val []byte, ttl time.Time query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("feedCache.Put: error building query") - return err + return errors.Wrap(err, "error building query") } if _, err = r.db.handler.Exec(query, args...); err != nil { - r.log.Error().Stack().Err(err).Msg("feedCache.Put: error executing query") - return err + return errors.Wrap(err, "error executing query") } return nil diff --git a/internal/database/filter.go b/internal/database/filter.go index 8757f07..d77b336 100644 --- a/internal/database/filter.go +++ b/internal/database/filter.go @@ -7,19 +7,21 @@ import ( "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/logger" + "github.com/autobrr/autobrr/pkg/errors" sq "github.com/Masterminds/squirrel" "github.com/lib/pq" + "github.com/rs/zerolog" ) type FilterRepo struct { - log logger.Logger + log zerolog.Logger db *DB } func NewFilterRepo(log logger.Logger, db *DB) domain.FilterRepo { return &FilterRepo{ - log: log, + log: log.With().Str("repo", "filter").Logger(), db: db, } } @@ -40,14 +42,12 @@ func (r *FilterRepo) ListFilters(ctx context.Context) ([]domain.Filter, error) { query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("filter.list: error building query") - return nil, err + return nil, errors.Wrap(err, "error building query") } rows, err := r.db.handler.QueryContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("filter.list: error executing query") - return nil, err + return nil, errors.Wrap(err, "error executing query") } defer rows.Close() @@ -59,8 +59,7 @@ func (r *FilterRepo) ListFilters(ctx context.Context) ([]domain.Filter, error) { var matchReleases, exceptReleases sql.NullString if err := rows.Scan(&f.ID, &f.Enabled, &f.Name, &matchReleases, &exceptReleases, &f.CreatedAt, &f.UpdatedAt); err != nil { - r.log.Error().Stack().Err(err).Msg("filter.list: error scanning row") - return nil, err + return nil, errors.Wrap(err, "error scanning row") } f.MatchReleases = matchReleases.String @@ -69,8 +68,7 @@ func (r *FilterRepo) ListFilters(ctx context.Context) ([]domain.Filter, error) { filters = append(filters, f) } if err := rows.Err(); err != nil { - r.log.Error().Stack().Err(err).Msg("filter.list: row error") - return nil, err + return nil, errors.Wrap(err, "row error") } return filters, nil @@ -133,14 +131,12 @@ func (r *FilterRepo) FindByID(ctx context.Context, filterID int) (*domain.Filter query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("filter.findByID: error building query") - return nil, err + return nil, errors.Wrap(err, "error building query") } row := r.db.handler.QueryRowContext(ctx, query, args...) if err := row.Err(); err != nil { - r.log.Error().Stack().Err(err).Msg("filter.findByID: error query row") - return nil, err + return nil, errors.Wrap(err, "error executing query") } var f domain.Filter @@ -149,8 +145,7 @@ func (r *FilterRepo) FindByID(ctx context.Context, filterID int) (*domain.Filter var delay, maxDownloads, logScore sql.NullInt32 if err := row.Scan(&f.ID, &f.Enabled, &f.Name, &minSize, &maxSize, &delay, &f.Priority, &maxDownloads, &maxDownloadsUnit, &matchReleases, &exceptReleases, &useRegex, &matchReleaseGroups, &exceptReleaseGroups, &scene, &freeleech, &freeleechPercent, &shows, &seasons, &episodes, pq.Array(&f.Resolutions), pq.Array(&f.Codecs), pq.Array(&f.Sources), pq.Array(&f.Containers), pq.Array(&f.MatchHDR), pq.Array(&f.ExceptHDR), pq.Array(&f.MatchOther), pq.Array(&f.ExceptOther), &years, &artists, &albums, pq.Array(&f.MatchReleaseTypes), pq.Array(&f.Formats), pq.Array(&f.Quality), pq.Array(&f.Media), &logScore, &hasLog, &hasCue, &perfectFlac, &matchCategories, &exceptCategories, &matchUploaders, &exceptUploaders, &tags, &exceptTags, pq.Array(&f.Origins), &f.CreatedAt, &f.UpdatedAt); err != nil { - r.log.Error().Stack().Err(err).Msgf("filter.findByID: %v : error scanning row", filterID) - return nil, err + return nil, errors.Wrap(err, "error scanning row") } f.MinSize = minSize.String @@ -191,7 +186,7 @@ func (r *FilterRepo) FindByIndexerIdentifier(indexer string) ([]domain.Filter, e ctx := context.TODO() tx, err := r.db.BeginTx(ctx, nil) if err != nil { - return nil, err + return nil, errors.Wrap(err, "error begin transaction") } defer tx.Rollback() @@ -273,14 +268,12 @@ func (r *FilterRepo) findByIndexerIdentifier(ctx context.Context, tx *Tx, indexe query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("filter.findByIndexerIdentifier: error building query") - return nil, err + return nil, errors.Wrap(err, "error building query") } rows, err := tx.QueryContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("filter.findByIndexerIdentifier: error executing query") - return nil, err + return nil, errors.Wrap(err, "error executing query") } defer rows.Close() @@ -294,8 +287,7 @@ func (r *FilterRepo) findByIndexerIdentifier(ctx context.Context, tx *Tx, indexe var delay, maxDownloads, logScore sql.NullInt32 if err := rows.Scan(&f.ID, &f.Enabled, &f.Name, &minSize, &maxSize, &delay, &f.Priority, &maxDownloads, &maxDownloadsUnit, &matchReleases, &exceptReleases, &useRegex, &matchReleaseGroups, &exceptReleaseGroups, &scene, &freeleech, &freeleechPercent, &shows, &seasons, &episodes, pq.Array(&f.Resolutions), pq.Array(&f.Codecs), pq.Array(&f.Sources), pq.Array(&f.Containers), pq.Array(&f.MatchHDR), pq.Array(&f.ExceptHDR), pq.Array(&f.MatchOther), pq.Array(&f.ExceptOther), &years, &artists, &albums, pq.Array(&f.MatchReleaseTypes), pq.Array(&f.Formats), pq.Array(&f.Quality), pq.Array(&f.Media), &logScore, &hasLog, &hasCue, &perfectFlac, &matchCategories, &exceptCategories, &matchUploaders, &exceptUploaders, &tags, &exceptTags, pq.Array(&f.Origins), &f.CreatedAt, &f.UpdatedAt); err != nil { - r.log.Error().Stack().Err(err).Msg("filter.findByIndexerIdentifier: error scanning row") - return nil, err + return nil, errors.Wrap(err, "error scanning row") } f.MinSize = minSize.String @@ -438,8 +430,7 @@ func (r *FilterRepo) Store(ctx context.Context, filter domain.Filter) (*domain.F err := queryBuilder.QueryRowContext(ctx).Scan(&retID) if err != nil { - r.log.Error().Stack().Err(err).Msg("filter.store: error executing query") - return nil, err + return nil, errors.Wrap(err, "error executing query") } filter.ID = retID @@ -502,14 +493,12 @@ func (r *FilterRepo) Update(ctx context.Context, filter domain.Filter) (*domain. query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("filter.update: error building query") - return nil, err + return nil, errors.Wrap(err, "error building query") } _, err = r.db.handler.ExecContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("filter.update: error executing query") - return nil, err + return nil, errors.Wrap(err, "error executing query") } return &filter, nil @@ -526,13 +515,11 @@ func (r *FilterRepo) ToggleEnabled(ctx context.Context, filterID int, enabled bo query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("filter.toggleEnabled: error building query") - return err + return errors.Wrap(err, "error building query") } _, err = r.db.handler.ExecContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("filter.toggleEnabled: error executing query") - return err + return errors.Wrap(err, "error executing query") } return nil @@ -552,13 +539,11 @@ func (r *FilterRepo) StoreIndexerConnections(ctx context.Context, filterID int, deleteQuery, deleteArgs, err := deleteQueryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("filter.StoreIndexerConnections: error building query") - return err + return errors.Wrap(err, "error building query") } _, err = tx.ExecContext(ctx, deleteQuery, deleteArgs...) if err != nil { - r.log.Error().Stack().Err(err).Msgf("filter.StoreIndexerConnections: error deleting indexers for filter: %v", filterID) - return err + return errors.Wrap(err, "error executing query") } for _, indexer := range indexers { @@ -568,13 +553,11 @@ func (r *FilterRepo) StoreIndexerConnections(ctx context.Context, filterID int, query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("filter.StoreIndexerConnections: error building query") - return err + return errors.Wrap(err, "error building query") } _, err = tx.ExecContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("filter.StoreIndexerConnections: error executing query") - return err + return errors.Wrap(err, "error executing query") } r.log.Debug().Msgf("filter.StoreIndexerConnections: store '%v' on filter: %v", indexer.Name, filterID) @@ -582,8 +565,7 @@ func (r *FilterRepo) StoreIndexerConnections(ctx context.Context, filterID int, err = tx.Commit() if err != nil { - r.log.Error().Stack().Err(err).Msgf("filter.StoreIndexerConnections: error storing indexers for filter: %v", filterID) - return err + return errors.Wrap(err, "error store indexers for filter: %v", filterID) } return nil @@ -596,14 +578,12 @@ func (r *FilterRepo) StoreIndexerConnection(ctx context.Context, filterID int, i query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("filter.storeIndexerConnection: error building query") - return err + return errors.Wrap(err, "error building query") } _, err = r.db.handler.ExecContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("filter.storeIndexerConnection: error executing query") - return err + return errors.Wrap(err, "error executing query") } return nil @@ -616,14 +596,12 @@ func (r *FilterRepo) DeleteIndexerConnections(ctx context.Context, filterID int) query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("filter.deleteIndexerConnections: error building query") - return err + return errors.Wrap(err, "error building query") } _, err = r.db.handler.ExecContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("filter.deleteIndexerConnections: error executing query") - return err + return errors.Wrap(err, "error executing query") } return nil @@ -636,14 +614,12 @@ func (r *FilterRepo) Delete(ctx context.Context, filterID int) error { query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("filter.delete: error building query") - return err + return errors.Wrap(err, "error building query") } _, err = r.db.handler.ExecContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("filter.delete: error executing query") - return err + return errors.Wrap(err, "error executing query") } r.log.Info().Msgf("filter.delete: successfully deleted: %v", filterID) @@ -671,15 +647,13 @@ WHERE "release".filter_id = ?;` row := tx.QueryRowContext(ctx, query, filterID) if err := row.Err(); err != nil { - r.log.Error().Stack().Err(err).Msg("filter.downloadsByFilterSqlite: error querying stats") - return nil, err + return nil, errors.Wrap(err, "error executing query") } var f domain.FilterDownloads if err := row.Scan(&f.HourCount, &f.DayCount, &f.WeekCount, &f.MonthCount, &f.TotalCount); err != nil { - r.log.Error().Stack().Err(err).Msg("filter.downloadsByFilterSqlite: error scanning stats data to struct") - return nil, err + return nil, errors.Wrap(err, "error scanning stats data sqlite") } return &f, nil @@ -697,15 +671,13 @@ WHERE "release".filter_id = $1;` row := tx.QueryRowContext(ctx, query, filterID) if err := row.Err(); err != nil { - r.log.Error().Stack().Err(err).Msg("filter.downloadsByFilterPostgres: error querying stats") - return nil, err + return nil, errors.Wrap(err, "error executing query") } var f domain.FilterDownloads if err := row.Scan(&f.HourCount, &f.DayCount, &f.WeekCount, &f.MonthCount, &f.TotalCount); err != nil { - r.log.Error().Stack().Err(err).Msg("filter.downloadsByFilterPostgres: error scanning stats data to struct") - return nil, err + return nil, errors.Wrap(err, "error scanning stats data postgres") } return &f, nil diff --git a/internal/database/indexer.go b/internal/database/indexer.go index d1dbc3e..00ac82d 100644 --- a/internal/database/indexer.go +++ b/internal/database/indexer.go @@ -8,16 +8,19 @@ import ( "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/logger" + "github.com/autobrr/autobrr/pkg/errors" + + "github.com/rs/zerolog" ) type IndexerRepo struct { - log logger.Logger + log zerolog.Logger db *DB } func NewIndexerRepo(log logger.Logger, db *DB) domain.IndexerRepo { return &IndexerRepo{ - log: log, + log: log.With().Str("repo", "indexer").Logger(), db: db, } } @@ -25,8 +28,7 @@ func NewIndexerRepo(log logger.Logger, db *DB) domain.IndexerRepo { func (r *IndexerRepo) Store(ctx context.Context, indexer domain.Indexer) (*domain.Indexer, error) { settings, err := json.Marshal(indexer.Settings) if err != nil { - r.log.Error().Stack().Err(err).Msg("error marshaling json data") - return nil, err + return nil, errors.Wrap(err, "error marshaling json data") } queryBuilder := r.db.squirrel. @@ -39,8 +41,7 @@ func (r *IndexerRepo) Store(ctx context.Context, indexer domain.Indexer) (*domai err = queryBuilder.QueryRowContext(ctx).Scan(&retID) if err != nil { - r.log.Error().Stack().Err(err).Msg("indexer.store: error executing query") - return nil, err + return nil, errors.Wrap(err, "error executing query") } indexer.ID = retID @@ -51,8 +52,7 @@ func (r *IndexerRepo) Store(ctx context.Context, indexer domain.Indexer) (*domai func (r *IndexerRepo) Update(ctx context.Context, indexer domain.Indexer) (*domain.Indexer, error) { settings, err := json.Marshal(indexer.Settings) if err != nil { - r.log.Error().Stack().Err(err).Msg("error marshaling json data") - return nil, err + return nil, errors.Wrap(err, "error marshaling json data") } queryBuilder := r.db.squirrel. @@ -65,14 +65,12 @@ func (r *IndexerRepo) Update(ctx context.Context, indexer domain.Indexer) (*doma query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("indexer.update: error building query") - return nil, err + return nil, errors.Wrap(err, "error building query") } _, err = r.db.handler.ExecContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("indexer.update: error executing query") - return nil, err + return nil, errors.Wrap(err, "error executing query") } return &indexer, nil @@ -81,8 +79,7 @@ func (r *IndexerRepo) Update(ctx context.Context, indexer domain.Indexer) (*doma func (r *IndexerRepo) List(ctx context.Context) ([]domain.Indexer, error) { rows, err := r.db.handler.QueryContext(ctx, "SELECT id, enabled, name, identifier, implementation, settings FROM indexer ORDER BY name ASC") if err != nil { - r.log.Error().Stack().Err(err).Msg("indexer.list: error query indexer") - return nil, err + return nil, errors.Wrap(err, "error executing query") } defer rows.Close() @@ -96,16 +93,14 @@ func (r *IndexerRepo) List(ctx context.Context) ([]domain.Indexer, error) { var settingsMap map[string]string if err := rows.Scan(&f.ID, &f.Enabled, &f.Name, &f.Identifier, &implementation, &settings); err != nil { - r.log.Error().Stack().Err(err).Msg("indexer.list: error scanning data to struct") - return nil, err + return nil, errors.Wrap(err, "error scanning row") } f.Implementation = implementation.String err = json.Unmarshal([]byte(settings), &settingsMap) if err != nil { - r.log.Error().Stack().Err(err).Msg("indexer.list: error unmarshal settings") - return nil, err + return nil, errors.Wrap(err, "error unmarshal settings") } f.Settings = settingsMap @@ -113,7 +108,7 @@ func (r *IndexerRepo) List(ctx context.Context) ([]domain.Indexer, error) { indexers = append(indexers, f) } if err := rows.Err(); err != nil { - return nil, err + return nil, errors.Wrap(err, "error rows") } return indexers, nil @@ -128,14 +123,12 @@ func (r *IndexerRepo) FindByFilterID(ctx context.Context, id int) ([]domain.Inde query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.check_existing_network: error fetching data") - return nil, err + return nil, errors.Wrap(err, "error building query") } rows, err := r.db.handler.QueryContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("indexer.find_by_filter_id: error query indexer") - return nil, err + return nil, errors.Wrap(err, "error executing query") } defer rows.Close() @@ -148,14 +141,12 @@ func (r *IndexerRepo) FindByFilterID(ctx context.Context, id int) ([]domain.Inde var settingsMap map[string]string if err := rows.Scan(&f.ID, &f.Enabled, &f.Name, &f.Identifier, &settings); err != nil { - r.log.Error().Stack().Err(err).Msg("indexer.find_by_filter_id: error scanning data to struct") - return nil, err + return nil, errors.Wrap(err, "error scanning row") } err = json.Unmarshal([]byte(settings), &settingsMap) if err != nil { - r.log.Error().Stack().Err(err).Msg("indexer.find_by_filter_id: error unmarshal settings") - return nil, err + return nil, errors.Wrap(err, "error unmarshal settings") } f.Settings = settingsMap @@ -163,7 +154,7 @@ func (r *IndexerRepo) FindByFilterID(ctx context.Context, id int) ([]domain.Inde indexers = append(indexers, f) } if err := rows.Err(); err != nil { - return nil, err + return nil, errors.Wrap(err, "error rows") } return indexers, nil @@ -177,14 +168,12 @@ func (r *IndexerRepo) Delete(ctx context.Context, id int) error { query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("indexer.delete: error building query") - return err + return errors.Wrap(err, "error building query") } _, err = r.db.handler.ExecContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msgf("indexer.delete: error executing query: '%v'", query) - return err + return errors.Wrap(err, "error executing query") } r.log.Debug().Msgf("indexer.delete: id %v", id) diff --git a/internal/database/irc.go b/internal/database/irc.go index 49774bc..1c87075 100644 --- a/internal/database/irc.go +++ b/internal/database/irc.go @@ -7,18 +7,19 @@ import ( "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/logger" + "github.com/autobrr/autobrr/pkg/errors" - "github.com/pkg/errors" + "github.com/rs/zerolog" ) type IrcRepo struct { - log logger.Logger + log zerolog.Logger db *DB } func NewIrcRepo(log logger.Logger, db *DB) domain.IrcRepo { return &IrcRepo{ - log: log, + log: log.With().Str("repo", "irc").Logger(), db: db, } } @@ -31,8 +32,7 @@ func (r *IrcRepo) GetNetworkByID(ctx context.Context, id int64) (*domain.IrcNetw query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.getNetworkByID: error building query") - return nil, err + return nil, errors.Wrap(err, "error building query") } r.log.Trace().Str("database", "irc.check_existing_network").Msgf("query: '%v', args: '%v'", query, args) @@ -44,8 +44,7 @@ func (r *IrcRepo) GetNetworkByID(ctx context.Context, id int64) (*domain.IrcNetw row := r.db.handler.QueryRowContext(ctx, query, args...) if err := row.Scan(&n.ID, &n.Enabled, &n.Name, &n.Server, &n.Port, &tls, &pass, &inviteCmd, &nsAccount, &nsPassword); err != nil { - r.log.Error().Stack().Err(err).Msg("irc.getNetworkByID: error executing query") - return nil, err + return nil, errors.Wrap(err, "error scanning row") } n.TLS = tls.Bool @@ -60,7 +59,7 @@ func (r *IrcRepo) GetNetworkByID(ctx context.Context, id int64) (*domain.IrcNetw func (r *IrcRepo) DeleteNetwork(ctx context.Context, id int64) error { tx, err := r.db.BeginTx(ctx, nil) if err != nil { - return err + return errors.Wrap(err, "error begin transaction") } defer tx.Rollback() @@ -71,14 +70,12 @@ func (r *IrcRepo) DeleteNetwork(ctx context.Context, id int64) error { query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.deleteNetwork: error building query") - return err + return errors.Wrap(err, "error building query") } _, err = tx.ExecContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.deleteNetwork: error executing query") - return err + return errors.Wrap(err, "error executing query") } netQueryBuilder := r.db.squirrel. @@ -87,20 +84,17 @@ func (r *IrcRepo) DeleteNetwork(ctx context.Context, id int64) error { netQuery, netArgs, err := netQueryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.deleteNetwork: error building query") - return err + return errors.Wrap(err, "error building query") } _, err = tx.ExecContext(ctx, netQuery, netArgs...) if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.deleteNetwork: error executing query") - return err + return errors.Wrap(err, "error executing query") } err = tx.Commit() if err != nil { - r.log.Error().Stack().Err(err).Msgf("irc.deleteNetwork: error deleting network %v", id) - return err + return errors.Wrap(err, "error commit deleting network") } @@ -115,14 +109,12 @@ func (r *IrcRepo) FindActiveNetworks(ctx context.Context) ([]domain.IrcNetwork, query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.findActiveNetworks: error building query") - return nil, err + return nil, errors.Wrap(err, "error building query") } rows, err := r.db.handler.QueryContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.findActiveNetworks: error executing query") - return nil, err + return nil, errors.Wrap(err, "error executing query") } defer rows.Close() @@ -136,8 +128,7 @@ func (r *IrcRepo) FindActiveNetworks(ctx context.Context) ([]domain.IrcNetwork, var tls sql.NullBool if err := rows.Scan(&net.ID, &net.Enabled, &net.Name, &net.Server, &net.Port, &tls, &pass, &inviteCmd, &nsAccount, &nsPassword); err != nil { - r.log.Error().Stack().Err(err).Msg("irc.findActiveNetworks: error scanning row") - return nil, err + return nil, errors.Wrap(err, "error scanning row") } net.TLS = tls.Bool @@ -150,8 +141,7 @@ func (r *IrcRepo) FindActiveNetworks(ctx context.Context) ([]domain.IrcNetwork, networks = append(networks, net) } if err := rows.Err(); err != nil { - r.log.Error().Stack().Err(err).Msg("irc.findActiveNetworks: row error") - return nil, err + return nil, errors.Wrap(err, "error row") } return networks, nil @@ -165,14 +155,12 @@ func (r *IrcRepo) ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error) query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.listNetworks: error building query") - return nil, err + return nil, errors.Wrap(err, "error building query") } rows, err := r.db.handler.QueryContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.listNetworks: error executing query") - return nil, err + return nil, errors.Wrap(err, "error executing query") } defer rows.Close() @@ -186,8 +174,7 @@ func (r *IrcRepo) ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error) var tls sql.NullBool if err := rows.Scan(&net.ID, &net.Enabled, &net.Name, &net.Server, &net.Port, &tls, &pass, &inviteCmd, &nsAccount, &nsPassword); err != nil { - r.log.Error().Stack().Err(err).Msg("irc.listNetworks: error scanning row") - return nil, err + return nil, errors.Wrap(err, "error scanning row") } net.TLS = tls.Bool @@ -200,8 +187,7 @@ func (r *IrcRepo) ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error) networks = append(networks, net) } if err := rows.Err(); err != nil { - r.log.Error().Stack().Err(err).Msg("irc.listNetworks: row error") - return nil, err + return nil, errors.Wrap(err, "error row") } return networks, nil @@ -215,14 +201,12 @@ func (r *IrcRepo) ListChannels(networkID int64) ([]domain.IrcChannel, error) { query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.listChannels: error building query") - return nil, err + return nil, errors.Wrap(err, "error building query") } rows, err := r.db.handler.Query(query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.listChannels: error executing query") - return nil, err + return nil, errors.Wrap(err, "error executing query") } defer rows.Close() @@ -232,8 +216,7 @@ func (r *IrcRepo) ListChannels(networkID int64) ([]domain.IrcChannel, error) { var pass sql.NullString if err := rows.Scan(&ch.ID, &ch.Name, &ch.Enabled, &pass); err != nil { - r.log.Error().Stack().Err(err).Msg("irc.listChannels: error scanning row") - return nil, err + return nil, errors.Wrap(err, "error scanning row") } ch.Password = pass.String @@ -241,8 +224,7 @@ func (r *IrcRepo) ListChannels(networkID int64) ([]domain.IrcChannel, error) { channels = append(channels, ch) } if err := rows.Err(); err != nil { - r.log.Error().Stack().Err(err).Msg("irc.listChannels: error row") - return nil, err + return nil, errors.Wrap(err, "error row") } return channels, nil @@ -257,8 +239,7 @@ func (r *IrcRepo) CheckExistingNetwork(ctx context.Context, network *domain.IrcN query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.checkExistingNetwork: error building query") - return nil, err + return nil, errors.Wrap(err, "error building query") } r.log.Trace().Str("database", "irc.checkExistingNetwork").Msgf("query: '%v', args: '%v'", query, args) @@ -274,8 +255,7 @@ func (r *IrcRepo) CheckExistingNetwork(ctx context.Context, network *domain.IrcN // no result is not an error in our case return nil, nil } else if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.checkExistingNetwork: error scanning data to struct") - return nil, err + return nil, errors.Wrap(err, "error scanning row") } net.TLS = tls.Bool @@ -326,7 +306,6 @@ func (r *IrcRepo) StoreNetwork(network *domain.IrcNetwork) error { err = queryBuilder.QueryRow().Scan(&retID) if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.storeNetwork: error executing query") return errors.Wrap(err, "error executing query") } @@ -361,15 +340,13 @@ func (r *IrcRepo) UpdateNetwork(ctx context.Context, network *domain.IrcNetwork) query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.updateNetwork: error building query") - return err + return errors.Wrap(err, "error building query") } // update record _, err = r.db.handler.ExecContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.updateNetwork: error executing query") - return err + return errors.Wrap(err, "error executing query") } return err @@ -391,14 +368,12 @@ func (r *IrcRepo) StoreNetworkChannels(ctx context.Context, networkID int64, cha query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.storeNetworkChannels: error building query") - return err + return errors.Wrap(err, "error building query") } _, err = tx.ExecContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.storeNetworkChannels: error executing query") - return err + return errors.Wrap(err, "error executing query") } for _, channel := range channels { @@ -429,8 +404,7 @@ func (r *IrcRepo) StoreNetworkChannels(ctx context.Context, networkID int64, cha err = channelQueryBuilder.QueryRowContext(ctx).Scan(&retID) if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.storeNetworkChannels: error executing query") - return errors.Wrap(err, "error executing query") + return errors.Wrap(err, "error executing query storeNetworkChannels") } channel.ID = retID @@ -452,8 +426,7 @@ func (r *IrcRepo) StoreNetworkChannels(ctx context.Context, networkID int64, cha err = tx.Commit() if err != nil { - r.log.Error().Stack().Err(err).Msgf("irc.storeNetworkChannels: error deleting network: %v", networkID) - return err + return errors.Wrap(err, "error commit transaction store network") } return nil @@ -475,14 +448,12 @@ func (r *IrcRepo) StoreChannel(networkID int64, channel *domain.IrcChannel) erro query, args, err := channelQueryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.storeChannel: error building query") - return err + return errors.Wrap(err, "error building query") } _, err = r.db.handler.Exec(query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.storeChannel: error executing query") - return err + return errors.Wrap(err, "error executing query") } } else { queryBuilder := r.db.squirrel. @@ -509,7 +480,6 @@ func (r *IrcRepo) StoreChannel(networkID int64, channel *domain.IrcChannel) erro err = queryBuilder.QueryRow().Scan(&retID) if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.storeChannels: error executing query") return errors.Wrap(err, "error executing query") } @@ -548,14 +518,12 @@ func (r *IrcRepo) UpdateChannel(channel *domain.IrcChannel) error { query, args, err := channelQueryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.updateChannel: error building query") - return err + return errors.Wrap(err, "error building query") } _, err = r.db.handler.Exec(query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.updateChannel: error executing query") - return err + return errors.Wrap(err, "error executing query") } return err @@ -571,14 +539,12 @@ func (r *IrcRepo) UpdateInviteCommand(networkID int64, invite string) error { query, args, err := channelQueryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.UpdateInviteCommand: error building query") - return err + return errors.Wrap(err, "error building query") } _, err = r.db.handler.Exec(query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("irc.UpdateInviteCommand: error executing query") - return err + return errors.Wrap(err, "error executing query") } return err diff --git a/internal/database/notification.go b/internal/database/notification.go index 801458f..33240f1 100644 --- a/internal/database/notification.go +++ b/internal/database/notification.go @@ -6,19 +6,21 @@ import ( "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/logger" + "github.com/autobrr/autobrr/pkg/errors" sq "github.com/Masterminds/squirrel" "github.com/lib/pq" + "github.com/rs/zerolog" ) type NotificationRepo struct { - log logger.Logger + log zerolog.Logger db *DB } func NewNotificationRepo(log logger.Logger, db *DB) domain.NotificationRepo { return &NotificationRepo{ - log: log, + log: log.With().Str("repo", "notification").Logger(), db: db, } } @@ -32,14 +34,12 @@ func (r *NotificationRepo) Find(ctx context.Context, params domain.NotificationQ query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("notification.find: error building query") - return nil, 0, err + return nil, 0, errors.Wrap(err, "error building query") } rows, err := r.db.handler.QueryContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("notification.find: error executing query") - return nil, 0, err + return nil, 0, errors.Wrap(err, "error executing query") } defer rows.Close() @@ -54,8 +54,7 @@ func (r *NotificationRepo) Find(ctx context.Context, params domain.NotificationQ //if err := rows.Scan(&n.ID, &n.Name, &n.Type, &n.Enabled, pq.Array(&n.Events), &token, &apiKey, &webhook, &title, &icon, &host, &username, &password, &channel, &targets, &devices, &n.CreatedAt, &n.UpdatedAt); err != nil { //var token, apiKey, webhook, title, icon, host, username, password, channel, targets, devices sql.NullString if err := rows.Scan(&n.ID, &n.Name, &n.Type, &n.Enabled, pq.Array(&n.Events), &webhook, &token, &channel, &n.CreatedAt, &n.UpdatedAt, &totalCount); err != nil { - r.log.Error().Stack().Err(err).Msg("notification.find: error scanning row") - return nil, 0, err + return nil, 0, errors.Wrap(err, "error scanning row") } //n.APIKey = apiKey.String @@ -74,7 +73,7 @@ func (r *NotificationRepo) Find(ctx context.Context, params domain.NotificationQ notifications = append(notifications, n) } if err := rows.Err(); err != nil { - return nil, 0, err + return nil, 0, errors.Wrap(err, "error rows find") } return notifications, totalCount, nil @@ -82,17 +81,9 @@ func (r *NotificationRepo) Find(ctx context.Context, params domain.NotificationQ func (r *NotificationRepo) List(ctx context.Context) ([]domain.Notification, error) { - //queryBuilder := r.db.squirrel. - // Select("r.id", "r.filter_status", "r.rejections", "r.indexer", "r.filter", "r.protocol", "r.title", "r.torrent_name", "r.size", "r.timestamp", "COUNT(*) OVER() AS total_count"). - // From("release r"). - // OrderBy("r.timestamp DESC") - // - //query, args, err := queryBuilder.ToSql() - rows, err := r.db.handler.QueryContext(ctx, "SELECT id, name, type, enabled, events, token, api_key, webhook, title, icon, host, username, password, channel, targets, devices, created_at, updated_at FROM notification ORDER BY name ASC") if err != nil { - r.log.Error().Stack().Err(err).Msg("filters_list: error query data") - return nil, err + return nil, errors.Wrap(err, "error executing query") } defer rows.Close() @@ -104,8 +95,7 @@ func (r *NotificationRepo) List(ctx context.Context) ([]domain.Notification, err var token, apiKey, webhook, title, icon, host, username, password, channel, targets, devices sql.NullString if err := rows.Scan(&n.ID, &n.Name, &n.Type, &n.Enabled, pq.Array(&n.Events), &token, &apiKey, &webhook, &title, &icon, &host, &username, &password, &channel, &targets, &devices, &n.CreatedAt, &n.UpdatedAt); err != nil { - r.log.Error().Stack().Err(err).Msg("notification_list: error scanning data to struct") - return nil, err + return nil, errors.Wrap(err, "error scanning row") } //n.Events = ([]domain.NotificationEvent)(eventsSlice) @@ -124,7 +114,7 @@ func (r *NotificationRepo) List(ctx context.Context) ([]domain.Notification, err notifications = append(notifications, n) } if err := rows.Err(); err != nil { - return nil, err + return nil, errors.Wrap(err, "error rows list") } return notifications, nil @@ -148,22 +138,20 @@ func (r *NotificationRepo) FindByID(ctx context.Context, id int) (*domain.Notifi query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("notification.findByID: error building query") - return nil, err + return nil, errors.Wrap(err, "error building query") } //row := r.db.handler.QueryRowContext(ctx, "SELECT id, name, type, enabled, events, token, api_key, webhook, title, icon, host, username, password, channel, targets, devices, created_at, updated_at FROM notification WHERE id = ?", id) row := r.db.handler.QueryRowContext(ctx, query, args...) if err := row.Err(); err != nil { - return nil, err + return nil, errors.Wrap(err, "error executing query") } var n domain.Notification var token, apiKey, webhook, title, icon, host, username, password, channel, targets, devices sql.NullString if err := row.Scan(&n.ID, &n.Name, &n.Type, &n.Enabled, pq.Array(&n.Events), &token, &apiKey, &webhook, &title, &icon, &host, &username, &password, &channel, &targets, &devices, &n.CreatedAt, &n.UpdatedAt); err != nil { - r.log.Error().Stack().Err(err).Msg("notification.findByID: error scanning row") - return nil, err + return nil, errors.Wrap(err, "error scanning row") } n.Token = token.String @@ -213,8 +201,7 @@ func (r *NotificationRepo) Store(ctx context.Context, notification domain.Notifi err := queryBuilder.QueryRowContext(ctx).Scan(&retID) if err != nil { - r.log.Error().Stack().Err(err).Msg("notification.store: error executing query") - return nil, err + return nil, errors.Wrap(err, "error executing query") } r.log.Debug().Msgf("notification.store: added new %v", retID) @@ -242,14 +229,12 @@ func (r *NotificationRepo) Update(ctx context.Context, notification domain.Notif query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("action.update: error building query") - return nil, err + return nil, errors.Wrap(err, "error building query") } _, err = r.db.handler.ExecContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("notification.update: error executing query") - return nil, err + return nil, errors.Wrap(err, "error executing query") } r.log.Debug().Msgf("notification.update: %v", notification.Name) @@ -264,14 +249,12 @@ func (r *NotificationRepo) Delete(ctx context.Context, notificationID int) error query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("notification.delete: error building query") - return err + return errors.Wrap(err, "error building query") } _, err = r.db.handler.ExecContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("notification.delete: error executing query") - return err + return errors.Wrap(err, "error executing query") } r.log.Info().Msgf("notification.delete: successfully deleted: %v", notificationID) diff --git a/internal/database/postgres.go b/internal/database/postgres.go index 22de5dc..442aa58 100644 --- a/internal/database/postgres.go +++ b/internal/database/postgres.go @@ -2,8 +2,8 @@ package database import ( "database/sql" - "errors" - "fmt" + + "github.com/autobrr/autobrr/pkg/errors" _ "github.com/lib/pq" ) @@ -14,19 +14,19 @@ func (db *DB) openPostgres() error { // open database connection if db.handler, err = sql.Open("postgres", db.DSN); err != nil { db.log.Fatal().Err(err).Msg("could not open postgres connection") - return err + return errors.Wrap(err, "could not open postgres connection") } err = db.handler.Ping() if err != nil { db.log.Fatal().Err(err).Msg("could not ping postgres database") - return err + return errors.Wrap(err, "could not ping postgres database") } // migrate db if err = db.migratePostgres(); err != nil { db.log.Fatal().Err(err).Msg("could not migrate postgres database") - return err + return errors.Wrap(err, "could not migrate postgres database") } return nil @@ -35,7 +35,7 @@ func (db *DB) openPostgres() error { func (db *DB) migratePostgres() error { tx, err := db.handler.Begin() if err != nil { - return err + return errors.Wrap(err, "error starting transaction") } defer tx.Rollback() @@ -45,37 +45,37 @@ func (db *DB) migratePostgres() error { );` if _, err := tx.Exec(initialSchema); err != nil { - return fmt.Errorf("failed to create schema_migrations table: %s", err) + return errors.New("failed to create schema_migrations table") } var version int err = tx.QueryRow(`SELECT version FROM schema_migrations`).Scan(&version) if err != nil && !errors.Is(err, sql.ErrNoRows) { - return err + return errors.Wrap(err, "no rows") } if version == len(postgresMigrations) { return nil } if version > len(postgresMigrations) { - return fmt.Errorf("old") + return errors.New("old") } if version == 0 { if _, err := tx.Exec(postgresSchema); err != nil { - return fmt.Errorf("failed to initialize schema: %v", err) + return errors.Wrap(err, "failed to initialize schema") } } else { for i := version; i < len(postgresMigrations); i++ { if _, err := tx.Exec(postgresMigrations[i]); err != nil { - return fmt.Errorf("failed to execute migration #%v: %v", i, err) + return errors.Wrap(err, "failed to execute migration #%v", i) } } } _, err = tx.Exec(`INSERT INTO schema_migrations (id, version) VALUES (1, $1) ON CONFLICT (id) DO UPDATE SET version = $1`, len(postgresMigrations)) if err != nil { - return fmt.Errorf("failed to bump schema version: %v", err) + return errors.Wrap(err, "failed to bump schema version") } return tx.Commit() diff --git a/internal/database/release.go b/internal/database/release.go index 3eca4f7..a8d543a 100644 --- a/internal/database/release.go +++ b/internal/database/release.go @@ -8,19 +8,21 @@ import ( "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/logger" + "github.com/autobrr/autobrr/pkg/errors" sq "github.com/Masterminds/squirrel" "github.com/lib/pq" + "github.com/rs/zerolog" ) type ReleaseRepo struct { - log logger.Logger + log zerolog.Logger db *DB } func NewReleaseRepo(log logger.Logger, db *DB) domain.ReleaseRepo { return &ReleaseRepo{ - log: log, + log: log.With().Str("repo", "release").Logger(), db: db, } } @@ -40,8 +42,7 @@ func (repo *ReleaseRepo) Store(ctx context.Context, r *domain.Release) (*domain. err := queryBuilder.QueryRowContext(ctx).Scan(&retID) if err != nil { - repo.log.Error().Stack().Err(err).Msg("release.store: error executing query") - return nil, err + return nil, errors.Wrap(err, "error executing query") } r.ID = retID @@ -63,14 +64,12 @@ func (repo *ReleaseRepo) StoreReleaseActionStatus(ctx context.Context, a *domain query, args, err := queryBuilder.ToSql() if err != nil { - repo.log.Error().Stack().Err(err).Msg("release.store: error building query") - return err + return errors.Wrap(err, "error building query") } _, err = repo.db.handler.ExecContext(ctx, query, args...) if err != nil { - repo.log.Error().Stack().Err(err).Msg("error updating status of release") - return err + return errors.Wrap(err, "error executing query") } } else { @@ -85,8 +84,7 @@ func (repo *ReleaseRepo) StoreReleaseActionStatus(ctx context.Context, a *domain err := queryBuilder.QueryRowContext(ctx).Scan(&retID) if err != nil { - repo.log.Error().Stack().Err(err).Msg("release.storeReleaseActionStatus: error executing query") - return err + return errors.Wrap(err, "error executing query") } a.ID = retID @@ -100,7 +98,7 @@ func (repo *ReleaseRepo) StoreReleaseActionStatus(ctx context.Context, a *domain func (repo *ReleaseRepo) Find(ctx context.Context, params domain.ReleaseQueryParams) ([]*domain.Release, int64, int64, error) { tx, err := repo.db.BeginTx(ctx, &sql.TxOptions{}) if err != nil { - return nil, 0, 0, err + return nil, 0, 0, errors.Wrap(err, "error begin transaction") } defer tx.Rollback() @@ -118,8 +116,7 @@ func (repo *ReleaseRepo) Find(ctx context.Context, params domain.ReleaseQueryPar } if err = tx.Commit(); err != nil { - repo.log.Error().Stack().Err(err).Msg("error finding releases") - return nil, 0, 0, err + return nil, 0, 0, errors.Wrap(err, "error commit transaction find releases") } return releases, nextCursor, total, nil @@ -165,23 +162,20 @@ func (repo *ReleaseRepo) findReleases(ctx context.Context, tx *Tx, params domain query, args, err := queryBuilder.ToSql() repo.log.Trace().Str("database", "release.find").Msgf("query: '%v', args: '%v'", query, args) if err != nil { - repo.log.Error().Stack().Err(err).Msg("error building query") - return nil, 0, 0, err + return nil, 0, 0, errors.Wrap(err, "error building query") } res := make([]*domain.Release, 0) rows, err := tx.QueryContext(ctx, query, args...) if err != nil { - repo.log.Error().Stack().Err(err).Msg("error fetching releases") - return res, 0, 0, nil + return nil, 0, 0, errors.Wrap(err, "error executing query") } defer rows.Close() if err := rows.Err(); err != nil { - repo.log.Error().Stack().Err(err) - return res, 0, 0, err + return res, 0, 0, errors.Wrap(err, "error rows findreleases") } var countItems int64 = 0 @@ -192,8 +186,7 @@ func (repo *ReleaseRepo) findReleases(ctx context.Context, tx *Tx, params domain var indexer, filter sql.NullString if err := rows.Scan(&rls.ID, &rls.FilterStatus, pq.Array(&rls.Rejections), &indexer, &filter, &rls.Protocol, &rls.Title, &rls.TorrentName, &rls.Size, &rls.Timestamp, &countItems); err != nil { - repo.log.Error().Stack().Err(err).Msg("release.find: error scanning data to struct") - return res, 0, 0, err + return res, 0, 0, errors.Wrap(err, "error scanning row") } rls.Indexer = indexer.String @@ -373,23 +366,20 @@ func (repo *ReleaseRepo) attachActionStatus(ctx context.Context, tx *Tx, release rows, err := tx.QueryContext(ctx, query, args...) if err != nil { - repo.log.Error().Stack().Err(err).Msg("error fetching releases") - return res, nil + return res, errors.Wrap(err, "error executing query") } defer rows.Close() if err := rows.Err(); err != nil { - repo.log.Error().Stack().Err(err) - return res, err + return res, errors.Wrap(err, "error rows") } for rows.Next() { var rls domain.ReleaseActionStatus if err := rows.Scan(&rls.ID, &rls.Status, &rls.Action, &rls.Type, pq.Array(&rls.Rejections), &rls.Timestamp); err != nil { - repo.log.Error().Stack().Err(err).Msg("release.find: error scanning data to struct") - return res, err + return res, errors.Wrap(err, "error scanning row") } res = append(res, rls) @@ -411,15 +401,13 @@ FROM "release";` row := repo.db.handler.QueryRowContext(ctx, query) if err := row.Err(); err != nil { - repo.log.Error().Stack().Err(err).Msg("release.stats: error querying stats") - return nil, err + return nil, errors.Wrap(err, "error executing query") } var rls domain.ReleaseStats if err := row.Scan(&rls.TotalCount, &rls.FilteredCount, &rls.FilterRejectedCount, &rls.PushApprovedCount, &rls.PushRejectedCount); err != nil { - repo.log.Error().Stack().Err(err).Msg("release.stats: error scanning stats data to struct") - return nil, err + return nil, errors.Wrap(err, "error scanning row") } return &rls, nil @@ -435,20 +423,17 @@ func (repo *ReleaseRepo) Delete(ctx context.Context) error { _, err = tx.ExecContext(ctx, `DELETE FROM "release"`) if err != nil { - repo.log.Error().Stack().Err(err).Msg("error deleting all releases") - return err + return errors.Wrap(err, "error executing query") } _, err = tx.ExecContext(ctx, `DELETE FROM release_action_status`) if err != nil { - repo.log.Error().Stack().Err(err).Msg("error deleting all release_action_status") - return err + return errors.Wrap(err, "error executing query") } err = tx.Commit() if err != nil { - repo.log.Error().Stack().Err(err).Msg("error deleting all releases") - return err + return errors.Wrap(err, "error commit transaction delete") } return nil diff --git a/internal/database/sqlite.go b/internal/database/sqlite.go index 15bcd3c..d8dc72b 100644 --- a/internal/database/sqlite.go +++ b/internal/database/sqlite.go @@ -4,13 +4,15 @@ import ( "database/sql" "fmt" + "github.com/autobrr/autobrr/pkg/errors" + "github.com/lib/pq" _ "modernc.org/sqlite" ) func (db *DB) openSQLite() error { if db.DSN == "" { - return fmt.Errorf("DSN required") + return errors.New("DSN required") } var err error @@ -23,20 +25,20 @@ func (db *DB) openSQLite() error { // Set busy timeout //if _, err = db.handler.Exec(`PRAGMA busy_timeout = 5000;`); err != nil { - // return fmt.Errorf("busy timeout pragma: %w", err) + // return errors.New("busy timeout pragma: %w", err) //} // Enable WAL. SQLite performs better with the WAL because it allows // multiple readers to operate while data is being written. if _, err = db.handler.Exec(`PRAGMA journal_mode = wal;`); err != nil { - return fmt.Errorf("enable wal: %w", err) + return errors.Wrap(err, "enable wal") } // Enable foreign key checks. For historical reasons, SQLite does not check // foreign key constraints by default. There's some overhead on inserts to // verify foreign key integrity, but it's definitely worth it. //if _, err = db.handler.Exec(`PRAGMA foreign_keys = ON;`); err != nil { - // return fmt.Errorf("foreign keys pragma: %w", err) + // return errors.New("foreign keys pragma: %w", err) //} // migrate db @@ -54,13 +56,13 @@ func (db *DB) migrateSQLite() error { var version int if err := db.handler.QueryRow("PRAGMA user_version").Scan(&version); err != nil { - return fmt.Errorf("failed to query schema version: %v", err) + return errors.Wrap(err, "failed to query schema version") } if version == len(sqliteMigrations) { return nil } else if version > len(sqliteMigrations) { - return fmt.Errorf("autobrr (version %d) older than schema (version: %d)", len(sqliteMigrations), version) + return errors.New("autobrr (version %d) older than schema (version: %d)", len(sqliteMigrations), version) } tx, err := db.handler.Begin() @@ -71,12 +73,12 @@ func (db *DB) migrateSQLite() error { if version == 0 { if _, err := tx.Exec(sqliteSchema); err != nil { - return fmt.Errorf("failed to initialize schema: %v", err) + return errors.Wrap(err, "failed to initialize schema") } } else { for i := version; i < len(sqliteMigrations); i++ { if _, err := tx.Exec(sqliteMigrations[i]); err != nil { - return fmt.Errorf("failed to execute migration #%v: %v", i, err) + return errors.Wrap(err, "failed to execute migration #%v", i) } } } @@ -87,13 +89,13 @@ func (db *DB) migrateSQLite() error { // TODO 2022-01-30 remove this in future version if version == 5 && len(sqliteMigrations) == 6 { if err := customMigrateCopySourcesToMedia(tx); err != nil { - return fmt.Errorf("could not run custom data migration: %v", err) + return errors.Wrap(err, "could not run custom data migration") } } _, err = tx.Exec(fmt.Sprintf("PRAGMA user_version = %d", len(sqliteMigrations))) if err != nil { - return fmt.Errorf("failed to bump schema version: %v", err) + return errors.Wrap(err, "failed to bump schema version") } return tx.Commit() @@ -115,7 +117,7 @@ func customMigrateCopySourcesToMedia(tx *sql.Tx) error { OR sources LIKE '%"SACD"%' ;`) if err != nil { - return fmt.Errorf("could not run custom data migration: %v", err) + return errors.Wrap(err, "could not run custom data migration") } defer rows.Close() diff --git a/internal/database/user.go b/internal/database/user.go index c0a54d8..2927918 100644 --- a/internal/database/user.go +++ b/internal/database/user.go @@ -2,18 +2,22 @@ package database import ( "context" + "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/logger" + "github.com/autobrr/autobrr/pkg/errors" + + "github.com/rs/zerolog" ) type UserRepo struct { - log logger.Logger + log zerolog.Logger db *DB } func NewUserRepo(log logger.Logger, db *DB) domain.UserRepo { return &UserRepo{ - log: log, + log: log.With().Str("repo", "user").Logger(), db: db, } } @@ -23,19 +27,17 @@ func (r *UserRepo) GetUserCount(ctx context.Context) (int, error) { query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("user.store: error building query") - return 0, err + return 0, errors.Wrap(err, "error building query") } row := r.db.handler.QueryRowContext(ctx, query, args...) if err := row.Err(); err != nil { - return 0, err + return 0, errors.Wrap(err, "error executing query") } result := 0 if err := row.Scan(&result); err != nil { - r.log.Error().Err(err).Msg("could not query number of users") - return 0, err + return 0, errors.Wrap(err, "error scanning row") } return result, nil @@ -50,20 +52,18 @@ func (r *UserRepo) FindByUsername(ctx context.Context, username string) (*domain query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("user.store: error building query") - return nil, err + return nil, errors.Wrap(err, "error building query") } row := r.db.handler.QueryRowContext(ctx, query, args...) if err := row.Err(); err != nil { - return nil, err + return nil, errors.Wrap(err, "error executing query") } var user domain.User if err := row.Scan(&user.ID, &user.Username, &user.Password); err != nil { - r.log.Error().Err(err).Msg("could not scan user to struct") - return nil, err + return nil, errors.Wrap(err, "error scanning row") } return &user, nil @@ -80,14 +80,12 @@ func (r *UserRepo) Store(ctx context.Context, user domain.User) error { query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("user.store: error building query") - return err + return errors.Wrap(err, "error building query") } _, err = r.db.handler.ExecContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("user.store: error executing query") - return err + return errors.Wrap(err, "error executing query") } return err @@ -105,14 +103,12 @@ func (r *UserRepo) Update(ctx context.Context, user domain.User) error { query, args, err := queryBuilder.ToSql() if err != nil { - r.log.Error().Stack().Err(err).Msg("user.store: error building query") - return err + return errors.Wrap(err, "error building query") } _, err = r.db.handler.ExecContext(ctx, query, args...) if err != nil { - r.log.Error().Stack().Err(err).Msg("user.store: error executing query") - return err + return errors.Wrap(err, "error executing query") } return err diff --git a/internal/domain/client.go b/internal/domain/client.go index 4f266e0..f7bacea 100644 --- a/internal/domain/client.go +++ b/internal/domain/client.go @@ -3,7 +3,6 @@ package domain import "context" type DownloadClientRepo interface { - //FindByActionID(actionID int) ([]DownloadClient, error) List(ctx context.Context) ([]DownloadClient, error) FindByID(ctx context.Context, id int32) (*DownloadClient, error) Store(ctx context.Context, client DownloadClient) (*DownloadClient, error) diff --git a/internal/domain/release.go b/internal/domain/release.go index 7dac291..30ac00b 100644 --- a/internal/domain/release.go +++ b/internal/domain/release.go @@ -14,12 +14,12 @@ import ( "strings" "time" - "golang.org/x/net/publicsuffix" + "github.com/autobrr/autobrr/pkg/errors" "github.com/anacrolix/torrent/metainfo" "github.com/dustin/go-humanize" "github.com/moistari/rls" - "github.com/pkg/errors" + "golang.org/x/net/publicsuffix" ) type ReleaseRepo interface { @@ -269,7 +269,7 @@ func (r *Release) DownloadTorrentFile() error { jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) if err != nil { - return err + return errors.Wrap(err, "could not create cookiejar") } customTransport := http.DefaultTransport.(*http.Transport).Clone() @@ -300,7 +300,7 @@ func (r *Release) DownloadTorrentFile() error { // retry logic if resp.StatusCode != http.StatusOK { - return fmt.Errorf("error downloading torrent (%v) file (%v) from '%v' - status code: %d", r.TorrentName, r.TorrentURL, r.Indexer, resp.StatusCode) + return errors.New("error downloading torrent (%v) file (%v) from '%v' - status code: %d", r.TorrentName, r.TorrentURL, r.Indexer, resp.StatusCode) } // Create tmp file @@ -479,7 +479,7 @@ func getStringMapValue(stringMap map[string]string, key string) (string, error) } } - return "", fmt.Errorf("key was not found in map: %q", lowerKey) + return "", errors.New("key was not found in map: %q", lowerKey) } func SplitAny(s string, seps string) []string { diff --git a/internal/domain/releasetags.go b/internal/domain/releasetags.go index d3c5b1e..5417f60 100644 --- a/internal/domain/releasetags.go +++ b/internal/domain/releasetags.go @@ -3,6 +3,8 @@ package domain import ( "fmt" "regexp" + + "github.com/autobrr/autobrr/pkg/errors" ) var types map[string][]*TagInfo @@ -192,7 +194,7 @@ func init() { var err error //if info.re, err = regexp.Compile(`(?i)^(?:` + info.RE() + `)$`); err != nil { if info.re, err = regexp.Compile(`(?i)(?:` + info.RE() + `)`); err != nil { - fmt.Errorf("tag %q has invalid regexp %q\n", s, info.re) + errors.Wrap(err, "tag %q has invalid regexp %q\n", s, info.re) } } } diff --git a/internal/download_client/connection.go b/internal/download_client/connection.go index befd9e5..98a787b 100644 --- a/internal/download_client/connection.go +++ b/internal/download_client/connection.go @@ -1,12 +1,10 @@ package download_client import ( - "fmt" "time" - "github.com/pkg/errors" - "github.com/autobrr/autobrr/internal/domain" + "github.com/autobrr/autobrr/pkg/errors" "github.com/autobrr/autobrr/pkg/lidarr" "github.com/autobrr/autobrr/pkg/qbittorrent" "github.com/autobrr/autobrr/pkg/radarr" @@ -48,6 +46,7 @@ func (s *service) testQbittorrentConnection(client domain.DownloadClient) error Password: client.Password, TLS: client.TLS, TLSSkipVerify: client.TLSSkipVerify, + Log: s.subLogger, } // only set basic auth if enabled @@ -60,7 +59,7 @@ func (s *service) testQbittorrentConnection(client domain.DownloadClient) error qbt := qbittorrent.NewClient(qbtSettings) err := qbt.Login() if err != nil { - return errors.Wrap(err, fmt.Sprintf("error logging into client: %v", client.Host)) + return errors.Wrap(err, "error logging into client: %v", client.Host) } s.log.Debug().Msgf("test client connection for qBittorrent: success") @@ -94,7 +93,7 @@ func (s *service) testDelugeConnection(client domain.DownloadClient) error { // perform connection to Deluge server err := deluge.Connect() if err != nil { - return errors.Wrap(err, fmt.Sprintf("error logging into client: %v", client.Host)) + return errors.Wrap(err, "error logging into client: %v", client.Host) } defer deluge.Close() @@ -102,7 +101,7 @@ func (s *service) testDelugeConnection(client domain.DownloadClient) error { // print daemon version ver, err := deluge.DaemonVersion() if err != nil { - return errors.Wrap(err, fmt.Sprintf("could not get daemon version: %v", client.Host)) + return errors.Wrap(err, "could not get daemon version: %v", client.Host) } s.log.Debug().Msgf("test client connection for Deluge: success - daemon version: %v", ver) @@ -117,11 +116,12 @@ func (s *service) testRadarrConnection(client domain.DownloadClient) error { BasicAuth: client.Settings.Basic.Auth, Username: client.Settings.Basic.Username, Password: client.Settings.Basic.Password, + Log: s.subLogger, }) _, err := r.Test() if err != nil { - return errors.Wrap(err, fmt.Sprintf("radarr: connection test failed: %v", client.Host)) + return errors.Wrap(err, "radarr: connection test failed: %v", client.Host) } s.log.Debug().Msgf("test client connection for Radarr: success") @@ -136,11 +136,12 @@ func (s *service) testSonarrConnection(client domain.DownloadClient) error { BasicAuth: client.Settings.Basic.Auth, Username: client.Settings.Basic.Username, Password: client.Settings.Basic.Password, + Log: s.subLogger, }) _, err := r.Test() if err != nil { - return errors.Wrap(err, fmt.Sprintf("sonarr: connection test failed: %v", client.Host)) + return errors.Wrap(err, "sonarr: connection test failed: %v", client.Host) } s.log.Debug().Msgf("test client connection for Sonarr: success") @@ -155,11 +156,12 @@ func (s *service) testLidarrConnection(client domain.DownloadClient) error { BasicAuth: client.Settings.Basic.Auth, Username: client.Settings.Basic.Username, Password: client.Settings.Basic.Password, + Log: s.subLogger, }) _, err := r.Test() if err != nil { - return errors.Wrap(err, fmt.Sprintf("lidarr: connection test failed: %v", client.Host)) + return errors.Wrap(err, "lidarr: connection test failed: %v", client.Host) } s.log.Debug().Msgf("test client connection for Lidarr: success") @@ -174,11 +176,12 @@ func (s *service) testWhisparrConnection(client domain.DownloadClient) error { BasicAuth: client.Settings.Basic.Auth, Username: client.Settings.Basic.Username, Password: client.Settings.Basic.Password, + Log: s.subLogger, }) _, err := r.Test() if err != nil { - return errors.Wrap(err, fmt.Sprintf("whisparr: connection test failed: %v", client.Host)) + return errors.Wrap(err, "whisparr: connection test failed: %v", client.Host) } s.log.Debug().Msgf("test client connection for whisparr: success") diff --git a/internal/download_client/service.go b/internal/download_client/service.go index ae85e01..bd93402 100644 --- a/internal/download_client/service.go +++ b/internal/download_client/service.go @@ -3,9 +3,13 @@ package download_client import ( "context" "errors" + "log" "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/logger" + + "github.com/dcarbone/zadapters/zstdlog" + "github.com/rs/zerolog" ) type Service interface { @@ -18,23 +22,40 @@ type Service interface { } type service struct { - log logger.Logger - repo domain.DownloadClientRepo + log zerolog.Logger + repo domain.DownloadClientRepo + subLogger *log.Logger } func NewService(log logger.Logger, repo domain.DownloadClientRepo) Service { - return &service{ - log: log, + s := &service{ + log: log.With().Str("module", "download_client").Logger(), repo: repo, } + + s.subLogger = zstdlog.NewStdLoggerWithLevel(s.log.With().Logger(), zerolog.TraceLevel) + + return s } func (s *service) List(ctx context.Context) ([]domain.DownloadClient, error) { - return s.repo.List(ctx) + clients, err := s.repo.List(ctx) + if err != nil { + s.log.Error().Err(err).Msg("could not list download clients") + return nil, err + } + + return clients, nil } func (s *service) FindByID(ctx context.Context, id int32) (*domain.DownloadClient, error) { - return s.repo.FindByID(ctx, id) + client, err := s.repo.FindByID(ctx, id) + if err != nil { + s.log.Error().Err(err).Msgf("could not find download client by id: %v", id) + return nil, err + } + + return client, nil } func (s *service) Store(ctx context.Context, client domain.DownloadClient) (*domain.DownloadClient, error) { @@ -46,7 +67,13 @@ func (s *service) Store(ctx context.Context, client domain.DownloadClient) (*dom } // store - return s.repo.Store(ctx, client) + c, err := s.repo.Store(ctx, client) + if err != nil { + s.log.Error().Err(err).Msgf("could not store download client: %+v", client) + return nil, err + } + + return c, err } func (s *service) Update(ctx context.Context, client domain.DownloadClient) (*domain.DownloadClient, error) { @@ -57,12 +84,23 @@ func (s *service) Update(ctx context.Context, client domain.DownloadClient) (*do return nil, errors.New("validation error: no type") } - // store - return s.repo.Update(ctx, client) + // update + c, err := s.repo.Update(ctx, client) + if err != nil { + s.log.Error().Err(err).Msgf("could not update download client: %+v", client) + return nil, err + } + + return c, err } func (s *service) Delete(ctx context.Context, clientID int) error { - return s.repo.Delete(ctx, clientID) + err := s.repo.Delete(ctx, clientID) + if err != nil { + s.log.Error().Err(err).Msgf("could not delete download client: %v", clientID) + return err + } + return nil } func (s *service) Test(client domain.DownloadClient) error { diff --git a/internal/events/subscribers.go b/internal/events/subscribers.go index 7444a15..768b4ae 100644 --- a/internal/events/subscribers.go +++ b/internal/events/subscribers.go @@ -9,10 +9,11 @@ import ( "github.com/autobrr/autobrr/internal/release" "github.com/asaskevich/EventBus" + "github.com/rs/zerolog" ) type Subscriber struct { - log logger.Logger + log zerolog.Logger eventbus EventBus.Bus notificationSvc notification.Service releaseSvc release.Service @@ -20,7 +21,7 @@ type Subscriber struct { func NewSubscribers(log logger.Logger, eventbus EventBus.Bus, notificationSvc notification.Service, releaseSvc release.Service) Subscriber { s := Subscriber{ - log: log, + log: log.With().Str("module", "events").Logger(), eventbus: eventbus, notificationSvc: notificationSvc, releaseSvc: releaseSvc, @@ -55,7 +56,7 @@ func (s Subscriber) releasePushStatus(actionStatus *domain.ReleaseActionStatus) } func (s Subscriber) sendNotification(event *domain.NotificationEvent, payload *domain.NotificationPayload) { - s.log.Trace().Msgf("events: '%v' '%+v'", event, payload) + s.log.Trace().Msgf("events: '%v' '%+v'", *event, payload) s.notificationSvc.Send(*event, *payload) } diff --git a/internal/feed/service.go b/internal/feed/service.go index e4a248f..3db4e8b 100644 --- a/internal/feed/service.go +++ b/internal/feed/service.go @@ -2,14 +2,16 @@ package feed import ( "context" - "errors" "fmt" "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/release" "github.com/autobrr/autobrr/internal/scheduler" + "github.com/autobrr/autobrr/pkg/errors" "github.com/autobrr/autobrr/pkg/torznab" + + "github.com/rs/zerolog" ) type Service interface { @@ -34,7 +36,7 @@ type feedInstance struct { } type service struct { - log logger.Logger + log zerolog.Logger jobs map[string]int repo domain.FeedRepo @@ -45,7 +47,7 @@ type service struct { func NewService(log logger.Logger, repo domain.FeedRepo, cacheRepo domain.FeedCacheRepo, releaseSvc release.Service, scheduler scheduler.Service) Service { return &service{ - log: log, + log: log.With().Str("module", "feed").Logger(), jobs: map[string]int{}, repo: repo, cacheRepo: cacheRepo, @@ -55,31 +57,67 @@ func NewService(log logger.Logger, repo domain.FeedRepo, cacheRepo domain.FeedCa } func (s *service) FindByID(ctx context.Context, id int) (*domain.Feed, error) { - return s.repo.FindByID(ctx, id) + feed, err := s.repo.FindByID(ctx, id) + if err != nil { + s.log.Error().Err(err).Msgf("could not find feed by id: %v", id) + return nil, err + } + + return feed, nil } func (s *service) FindByIndexerIdentifier(ctx context.Context, indexer string) (*domain.Feed, error) { - return s.repo.FindByIndexerIdentifier(ctx, indexer) + feed, err := s.repo.FindByIndexerIdentifier(ctx, indexer) + if err != nil { + s.log.Error().Err(err).Msgf("could not find feed by indexer: %v", indexer) + return nil, err + } + + return feed, nil } func (s *service) Find(ctx context.Context) ([]domain.Feed, error) { - return s.repo.Find(ctx) + feeds, err := s.repo.Find(ctx) + if err != nil { + s.log.Error().Err(err).Msg("could not find feeds") + return nil, err + } + + return feeds, err } func (s *service) Store(ctx context.Context, feed *domain.Feed) error { - return s.repo.Store(ctx, feed) + if err := s.repo.Store(ctx, feed); err != nil { + s.log.Error().Err(err).Msgf("could not store feed: %+v", feed) + return err + } + + return nil } func (s *service) Update(ctx context.Context, feed *domain.Feed) error { - return s.update(ctx, feed) + if err := s.repo.Update(ctx, feed); err != nil { + s.log.Error().Err(err).Msgf("could not update feed: %+v", feed) + return err + } + return nil } func (s *service) Delete(ctx context.Context, id int) error { - return s.delete(ctx, id) + if err := s.repo.Delete(ctx, id); err != nil { + s.log.Error().Err(err).Msgf("could not delete feed by id: %v", id) + return err + } + return nil } func (s *service) ToggleEnabled(ctx context.Context, id int, enabled bool) error { - return s.toggleEnabled(ctx, id, enabled) + err := s.repo.ToggleEnabled(ctx, id, enabled) + if err != nil { + s.log.Error().Err(err).Msgf("could not toggle feed by id: %v", id) + return err + } + return nil } func (s *service) update(ctx context.Context, feed *domain.Feed) error { @@ -155,7 +193,7 @@ func (s *service) Start() error { // get all torznab indexer definitions feeds, err := s.repo.Find(context.TODO()) if err != nil { - s.log.Error().Err(err).Msg("feed.Start: error getting feeds") + s.log.Error().Err(err).Msg("feed.Start: error finding feeds") return err } @@ -241,20 +279,12 @@ func (s *service) addTorznabJob(f feedInstance) error { c := torznab.NewClient(f.URL, f.ApiKey) // create job - job := &TorznabJob{ - Name: f.Name, - IndexerIdentifier: f.IndexerIdentifier, - Client: c, - Log: l, - Repo: s.cacheRepo, - ReleaseSvc: s.releaseSvc, - URL: f.URL, - } + job := NewTorznabJob(f.Name, f.IndexerIdentifier, l, f.URL, c, s.cacheRepo, s.releaseSvc) // schedule job id, err := s.scheduler.AddJob(job, f.CronSchedule, f.IndexerIdentifier) if err != nil { - return fmt.Errorf("feed.AddTorznabJob: add job failed: %w", err) + return errors.Wrap(err, "feed.AddTorznabJob: add job failed") } job.JobID = id @@ -269,7 +299,7 @@ func (s *service) addTorznabJob(f feedInstance) error { func (s *service) stopTorznabJob(indexer string) error { // remove job from scheduler if err := s.scheduler.RemoveJobByIdentifier(indexer); err != nil { - return fmt.Errorf("feed.stopTorznabJob: stop job failed: %w", err) + return errors.Wrap(err, "feed.stopTorznabJob: stop job failed") } s.log.Debug().Msgf("feed.stopTorznabJob: %v", indexer) diff --git a/internal/feed/torznab.go b/internal/feed/torznab.go index a35ec1f..289f4c9 100644 --- a/internal/feed/torznab.go +++ b/internal/feed/torznab.go @@ -1,15 +1,15 @@ package feed import ( - "fmt" "sort" "time" - "github.com/rs/zerolog" - "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/release" + "github.com/autobrr/autobrr/pkg/errors" "github.com/autobrr/autobrr/pkg/torznab" + + "github.com/rs/zerolog" ) type TorznabJob struct { @@ -27,6 +27,18 @@ type TorznabJob struct { JobID int } +func NewTorznabJob(name string, indexerIdentifier string, log zerolog.Logger, url string, client *torznab.Client, repo domain.FeedCacheRepo, releaseSvc release.Service) *TorznabJob { + return &TorznabJob{ + Name: name, + IndexerIdentifier: indexerIdentifier, + Log: log, + URL: url, + Client: client, + Repo: repo, + ReleaseSvc: releaseSvc, + } +} + func (j *TorznabJob) Run() { err := j.process() if err != nil { @@ -44,7 +56,7 @@ func (j *TorznabJob) process() error { items, err := j.getFeed() if err != nil { j.Log.Error().Err(err).Msgf("torznab.process: error fetching feed items") - return fmt.Errorf("torznab.process: error getting feed items: %w", err) + return errors.Wrap(err, "torznab.process: error getting feed items") } if len(items) == 0 { @@ -82,7 +94,7 @@ func (j *TorznabJob) getFeed() ([]torznab.FeedItem, error) { feedItems, err := j.Client.GetFeed() if err != nil { j.Log.Error().Err(err).Msgf("torznab.getFeed: error fetching feed items") - return nil, err + return nil, errors.Wrap(err, "error fetching feed items") } j.Log.Trace().Msgf("torznab getFeed: refreshing feed: %v, found (%d) items", j.Name, len(feedItems)) diff --git a/internal/filter/service.go b/internal/filter/service.go index ab2e23d..370984d 100644 --- a/internal/filter/service.go +++ b/internal/filter/service.go @@ -10,6 +10,7 @@ import ( "github.com/autobrr/autobrr/internal/logger" "github.com/dustin/go-humanize" + "github.com/rs/zerolog" ) type Service interface { @@ -25,7 +26,7 @@ type Service interface { } type service struct { - log logger.Logger + log zerolog.Logger repo domain.FilterRepo actionRepo domain.ActionRepo indexerSvc indexer.Service @@ -34,7 +35,7 @@ type service struct { func NewService(log logger.Logger, repo domain.FilterRepo, actionRepo domain.ActionRepo, apiService indexer.APIService, indexerSvc indexer.Service) Service { return &service{ - log: log, + log: log.With().Str("module", "filter").Logger(), repo: repo, actionRepo: actionRepo, apiService: apiService, @@ -46,6 +47,7 @@ func (s *service) ListFilters(ctx context.Context) ([]domain.Filter, error) { // get filters filters, err := s.repo.ListFilters(ctx) if err != nil { + s.log.Error().Err(err).Msgf("could not find list filters") return nil, err } @@ -238,7 +240,7 @@ func (s *service) CheckFilter(f domain.Filter, release *domain.Release) (bool, e rejections, matchedFilter := f.CheckFilter(release) if len(rejections) > 0 { - s.log.Trace().Msgf("filter.Service.CheckFilter: (%v) for release: %v rejections: (%v)", f.Name, release.TorrentName, release.RejectionsString()) + s.log.Debug().Msgf("filter.Service.CheckFilter: (%v) for release: %v rejections: (%v)", f.Name, release.TorrentName, release.RejectionsString()) return false, nil } diff --git a/internal/indexer/api.go b/internal/indexer/api.go index 1a09aa4..dc75805 100644 --- a/internal/indexer/api.go +++ b/internal/indexer/api.go @@ -1,12 +1,13 @@ package indexer import ( - "fmt" + "github.com/rs/zerolog" "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/mock" "github.com/autobrr/autobrr/pkg/btn" + "github.com/autobrr/autobrr/pkg/errors" "github.com/autobrr/autobrr/pkg/ggn" "github.com/autobrr/autobrr/pkg/ptp" "github.com/autobrr/autobrr/pkg/red" @@ -25,13 +26,13 @@ type apiClient interface { } type apiService struct { - log logger.Logger + log zerolog.Logger apiClients map[string]apiClient } func NewAPIService(log logger.Logger) APIService { return &apiService{ - log: log, + log: log.With().Str("module", "indexer-api").Logger(), apiClients: make(map[string]apiClient), } } @@ -42,7 +43,7 @@ func (s *apiService) GetTorrentByID(indexer string, torrentID string) (*domain.T return nil, nil } - s.log.Trace().Str("service", "api").Str("method", "GetTorrentByID").Msgf("'%v' trying to fetch torrent from api", indexer) + s.log.Trace().Str("method", "GetTorrentByID").Msgf("'%v' trying to fetch torrent from api", indexer) t, err := v.GetTorrentByID(torrentID) if err != nil { @@ -50,7 +51,7 @@ func (s *apiService) GetTorrentByID(indexer string, torrentID string) (*domain.T return nil, err } - s.log.Trace().Str("service", "api").Str("method", "GetTorrentByID").Msgf("'%v' successfully fetched torrent from api: %+v", indexer, t) + s.log.Trace().Str("method", "GetTorrentByID").Msgf("'%v' successfully fetched torrent from api: %+v", indexer, t) return t, nil } @@ -63,6 +64,7 @@ func (s *apiService) TestConnection(indexer string) (bool, error) { t, err := v.TestAPI() if err != nil { + s.log.Error().Err(err).Msgf("error testing connection for api: %v", indexer) return false, err } @@ -72,9 +74,9 @@ func (s *apiService) TestConnection(indexer string) (bool, error) { func (s *apiService) AddClient(indexer string, settings map[string]string) error { // basic validation if indexer == "" { - return fmt.Errorf("api.Service.AddClient: validation falied: indexer can't be empty") + return errors.New("api.Service.AddClient: validation falied: indexer can't be empty") } else if len(settings) == 0 { - return fmt.Errorf("api.Service.AddClient: validation falied: settings can't be empty") + return errors.New("api.Service.AddClient: validation falied: settings can't be empty") } s.log.Trace().Msgf("api.Service.AddClient: init api client for '%v'", indexer) @@ -84,33 +86,33 @@ func (s *apiService) AddClient(indexer string, settings map[string]string) error case "btn": key, ok := settings["api_key"] if !ok || key == "" { - return fmt.Errorf("api.Service.AddClient: could not initialize btn client: missing var 'api_key'") + return errors.New("api.Service.AddClient: could not initialize btn client: missing var 'api_key'") } s.apiClients[indexer] = btn.NewClient("", key) case "ptp": user, ok := settings["api_user"] if !ok || user == "" { - return fmt.Errorf("api.Service.AddClient: could not initialize ptp client: missing var 'api_user'") + return errors.New("api.Service.AddClient: could not initialize ptp client: missing var 'api_user'") } key, ok := settings["api_key"] if !ok || key == "" { - return fmt.Errorf("api.Service.AddClient: could not initialize ptp client: missing var 'api_key'") + return errors.New("api.Service.AddClient: could not initialize ptp client: missing var 'api_key'") } s.apiClients[indexer] = ptp.NewClient("", user, key) case "ggn": key, ok := settings["api_key"] if !ok || key == "" { - return fmt.Errorf("api.Service.AddClient: could not initialize ggn client: missing var 'api_key'") + return errors.New("api.Service.AddClient: could not initialize ggn client: missing var 'api_key'") } s.apiClients[indexer] = ggn.NewClient("", key) case "redacted": key, ok := settings["api_key"] if !ok || key == "" { - return fmt.Errorf("api.Service.AddClient: could not initialize red client: missing var 'api_key'") + return errors.New("api.Service.AddClient: could not initialize red client: missing var 'api_key'") } s.apiClients[indexer] = red.NewClient("", key) @@ -118,7 +120,7 @@ func (s *apiService) AddClient(indexer string, settings map[string]string) error s.apiClients[indexer] = mock.NewMockClient("", "mock") default: - return fmt.Errorf("api.Service.AddClient: could not initialize client: unsupported indexer '%v'", indexer) + return errors.New("api.Service.AddClient: could not initialize client: unsupported indexer '%v'", indexer) } return nil diff --git a/internal/indexer/service.go b/internal/indexer/service.go index 7529d0b..26466a7 100644 --- a/internal/indexer/service.go +++ b/internal/indexer/service.go @@ -2,7 +2,6 @@ package indexer import ( "context" - "errors" "fmt" "io/fs" "os" @@ -13,6 +12,7 @@ import ( "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/scheduler" + "github.com/autobrr/autobrr/pkg/errors" "github.com/gosimple/slug" "github.com/rs/zerolog" @@ -52,7 +52,7 @@ type service struct { func NewService(log logger.Logger, config *domain.Config, repo domain.IndexerRepo, apiService APIService, scheduler scheduler.Service) Service { return &service{ - log: log.With().Str("service", "indexer").Logger(), + log: log.With().Str("module", "indexer").Logger(), config: config, repo: repo, apiService: apiService, @@ -94,6 +94,7 @@ func (s *service) Store(ctx context.Context, indexer domain.Indexer) (*domain.In func (s *service) Update(ctx context.Context, indexer domain.Indexer) (*domain.Indexer, error) { i, err := s.repo.Update(ctx, indexer) if err != nil { + s.log.Error().Err(err).Msgf("could not update indexer: %+v", indexer) return nil, err } @@ -115,6 +116,7 @@ func (s *service) Update(ctx context.Context, indexer domain.Indexer) (*domain.I func (s *service) Delete(ctx context.Context, id int) error { if err := s.repo.Delete(ctx, id); err != nil { + s.log.Error().Err(err).Msgf("could not delete indexer by id: %v", id) return err } @@ -125,11 +127,23 @@ func (s *service) Delete(ctx context.Context, id int) error { } func (s *service) FindByFilterID(ctx context.Context, id int) ([]domain.Indexer, error) { - return s.repo.FindByFilterID(ctx, id) + indexers, err := s.repo.FindByFilterID(ctx, id) + if err != nil { + s.log.Error().Err(err).Msgf("could not find indexers by filter id: %v", id) + return nil, err + } + + return indexers, err } func (s *service) List(ctx context.Context) ([]domain.Indexer, error) { - return s.repo.List(ctx) + indexers, err := s.repo.List(ctx) + if err != nil { + s.log.Error().Err(err).Msg("could not get indexer list") + return nil, err + } + + return indexers, err } func (s *service) GetAll() ([]*domain.IndexerDefinition, error) { @@ -154,6 +168,7 @@ func (s *service) GetAll() ([]*domain.IndexerDefinition, error) { func (s *service) mapIndexers() (map[string]*domain.IndexerDefinition, error) { indexers, err := s.repo.List(context.Background()) if err != nil { + s.log.Error().Err(err).Msg("could not read indexer list") return nil, err } @@ -264,6 +279,7 @@ func (s *service) Start() error { // load all indexer definitions err := s.LoadIndexerDefinitions() if err != nil { + s.log.Error().Err(err).Msg("could not load indexer definitions") return err } @@ -271,7 +287,7 @@ func (s *service) Start() error { // load custom indexer definitions err = s.LoadCustomIndexerDefinitions() if err != nil { - return fmt.Errorf("could not load custom indexer definitions: %w", err) + return errors.Wrap(err, "could not load custom indexer definitions") } } @@ -394,12 +410,12 @@ func (s *service) mapIRCServerDefinitionLookup(ircServer string, indexerDefiniti func (s *service) LoadIndexerDefinitions() error { entries, err := fs.ReadDir(Definitions, "definitions") if err != nil { - s.log.Fatal().Stack().Msgf("failed reading directory: %s", err) + s.log.Fatal().Err(err).Stack().Msg("failed reading directory") } if len(entries) == 0 { - s.log.Fatal().Stack().Msgf("failed reading directory: %s", err) - return err + s.log.Fatal().Err(err).Stack().Msg("failed reading directory") + return errors.Wrap(err, "could not read directory") } for _, f := range entries { @@ -417,13 +433,13 @@ func (s *service) LoadIndexerDefinitions() error { data, err := fs.ReadFile(Definitions, file) if err != nil { s.log.Error().Stack().Err(err).Msgf("failed reading file: %v", file) - return err + return errors.Wrap(err, "could not read file: %v", file) } err = yaml.Unmarshal(data, &d) if err != nil { s.log.Error().Stack().Err(err).Msgf("failed unmarshal file: %v", file) - return err + return errors.Wrap(err, "could not unmarshal file: %v", file) } if d.Implementation == "" { @@ -454,7 +470,8 @@ func (s *service) LoadCustomIndexerDefinitions() error { entries, err := outputDirRead.ReadDir(0) if err != nil { - s.log.Fatal().Stack().Msgf("failed reading directory: %s", err) + s.log.Fatal().Err(err).Stack().Msg("failed reading directory") + return errors.Wrap(err, "could not read directory") } customCount := 0 @@ -474,13 +491,13 @@ func (s *service) LoadCustomIndexerDefinitions() error { data, err := os.ReadFile(file) if err != nil { s.log.Error().Stack().Err(err).Msgf("failed reading file: %v", file) - return err + return errors.Wrap(err, "could not read file: %v", file) } var d *domain.IndexerDefinition if err = yaml.Unmarshal(data, &d); err != nil { s.log.Error().Stack().Err(err).Msgf("failed unmarshal file: %v", file) - return err + return errors.Wrap(err, "could not unmarshal file: %v", file) } if d == nil { diff --git a/internal/irc/handler.go b/internal/irc/handler.go index d45023a..a54b570 100644 --- a/internal/irc/handler.go +++ b/internal/irc/handler.go @@ -10,9 +10,9 @@ import ( "github.com/autobrr/autobrr/internal/announce" "github.com/autobrr/autobrr/internal/domain" - "github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/notification" "github.com/autobrr/autobrr/internal/release" + "github.com/autobrr/autobrr/pkg/errors" "github.com/avast/retry-go" "github.com/dcarbone/zadapters/zstdlog" @@ -80,7 +80,7 @@ type Handler struct { authenticated bool } -func NewHandler(log logger.Logger, network domain.IrcNetwork, definitions []*domain.IndexerDefinition, releaseSvc release.Service, notificationSvc notification.Service) *Handler { +func NewHandler(log zerolog.Logger, network domain.IrcNetwork, definitions []*domain.IndexerDefinition, releaseSvc release.Service, notificationSvc notification.Service) *Handler { h := &Handler{ log: log.With().Str("network", network.Server).Logger(), client: nil, @@ -550,7 +550,7 @@ func (h *Handler) sendToAnnounceProcessor(channel string, msg string) error { // check if queue exists queue, ok := h.announceProcessors[channel] if !ok { - return fmt.Errorf("queue '%v' not found", channel) + return errors.New("queue '%v' not found", channel) } // if it exists, add msg diff --git a/internal/irc/service.go b/internal/irc/service.go index d88cd2a..7b71e84 100644 --- a/internal/irc/service.go +++ b/internal/irc/service.go @@ -2,7 +2,6 @@ package irc import ( "context" - "fmt" "strings" "sync" @@ -11,8 +10,9 @@ import ( "github.com/autobrr/autobrr/internal/logger" "github.com/autobrr/autobrr/internal/notification" "github.com/autobrr/autobrr/internal/release" + "github.com/autobrr/autobrr/pkg/errors" - "github.com/pkg/errors" + "github.com/rs/zerolog" ) type Service interface { @@ -29,7 +29,7 @@ type Service interface { } type service struct { - log logger.Logger + log zerolog.Logger repo domain.IrcRepo releaseService release.Service indexerService indexer.Service @@ -43,7 +43,7 @@ type service struct { func NewService(log logger.Logger, repo domain.IrcRepo, releaseSvc release.Service, indexerSvc indexer.Service, notificationSvc notification.Service) Service { return &service{ - log: log, + log: log.With().Str("module", "irc").Logger(), repo: repo, releaseService: releaseSvc, indexerService: indexerSvc, @@ -60,7 +60,7 @@ type handlerKey struct { func (s *service) StartHandlers() { networks, err := s.repo.FindActiveNetworks(context.Background()) if err != nil { - s.log.Error().Msgf("failed to list networks: %v", err) + s.log.Error().Err(err).Msg("failed to list networks") } for _, network := range networks { @@ -356,7 +356,7 @@ func (s *service) GetNetworkByID(ctx context.Context, id int64) (*domain.IrcNetw func (s *service) ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error) { networks, err := s.repo.ListNetworks(ctx) if err != nil { - s.log.Error().Err(err).Msgf("failed to list networks: %v", err) + s.log.Error().Err(err).Msg("failed to list networks") return nil, err } @@ -379,7 +379,7 @@ func (s *service) ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error) func (s *service) GetNetworksWithHealth(ctx context.Context) ([]domain.IrcNetworkWithHealth, error) { networks, err := s.repo.ListNetworks(ctx) if err != nil { - s.log.Error().Err(err).Msgf("failed to list networks: %v", err) + s.log.Error().Err(err).Msg("failed to list networks") return nil, err } @@ -469,6 +469,7 @@ func (s *service) GetNetworksWithHealth(ctx context.Context) ([]domain.IrcNetwor func (s *service) DeleteNetwork(ctx context.Context, id int64) error { network, err := s.GetNetworkByID(ctx, id) if err != nil { + s.log.Error().Stack().Err(err).Msgf("could not find network before delete: %v", network.Name) return err } @@ -477,10 +478,12 @@ func (s *service) DeleteNetwork(ctx context.Context, id int64) error { // Remove network and handler //if err = s.StopNetwork(network.Server); err != nil { if err = s.StopAndRemoveNetwork(handlerKey{network.Server, network.NickServ.Account}); err != nil { + s.log.Error().Stack().Err(err).Msgf("could not stop and delete network: %v", network.Name) return err } if err = s.repo.DeleteNetwork(ctx, id); err != nil { + s.log.Error().Stack().Err(err).Msgf("could not delete network: %v", network.Name) return err } @@ -509,7 +512,7 @@ func (s *service) UpdateNetwork(ctx context.Context, network *domain.IrcNetwork) err := s.checkIfNetworkRestartNeeded(network) if err != nil { s.log.Error().Stack().Err(err).Msgf("could not restart network: %+v", network.Name) - return fmt.Errorf("could not restart network: %v", network.Name) + return errors.New("could not restart network: %v", network.Name) } } else { @@ -517,7 +520,7 @@ func (s *service) UpdateNetwork(ctx context.Context, network *domain.IrcNetwork) err := s.StopAndRemoveNetwork(handlerKey{network.Server, network.NickServ.Account}) if err != nil { s.log.Error().Stack().Err(err).Msgf("could not stop network: %+v", network.Name) - return fmt.Errorf("could not stop network: %v", network.Name) + return errors.New("could not stop network: %v", network.Name) } } @@ -585,7 +588,7 @@ func (s *service) StoreNetwork(ctx context.Context, network *domain.IrcNetwork) err := s.checkIfNetworkRestartNeeded(existingNetwork) if err != nil { s.log.Error().Err(err).Msgf("could not restart network: %+v", existingNetwork.Name) - return fmt.Errorf("could not restart network: %v", existingNetwork.Name) + return errors.New("could not restart network: %v", existingNetwork.Name) } } diff --git a/internal/mock/indexer_api.go b/internal/mock/indexer_api.go index 74faaad..f613ebb 100644 --- a/internal/mock/indexer_api.go +++ b/internal/mock/indexer_api.go @@ -1,9 +1,8 @@ package mock import ( - "fmt" - "github.com/autobrr/autobrr/internal/domain" + "github.com/autobrr/autobrr/pkg/errors" ) type IndexerApiClient interface { @@ -27,7 +26,7 @@ func NewMockClient(url string, apiKey string) IndexerApiClient { func (c *IndexerClient) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error) { if torrentID == "" { - return nil, fmt.Errorf("mock client: must have torrentID") + return nil, errors.New("mock client: must have torrentID") } r := &domain.TorrentBasic{ diff --git a/internal/notification/discord.go b/internal/notification/discord.go index 4735b7c..f27f29a 100644 --- a/internal/notification/discord.go +++ b/internal/notification/discord.go @@ -11,7 +11,9 @@ import ( "time" "github.com/autobrr/autobrr/internal/domain" - "github.com/autobrr/autobrr/internal/logger" + "github.com/autobrr/autobrr/pkg/errors" + + "github.com/rs/zerolog" ) type DiscordMessage struct { @@ -42,12 +44,15 @@ const ( ) type discordSender struct { - log logger.Logger + log zerolog.Logger Settings domain.Notification } -func NewDiscordSender(log logger.Logger, settings domain.Notification) domain.NotificationSender { - return &discordSender{log: log, Settings: settings} +func NewDiscordSender(log zerolog.Logger, settings domain.Notification) domain.NotificationSender { + return &discordSender{ + log: log.With().Str("sender", "discord").Logger(), + Settings: settings, + } } func (a *discordSender) Send(event domain.NotificationEvent, payload domain.NotificationPayload) error { @@ -59,13 +64,13 @@ func (a *discordSender) Send(event domain.NotificationEvent, payload domain.Noti jsonData, err := json.Marshal(m) if err != nil { a.log.Error().Err(err).Msgf("discord client could not marshal data: %v", m) - return err + return errors.Wrap(err, "could not marshal data: %+v", m) } req, err := http.NewRequest(http.MethodPost, a.Settings.Webhook, bytes.NewBuffer(jsonData)) if err != nil { a.log.Error().Err(err).Msgf("discord client request error: %v", event) - return err + return errors.Wrap(err, "could not create request") } req.Header.Set("Content-Type", "application/json") @@ -81,22 +86,23 @@ func (a *discordSender) Send(event domain.NotificationEvent, payload domain.Noti res, err := client.Do(req) if err != nil { a.log.Error().Err(err).Msgf("discord client request error: %v", event) - return err + return errors.Wrap(err, "could not make request: %+v", req) } body, err := ioutil.ReadAll(res.Body) if err != nil { a.log.Error().Err(err).Msgf("discord client request error: %v", event) - return err + return errors.Wrap(err, "could not read data") } defer res.Body.Close() a.log.Trace().Msgf("discord status: %v response: %v", res.StatusCode, string(body)) - if res.StatusCode != http.StatusNoContent { + // discord responds with 204, Notifiarr with 204 so lets take all 200 as ok + if res.StatusCode >= 300 { a.log.Error().Err(err).Msgf("discord client request error: %v", string(body)) - return fmt.Errorf("err: %v", string(body)) + return errors.New("bad status: %v body: %v", res.StatusCode, string(body)) } a.log.Debug().Msg("notification successfully sent to discord") diff --git a/internal/notification/service.go b/internal/notification/service.go index e246289..8f2a58d 100644 --- a/internal/notification/service.go +++ b/internal/notification/service.go @@ -5,6 +5,8 @@ import ( "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/logger" + + "github.com/rs/zerolog" ) type Service interface { @@ -18,14 +20,14 @@ type Service interface { } type service struct { - log logger.Logger + log zerolog.Logger repo domain.NotificationRepo senders []domain.NotificationSender } func NewService(log logger.Logger, repo domain.NotificationRepo) Service { s := &service{ - log: log, + log: log.With().Str("module", "notification").Logger(), repo: repo, senders: []domain.NotificationSender{}, } @@ -36,16 +38,29 @@ func NewService(log logger.Logger, repo domain.NotificationRepo) Service { } func (s *service) Find(ctx context.Context, params domain.NotificationQueryParams) ([]domain.Notification, int, error) { - return s.repo.Find(ctx, params) + n, count, err := s.repo.Find(ctx, params) + if err != nil { + s.log.Error().Err(err).Msgf("could not find notification with params: %+v", params) + return nil, 0, err + } + + return n, count, err } func (s *service) FindByID(ctx context.Context, id int) (*domain.Notification, error) { - return s.repo.FindByID(ctx, id) + n, err := s.repo.FindByID(ctx, id) + if err != nil { + s.log.Error().Err(err).Msgf("could not find notification by id: %v", id) + return nil, err + } + + return n, err } func (s *service) Store(ctx context.Context, n domain.Notification) (*domain.Notification, error) { _, err := s.repo.Store(ctx, n) if err != nil { + s.log.Error().Err(err).Msgf("could not store notification: %+v", n) return nil, err } @@ -61,6 +76,7 @@ func (s *service) Store(ctx context.Context, n domain.Notification) (*domain.Not func (s *service) Update(ctx context.Context, n domain.Notification) (*domain.Notification, error) { _, err := s.repo.Update(ctx, n) if err != nil { + s.log.Error().Err(err).Msgf("could not update notification: %+v", n) return nil, err } @@ -76,6 +92,7 @@ func (s *service) Update(ctx context.Context, n domain.Notification) (*domain.No func (s *service) Delete(ctx context.Context, id int) error { err := s.repo.Delete(ctx, id) if err != nil { + s.log.Error().Err(err).Msgf("could not delete notification: %v", id) return err } @@ -91,6 +108,7 @@ func (s *service) Delete(ctx context.Context, id int) error { func (s *service) registerSenders() { senders, err := s.repo.List(context.Background()) if err != nil { + s.log.Error().Err(err).Msg("could not find notifications") return } diff --git a/internal/notification/telegram.go b/internal/notification/telegram.go index 3ca142e..567800d 100644 --- a/internal/notification/telegram.go +++ b/internal/notification/telegram.go @@ -12,7 +12,9 @@ import ( "time" "github.com/autobrr/autobrr/internal/domain" - "github.com/autobrr/autobrr/internal/logger" + "github.com/autobrr/autobrr/pkg/errors" + + "github.com/rs/zerolog" ) type TelegramMessage struct { @@ -22,13 +24,13 @@ type TelegramMessage struct { } type telegramSender struct { - log logger.Logger + log zerolog.Logger Settings domain.Notification } -func NewTelegramSender(log logger.Logger, settings domain.Notification) domain.NotificationSender { +func NewTelegramSender(log zerolog.Logger, settings domain.Notification) domain.NotificationSender { return &telegramSender{ - log: log, + log: log.With().Str("sender", "telegram").Logger(), Settings: settings, } } @@ -44,7 +46,7 @@ func (s *telegramSender) Send(event domain.NotificationEvent, payload domain.Not jsonData, err := json.Marshal(m) if err != nil { s.log.Error().Err(err).Msgf("telegram client could not marshal data: %v", m) - return err + return errors.Wrap(err, "could not marshal data: %+v", m) } url := fmt.Sprintf("https://api.telegram.org/bot%v/sendMessage", s.Settings.Token) @@ -52,7 +54,7 @@ func (s *telegramSender) Send(event domain.NotificationEvent, payload domain.Not req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(jsonData)) if err != nil { s.log.Error().Err(err).Msgf("telegram client request error: %v", event) - return err + return errors.Wrap(err, "could not create request") } req.Header.Set("Content-Type", "application/json") @@ -68,13 +70,13 @@ func (s *telegramSender) Send(event domain.NotificationEvent, payload domain.Not res, err := client.Do(req) if err != nil { s.log.Error().Err(err).Msgf("telegram client request error: %v", event) - return err + return errors.Wrap(err, "could not make request: %+v", req) } body, err := ioutil.ReadAll(res.Body) if err != nil { s.log.Error().Err(err).Msgf("telegram client request error: %v", event) - return err + return errors.Wrap(err, "could not read data") } defer res.Body.Close() @@ -83,7 +85,7 @@ func (s *telegramSender) Send(event domain.NotificationEvent, payload domain.Not if res.StatusCode != http.StatusOK { s.log.Error().Err(err).Msgf("telegram client request error: %v", string(body)) - return fmt.Errorf("err: %v", string(body)) + return errors.New("bad status: %v body: %v", res.StatusCode, string(body)) } s.log.Debug().Msg("notification successfully sent to telegram") diff --git a/internal/release/service.go b/internal/release/service.go index 69e3330..912d7ec 100644 --- a/internal/release/service.go +++ b/internal/release/service.go @@ -9,6 +9,8 @@ import ( "github.com/autobrr/autobrr/internal/domain" "github.com/autobrr/autobrr/internal/filter" "github.com/autobrr/autobrr/internal/logger" + + "github.com/rs/zerolog" ) type Service interface { @@ -30,7 +32,7 @@ type actionClientTypeKey struct { } type service struct { - log logger.Logger + log zerolog.Logger repo domain.ReleaseRepo actionSvc action.Service @@ -39,7 +41,7 @@ type service struct { func NewService(log logger.Logger, repo domain.ReleaseRepo, actionSvc action.Service, filterSvc filter.Service) Service { return &service{ - log: log, + log: log.With().Str("module", "release").Logger(), repo: repo, actionSvc: actionSvc, filterSvc: filterSvc, @@ -91,7 +93,7 @@ func (s *service) Process(release *domain.Release) { // get filters by priority filters, err := s.filterSvc.FindByIndexerIdentifier(release.Indexer) if err != nil { - s.log.Error().Err(err).Msgf("announce.Service.Process: error finding filters for indexer: %v", release.Indexer) + s.log.Error().Err(err).Msgf("release.Process: error finding filters for indexer: %v", release.Indexer) return } @@ -105,6 +107,8 @@ func (s *service) Process(release *domain.Release) { // loop over and check filters for _, f := range filters { + l := s.log.With().Str("indexer", release.Indexer).Str("filter", f.Name).Str("release", release.TorrentName).Logger() + // save filter on release release.Filter = &f release.FilterName = f.Name @@ -115,23 +119,25 @@ func (s *service) Process(release *domain.Release) { // test filter match, err := s.filterSvc.CheckFilter(f, release) if err != nil { - s.log.Error().Err(err).Msg("announce.Service.Process: could not find filter") + l.Error().Err(err).Msg("release.Process: error checking filter") return } if !match { - s.log.Trace().Msgf("announce.Service.Process: indexer: %v, filter: %v release: %v, no match", release.Indexer, release.Filter.Name, release.TorrentName) + l.Trace().Msgf("release.Process: indexer: %v, filter: %v release: %v, no match. rejections: %v", release.Indexer, release.Filter.Name, release.TorrentName, release.RejectionsString()) + + l.Debug().Msgf("release rejected: %v", release.RejectionsString()) continue } - s.log.Info().Msgf("Matched '%v' (%v) for %v", release.TorrentName, release.Filter.Name, release.Indexer) + l.Info().Msgf("Matched '%v' (%v) for %v", release.TorrentName, release.Filter.Name, release.Indexer) // save release here to only save those with rejections from actions instead of all releases if release.ID == 0 { release.FilterStatus = domain.ReleaseStatusFilterApproved err = s.Store(context.Background(), release) if err != nil { - s.log.Error().Err(err).Msgf("announce.Service.Process: error writing release to database: %+v", release) + l.Error().Err(err).Msgf("release.Process: error writing release to database: %+v", release) return } } @@ -139,7 +145,7 @@ func (s *service) Process(release *domain.Release) { // sleep for the delay period specified in the filter before running actions delay := release.Filter.Delay if delay > 0 { - s.log.Debug().Msgf("Delaying processing of '%v' (%v) for %v by %d seconds as specified in the filter", release.TorrentName, release.Filter.Name, release.Indexer, delay) + l.Debug().Msgf("Delaying processing of '%v' (%v) for %v by %d seconds as specified in the filter", release.TorrentName, release.Filter.Name, release.Indexer, delay) time.Sleep(time.Duration(delay) * time.Second) } @@ -149,22 +155,22 @@ func (s *service) Process(release *domain.Release) { for _, a := range release.Filter.Actions { // only run enabled actions if !a.Enabled { - s.log.Trace().Msgf("announce.Service.Process: indexer: %v, filter: %v release: %v action '%v' not enabled, skip", release.Indexer, release.Filter.Name, release.TorrentName, a.Name) + l.Trace().Msgf("release.Process: indexer: %v, filter: %v release: %v action '%v' not enabled, skip", release.Indexer, release.Filter.Name, release.TorrentName, a.Name) continue } - s.log.Trace().Msgf("announce.Service.Process: indexer: %v, filter: %v release: %v , run action: %v", release.Indexer, release.Filter.Name, release.TorrentName, a.Name) + l.Trace().Msgf("release.Process: indexer: %v, filter: %v release: %v , run action: %v", release.Indexer, release.Filter.Name, release.TorrentName, a.Name) // keep track of action clients to avoid sending the same thing all over again _, tried := triedActionClients[actionClientTypeKey{Type: a.Type, ClientID: a.ClientID}] if tried { - s.log.Trace().Msgf("announce.Service.Process: indexer: %v, filter: %v release: %v action client already tried, skip", release.Indexer, release.Filter.Name, release.TorrentName) + l.Trace().Msgf("release.Process: indexer: %v, filter: %v release: %v action client already tried, skip", release.Indexer, release.Filter.Name, release.TorrentName) continue } rejections, err = s.actionSvc.RunAction(a, *release) if err != nil { - s.log.Error().Stack().Err(err).Msgf("announce.Service.Process: error running actions for filter: %v", release.Filter.Name) + l.Error().Stack().Err(err).Msgf("release.Process: error running actions for filter: %v", release.Filter.Name) continue } @@ -173,7 +179,7 @@ func (s *service) Process(release *domain.Release) { triedActionClients[actionClientTypeKey{Type: a.Type, ClientID: a.ClientID}] = struct{}{} // log something and fire events - s.log.Debug().Msgf("announce.Service.Process: indexer: %v, filter: %v release: %v, rejected: %v", release.Indexer, release.Filter.Name, release.TorrentName, strings.Join(rejections, ", ")) + l.Debug().Str("action", a.Name).Str("action_type", string(a.Type)).Msgf("release rejected: %v", strings.Join(rejections, ", ")) } // if no rejections consider action approved, run next diff --git a/internal/scheduler/service.go b/internal/scheduler/service.go index 10e0957..917cce8 100644 --- a/internal/scheduler/service.go +++ b/internal/scheduler/service.go @@ -1,11 +1,11 @@ package scheduler import ( - "fmt" - "github.com/autobrr/autobrr/internal/logger" + "github.com/autobrr/autobrr/pkg/errors" "github.com/robfig/cron/v3" + "github.com/rs/zerolog" ) type Service interface { @@ -17,7 +17,7 @@ type Service interface { } type service struct { - log logger.Logger + log zerolog.Logger cron *cron.Cron jobs map[string]cron.EntryID @@ -25,7 +25,7 @@ type service struct { func NewService(log logger.Logger) Service { return &service{ - log: log, + log: log.With().Str("module", "scheduler").Logger(), cron: cron.New(cron.WithChain( cron.Recover(cron.DefaultLogger), )), @@ -51,7 +51,7 @@ func (s *service) AddJob(job cron.Job, interval string, identifier string) (int, cron.SkipIfStillRunning(cron.DiscardLogger)).Then(job), ) if err != nil { - return 0, fmt.Errorf("scheduler: add job failed: %w", err) + return 0, errors.Wrap(err, "scheduler: add job failed") } s.log.Debug().Msgf("scheduler.AddJob: job successfully added: %v", id) diff --git a/internal/server/server.go b/internal/server/server.go index 31ffc70..945ea98 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -3,6 +3,8 @@ package server import ( "sync" + "github.com/rs/zerolog" + "github.com/autobrr/autobrr/internal/feed" "github.com/autobrr/autobrr/internal/indexer" "github.com/autobrr/autobrr/internal/irc" @@ -11,7 +13,7 @@ import ( ) type Server struct { - log logger.Logger + log zerolog.Logger Hostname string Port int @@ -26,7 +28,7 @@ type Server struct { func NewServer(log logger.Logger, ircSvc irc.Service, indexerSvc indexer.Service, feedSvc feed.Service, scheduler scheduler.Service) *Server { return &Server{ - log: log, + log: log.With().Str("module", "server").Logger(), indexerService: indexerSvc, ircService: ircSvc, feedService: feedSvc, diff --git a/internal/user/service.go b/internal/user/service.go index 3105d98..1dbad52 100644 --- a/internal/user/service.go +++ b/internal/user/service.go @@ -2,9 +2,9 @@ package user import ( "context" - "errors" "github.com/autobrr/autobrr/internal/domain" + "github.com/autobrr/autobrr/pkg/errors" ) type Service interface { diff --git a/pkg/btn/btn.go b/pkg/btn/btn.go index 917a40b..2c02a7b 100644 --- a/pkg/btn/btn.go +++ b/pkg/btn/btn.go @@ -1,21 +1,20 @@ package btn import ( - "fmt" - "github.com/autobrr/autobrr/internal/domain" + "github.com/autobrr/autobrr/pkg/errors" ) func (c *Client) TestAPI() (bool, error) { res, err := c.rpcClient.Call("userInfo", [2]string{c.APIKey}) if err != nil { - return false, err + return false, errors.Wrap(err, "test api userInfo failed") } var u *UserInfo err = res.GetObject(&u) if err != nil { - return false, err + return false, errors.Wrap(err, "test api get userInfo") } if u.Username != "" { @@ -27,12 +26,12 @@ func (c *Client) TestAPI() (bool, error) { func (c *Client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error) { if torrentID == "" { - return nil, fmt.Errorf("btn client: must have torrentID") + return nil, errors.New("btn client: must have torrentID") } res, err := c.rpcClient.Call("getTorrentById", [2]string{c.APIKey, torrentID}) if err != nil { - return nil, err + return nil, errors.Wrap(err, "call getTorrentById failed") } var r *domain.TorrentBasic diff --git a/pkg/btn/client.go b/pkg/btn/client.go index 079884b..0a2edae 100644 --- a/pkg/btn/client.go +++ b/pkg/btn/client.go @@ -2,10 +2,13 @@ package btn import ( "context" + "io" + "log" "net/http" "time" "github.com/autobrr/autobrr/internal/domain" + "github.com/autobrr/autobrr/pkg/errors" "github.com/autobrr/autobrr/pkg/jsonrpc" "golang.org/x/time/rate" @@ -23,6 +26,8 @@ type Client struct { Ratelimiter *rate.Limiter APIKey string Headers http.Header + + Log *log.Logger } func NewClient(url string, apiKey string) BTNClient { @@ -41,6 +46,10 @@ func NewClient(url string, apiKey string) BTNClient { Ratelimiter: rate.NewLimiter(rate.Every(150*time.Hour), 1), // 150 rpcRequest every 1 hour } + if c.Log == nil { + c.Log = log.New(io.Discard, "", log.LstdFlags) + } + return c } @@ -48,11 +57,11 @@ func (c *Client) Do(req *http.Request) (*http.Response, error) { ctx := context.Background() err := c.Ratelimiter.Wait(ctx) // This is a blocking call. Honors the rate limit if err != nil { - return nil, err + return nil, errors.Wrap(err, "error waiting for ratelimiter") } resp, err := c.client.Do(req) if err != nil { - return nil, err + return nil, errors.Wrap(err, "could not make request") } return resp, nil } diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go new file mode 100644 index 0000000..b80141e --- /dev/null +++ b/pkg/errors/errors.go @@ -0,0 +1,171 @@ +package errors + +import ( + "fmt" + "reflect" + "runtime" + "unsafe" + + "github.com/pkg/errors" +) + +// Export a number of functions or variables from pkg/errors. We want people to be able to +// use them, if only via the entrypoints we've vetted in this file. +var ( + As = errors.As + Is = errors.Is + Cause = errors.Cause + Unwrap = errors.Unwrap +) + +// StackTrace should be aliases rather than newtype'd, so it can work with any of the +// functions we export from pkg/errors. +type StackTrace = errors.StackTrace + +type StackTracer interface { + StackTrace() errors.StackTrace +} + +// Sentinel is used to create compile-time errors that are intended to be value only, with +// no associated stack trace. +func Sentinel(msg string, args ...interface{}) error { + return fmt.Errorf(msg, args...) +} + +// New acts as pkg/errors.New does, producing a stack traced error, but supports +// interpolating of message parameters. Use this when you want the stack trace to start at +// the place you create the error. +func New(msg string, args ...interface{}) error { + return PopStack(errors.New(fmt.Sprintf(msg, args...))) +} + +// Wrap creates a new error from a cause, decorating the original error message with a +// prefix. +// +// It differs from the pkg/errors Wrap/Wrapf by idempotently creating a stack trace, +// meaning we won't create another stack trace when there is already a stack trace present +// that matches our current program position. +func Wrap(cause error, msg string, args ...interface{}) error { + causeStackTracer := new(StackTracer) + if errors.As(cause, causeStackTracer) { + // If our cause has set a stack trace, and that trace is a child of our own function + // as inferred by prefix matching our current program counter stack, then we only want + // to decorate the error message rather than add a redundant stack trace. + if ancestorOfCause(callers(1), (*causeStackTracer).StackTrace()) { + return errors.WithMessagef(cause, msg, args...) // no stack added, no pop required + } + } + + // Otherwise we can't see a stack trace that represents ourselves, so let's add one. + return PopStack(errors.Wrapf(cause, msg, args...)) +} + +// ancestorOfCause returns true if the caller looks to be an ancestor of the given stack +// trace. We check this by seeing whether our stack prefix-matches the cause stack, which +// should imply the error was generated directly from our goroutine. +func ancestorOfCause(ourStack []uintptr, causeStack errors.StackTrace) bool { + // Stack traces are ordered such that the deepest frame is first. We'll want to check + // for prefix matching in reverse. + // + // As an example, imagine we have a prefix-matching stack for ourselves: + // [ + // "github.com/onsi/ginkgo/internal/leafnodes.(*runner).runSync", + // "github.com/incident-io/core/server/pkg/errors_test.TestSuite", + // "testing.tRunner", + // "runtime.goexit" + // ] + // + // We'll want to compare this against an error cause that will have happened further + // down the stack. An example stack trace from such an error might be: + // [ + // "github.com/incident-io/core/server/pkg/errors.New", + // "github.com/incident-io/core/server/pkg/errors_test.glob..func1.2.2.2.1",, + // "github.com/onsi/ginkgo/internal/leafnodes.(*runner).runSync", + // "github.com/incident-io/core/server/pkg/errors_test.TestSuite", + // "testing.tRunner", + // "runtime.goexit" + // ] + // + // They prefix match, but we'll have to handle the match carefully as we need to match + // from back to forward. + + // We can't possibly prefix match if our stack is larger than the cause stack. + if len(ourStack) > len(causeStack) { + return false + } + + // We know the sizes are compatible, so compare program counters from back to front. + for idx := 0; idx < len(ourStack); idx++ { + if ourStack[len(ourStack)-1] != (uintptr)(causeStack[len(causeStack)-1]) { + return false + } + } + + // All comparisons checked out, these stacks match + return true +} + +func callers(skip int) []uintptr { + pc := make([]uintptr, 32) // assume we'll have at most 32 frames + n := runtime.Callers(skip+3, pc) // capture those frames, skipping runtime.Callers, ourself and the calling function + + return pc[:n] // return everything that we captured +} + +// RecoverPanic turns a panic into an error, adjusting the stacktrace so it originates at +// the line that caused it. +// +// Example: +// +// func Do() (err error) { +// defer func() { +// errors.RecoverPanic(recover(), &err) +// }() +// } +func RecoverPanic(r interface{}, errPtr *error) { + var err error + if r != nil { + if panicErr, ok := r.(error); ok { + err = errors.Wrap(panicErr, "caught panic") + } else { + err = errors.New(fmt.Sprintf("caught panic: %v", r)) + } + } + + if err != nil { + // Pop twice: once for the errors package, then again for the defer function we must + // run this under. We want the stacktrace to originate at the source of the panic, not + // in the infrastructure that catches it. + err = PopStack(err) // errors.go + err = PopStack(err) // defer + + *errPtr = err + } +} + +// PopStack removes the top of the stack from an errors stack trace. +func PopStack(err error) error { + if err == nil { + return err + } + + // We want to remove us, the internal/errors.New function, from the error stack we just + // produced. There's no official way of reaching into the error and adjusting this, as + // the stack is stored as a private field on an unexported struct. + // + // This does some unsafe badness to adjust that field, which should not be repeated + // anywhere else. + stackField := reflect.ValueOf(err).Elem().FieldByName("stack") + if stackField.IsZero() { + return err + } + stackFieldPtr := (**[]uintptr)(unsafe.Pointer(stackField.UnsafeAddr())) + + // Remove the first of the frames, dropping 'us' from the error stack trace. + frames := (**stackFieldPtr)[1:] + + // Assign to the internal stack field + *stackFieldPtr = &frames + + return err +} diff --git a/pkg/ggn/ggn.go b/pkg/ggn/ggn.go index 148c30c..06f87e2 100644 --- a/pkg/ggn/ggn.go +++ b/pkg/ggn/ggn.go @@ -3,7 +3,6 @@ package ggn import ( "context" "encoding/json" - "errors" "fmt" "io/ioutil" "net/http" @@ -12,6 +11,7 @@ import ( "time" "github.com/autobrr/autobrr/internal/domain" + "github.com/autobrr/autobrr/pkg/errors" "golang.org/x/time/rate" ) @@ -147,11 +147,11 @@ func (c *client) Do(req *http.Request) (*http.Response, error) { ctx := context.Background() err := c.Ratelimiter.Wait(ctx) // This is a blocking call. Honors the rate limit if err != nil { - return nil, err + return nil, errors.Wrap(err, "error waiting for ratelimiter") } resp, err := c.client.Do(req) if err != nil { - return nil, err + return nil, errors.Wrap(err, "error making request") } return resp, nil } @@ -159,7 +159,7 @@ func (c *client) Do(req *http.Request) (*http.Response, error) { func (c *client) get(url string) (*http.Response, error) { req, err := http.NewRequest(http.MethodGet, url, http.NoBody) if err != nil { - return nil, errors.New(fmt.Sprintf("ggn client request error : %v", url)) + return nil, errors.Wrap(err, "ggn client request error : %v", url) } req.Header.Add("X-API-Key", c.APIKey) @@ -167,7 +167,7 @@ func (c *client) get(url string) (*http.Response, error) { res, err := c.Do(req) if err != nil { - return nil, errors.New(fmt.Sprintf("ggn client request error : %v", url)) + return nil, errors.Wrap(err, "ggn client request error : %v", url) } if res.StatusCode == http.StatusUnauthorized { @@ -183,7 +183,7 @@ func (c *client) get(url string) (*http.Response, error) { func (c *client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error) { if torrentID == "" { - return nil, fmt.Errorf("ggn client: must have torrentID") + return nil, errors.New("ggn client: must have torrentID") } var r Response @@ -192,27 +192,27 @@ func (c *client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error) v.Add("id", torrentID) params := v.Encode() - url := fmt.Sprintf("%v?%v&%v", c.Url, "request=torrent", params) + reqUrl := fmt.Sprintf("%v?%v&%v", c.Url, "request=torrent", params) - resp, err := c.get(url) + resp, err := c.get(reqUrl) if err != nil { - return nil, err + return nil, errors.Wrap(err, "error getting data") } defer resp.Body.Close() body, readErr := ioutil.ReadAll(resp.Body) if readErr != nil { - return nil, readErr + return nil, errors.Wrap(readErr, "error reading body") } err = json.Unmarshal(body, &r) if err != nil { - return nil, err + return nil, errors.Wrap(err, "error unmarshal body") } if r.Status != "success" { - return nil, fmt.Errorf("bad status: %v", r.Status) + return nil, errors.New("bad status: %v", r.Status) } t := &domain.TorrentBasic{ @@ -229,7 +229,7 @@ func (c *client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error) func (c *client) TestAPI() (bool, error) { resp, err := c.get(c.Url) if err != nil { - return false, err + return false, errors.Wrap(err, "error getting data") } defer resp.Body.Close() diff --git a/pkg/jsonrpc/jsonrpc.go b/pkg/jsonrpc/jsonrpc.go index f0c67f2..fd8d19b 100644 --- a/pkg/jsonrpc/jsonrpc.go +++ b/pkg/jsonrpc/jsonrpc.go @@ -7,6 +7,8 @@ import ( "net/http" "reflect" "strconv" + + "github.com/autobrr/autobrr/pkg/errors" ) type Client interface { @@ -110,12 +112,12 @@ func (c *rpcClient) Call(method string, params ...interface{}) (*RPCResponse, er func (c *rpcClient) newRequest(req interface{}) (*http.Request, error) { body, err := json.Marshal(req) if err != nil { - return nil, err + return nil, errors.Wrap(err, "could not marshal request") } request, err := http.NewRequest("POST", c.endpoint, bytes.NewReader(body)) if err != nil { - return nil, err + return nil, errors.Wrap(err, "error creating request") } request.Header.Set("Content-Type", "application/json") request.Header.Set("Accept", "application/json") @@ -131,12 +133,12 @@ func (c *rpcClient) doCall(request RPCRequest) (*RPCResponse, error) { httpRequest, err := c.newRequest(request) if err != nil { - return nil, err + return nil, errors.Wrap(err, "could not create rpc http request") } httpResponse, err := c.httpClient.Do(httpRequest) if err != nil { - return nil, err + return nil, errors.Wrap(err, "error during rpc http request") } defer httpResponse.Body.Close() @@ -149,7 +151,7 @@ func (c *rpcClient) doCall(request RPCRequest) (*RPCResponse, error) { if err != nil { if httpResponse.StatusCode >= 400 { - return nil, fmt.Errorf("rpc call %v() on %v status code: %v. Could not decode body to rpc response: %v", request.Method, httpRequest.URL.String(), httpResponse.StatusCode, err.Error()) + return nil, errors.Wrap(err, fmt.Sprintf("rpc call %v() on %v status code: %v. Could not decode body to rpc response", request.Method, httpRequest.URL.String(), httpResponse.StatusCode)) } // if res.StatusCode == http.StatusUnauthorized { // return nil, errors.New("unauthorized: bad credentials") @@ -167,7 +169,7 @@ func (c *rpcClient) doCall(request RPCRequest) (*RPCResponse, error) { } if rpcResponse == nil { - return nil, fmt.Errorf("rpc call %v() on %v status code: %v. rpc response missing", request.Method, httpRequest.URL.String(), httpResponse.StatusCode) + return nil, errors.New("rpc call %v() on %v status code: %v. rpc response missing", request.Method, httpRequest.URL.String(), httpResponse.StatusCode) } return rpcResponse, nil @@ -220,12 +222,12 @@ func Params(params ...interface{}) interface{} { func (r *RPCResponse) GetObject(toType interface{}) error { js, err := json.Marshal(r.Result) if err != nil { - return err + return errors.Wrap(err, "could not marshal object") } err = json.Unmarshal(js, toType) if err != nil { - return err + return errors.Wrap(err, "could not unmarshal object") } return nil diff --git a/pkg/lidarr/client.go b/pkg/lidarr/client.go index a9b2b7e..7e5b98c 100644 --- a/pkg/lidarr/client.go +++ b/pkg/lidarr/client.go @@ -3,12 +3,12 @@ package lidarr import ( "bytes" "encoding/json" - "errors" - "fmt" "io" "net/http" "net/url" "path" + + "github.com/autobrr/autobrr/pkg/errors" ) func (c *client) get(endpoint string) (int, []byte, error) { @@ -18,7 +18,7 @@ func (c *client) get(endpoint string) (int, []byte, error) { req, err := http.NewRequest(http.MethodGet, reqUrl, http.NoBody) if err != nil { - return 0, nil, errors.New(fmt.Sprintf("lidarr client request error : %v", reqUrl)) + return 0, nil, errors.Wrap(err, "lidarr client request error : %v", reqUrl) } if c.config.BasicAuth { @@ -29,14 +29,14 @@ func (c *client) get(endpoint string) (int, []byte, error) { resp, err := c.http.Do(req) if err != nil { - return 0, nil, fmt.Errorf("lidarr.http.Do(req): %w", err) + return 0, nil, errors.Wrap(err, "lidarr.http.Do(req)") } defer resp.Body.Close() var buf bytes.Buffer if _, err = io.Copy(&buf, resp.Body); err != nil { - return resp.StatusCode, nil, fmt.Errorf("lidarr.io.Copy: %w", err) + return resp.StatusCode, nil, errors.Wrap(err, "lidarr.io.Copy error") } return resp.StatusCode, buf.Bytes(), nil @@ -49,12 +49,12 @@ func (c *client) post(endpoint string, data interface{}) (*http.Response, error) jsonData, err := json.Marshal(data) if err != nil { - return nil, fmt.Errorf("lidarr client could not marshal data: %v", reqUrl) + return nil, errors.Wrap(err, "lidarr client could not marshal data: %v", reqUrl) } req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData)) if err != nil { - return nil, fmt.Errorf("lidarr client request error: %v", reqUrl) + return nil, errors.Wrap(err, "lidarr client request error: %v", reqUrl) } if c.config.BasicAuth { @@ -67,7 +67,7 @@ func (c *client) post(endpoint string, data interface{}) (*http.Response, error) res, err := c.http.Do(req) if err != nil { - return nil, fmt.Errorf("lidarr client request error: %v", reqUrl) + return nil, errors.Wrap(err, "lidarr client request error: %v", reqUrl) } // validate response @@ -88,12 +88,12 @@ func (c *client) postBody(endpoint string, data interface{}) (int, []byte, error jsonData, err := json.Marshal(data) if err != nil { - return 0, nil, fmt.Errorf("lidarr client could not marshal data: %v", reqUrl) + return 0, nil, errors.Wrap(err, "lidarr client could not marshal data: %v", reqUrl) } req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData)) if err != nil { - return 0, nil, fmt.Errorf("lidarr client request error: %v", reqUrl) + return 0, nil, errors.Wrap(err, "lidarr client request error: %v", reqUrl) } if c.config.BasicAuth { @@ -104,18 +104,20 @@ func (c *client) postBody(endpoint string, data interface{}) (int, []byte, error resp, err := c.http.Do(req) if err != nil { - return 0, nil, fmt.Errorf("lidarr.http.Do(req): %w", err) + return 0, nil, errors.Wrap(err, "lidarr.http.Do(req)") } defer resp.Body.Close() var buf bytes.Buffer if _, err = io.Copy(&buf, resp.Body); err != nil { - return resp.StatusCode, nil, fmt.Errorf("lidarr.io.Copy: %w", err) + return resp.StatusCode, nil, errors.Wrap(err, "lidarr.io.Copy") } - if resp.StatusCode < 200 || resp.StatusCode > 299 { - return resp.StatusCode, buf.Bytes(), fmt.Errorf("lidarr: bad request: %v (status: %s): %s", resp.Request.RequestURI, resp.Status, buf.String()) + if resp.StatusCode == http.StatusBadRequest { + return resp.StatusCode, buf.Bytes(), nil + } else if resp.StatusCode < 200 || resp.StatusCode > 401 { + return resp.StatusCode, buf.Bytes(), errors.New("lidarr: bad request: %v (status: %s): %s", resp.Request.RequestURI, resp.Status, buf.String()) } return resp.StatusCode, buf.Bytes(), nil diff --git a/pkg/lidarr/lidarr.go b/pkg/lidarr/lidarr.go index 6f36e62..5cac53b 100644 --- a/pkg/lidarr/lidarr.go +++ b/pkg/lidarr/lidarr.go @@ -2,11 +2,14 @@ package lidarr import ( "encoding/json" - "errors" "fmt" + "io" + "log" "net/http" "strings" "time" + + "github.com/autobrr/autobrr/pkg/errors" ) type Config struct { @@ -17,6 +20,8 @@ type Config struct { BasicAuth bool Username string Password string + + Log *log.Logger } type Client interface { @@ -27,6 +32,8 @@ type Client interface { type client struct { config Config http *http.Client + + Log *log.Logger } // New create new lidarr client @@ -39,6 +46,11 @@ func New(config Config) Client { c := &client{ config: config, http: httpClient, + Log: config.Log, + } + + if config.Log == nil { + c.Log = log.New(io.Discard, "", log.LstdFlags) } return c @@ -61,6 +73,13 @@ type PushResponse struct { Rejections []string `json:"rejections"` } +type BadRequestResponse struct { + PropertyName string `json:"propertyName"` + ErrorMessage string `json:"errorMessage"` + AttemptedValue string `json:"attemptedValue"` + Severity string `json:"severity"` +} + type SystemStatusResponse struct { Version string `json:"version"` } @@ -68,43 +87,56 @@ type SystemStatusResponse struct { func (c *client) Test() (*SystemStatusResponse, error) { status, res, err := c.get("system/status") if err != nil { - return nil, fmt.Errorf("lidarr client get error: %w", err) + return nil, errors.Wrap(err, "lidarr client get error") } if status == http.StatusUnauthorized { return nil, errors.New("unauthorized: bad credentials") } - //log.Trace().Msgf("lidarr system/status response status: %v body: %v", status, string(res)) + c.Log.Printf("lidarr system/status response status: %v body: %v", status, string(res)) response := SystemStatusResponse{} err = json.Unmarshal(res, &response) if err != nil { - return nil, fmt.Errorf("lidarr client error json unmarshal: %w", err) + return nil, errors.Wrap(err, "lidarr client error json unmarshal") } return &response, nil } func (c *client) Push(release Release) ([]string, error) { - _, res, err := c.postBody("release/push", release) + status, res, err := c.postBody("release/push", release) if err != nil { - return nil, fmt.Errorf("lidarr client post error: %w", err) + return nil, errors.Wrap(err, "lidarr client post error") } - //log.Trace().Msgf("lidarr release/push response status: %v body: %v", status, string(res)) + c.Log.Printf("lidarr release/push response status: %v body: %v", status, string(res)) + + if status == http.StatusBadRequest { + badreqResponse := make([]*BadRequestResponse, 0) + err = json.Unmarshal(res, &badreqResponse) + if err != nil { + return nil, errors.Wrap(err, "could not unmarshal data") + } + + if badreqResponse[0] != nil && badreqResponse[0].PropertyName == "Title" && badreqResponse[0].ErrorMessage == "Unable to parse" { + rejections := []string{fmt.Sprintf("unable to parse: %v", badreqResponse[0].AttemptedValue)} + return rejections, err + } + } pushResponse := PushResponse{} err = json.Unmarshal(res, &pushResponse) if err != nil { - return nil, fmt.Errorf("lidarr client error json unmarshal: %w", err) + return nil, errors.Wrap(err, "lidarr client error json unmarshal") } // log and return if rejected if pushResponse.Rejected { rejections := strings.Join(pushResponse.Rejections, ", ") - return pushResponse.Rejections, fmt.Errorf("lidarr push rejected: %s - reasons: %q: err %w", release.Title, rejections, err) + return pushResponse.Rejections, errors.New("lidarr push rejected: %s - reasons: %q", release.Title, rejections) } return nil, nil diff --git a/pkg/lidarr/lidarr_test.go b/pkg/lidarr/lidarr_test.go index 7d691e4..22daa39 100644 --- a/pkg/lidarr/lidarr_test.go +++ b/pkg/lidarr/lidarr_test.go @@ -1,7 +1,6 @@ package lidarr import ( - "errors" "io/ioutil" "net/http" "net/http/httptest" @@ -134,11 +133,11 @@ func Test_client_Test(t *testing.T) { defer srv.Close() tests := []struct { - name string - cfg Config - want *SystemStatusResponse - err error - wantErr bool + name string + cfg Config + want *SystemStatusResponse + expectedErr string + wantErr bool }{ { name: "fetch", @@ -149,9 +148,9 @@ func Test_client_Test(t *testing.T) { Username: "", Password: "", }, - want: &SystemStatusResponse{Version: "0.8.1.2135"}, - err: nil, - wantErr: false, + want: &SystemStatusResponse{Version: "0.8.1.2135"}, + expectedErr: "", + wantErr: false, }, { name: "fetch_unauthorized", @@ -162,9 +161,9 @@ func Test_client_Test(t *testing.T) { Username: "", Password: "", }, - want: nil, - wantErr: true, - err: errors.New("unauthorized: bad credentials"), + want: nil, + wantErr: true, + expectedErr: "unauthorized: bad credentials", }, } for _, tt := range tests { @@ -173,7 +172,7 @@ func Test_client_Test(t *testing.T) { got, err := c.Test() if tt.wantErr && assert.Error(t, err) { - assert.Equal(t, tt.err, err) + assert.EqualErrorf(t, err, tt.expectedErr, "Error should be: %v, got: %v", tt.wantErr, err) } assert.Equal(t, tt.want, got) diff --git a/pkg/ptp/ptp.go b/pkg/ptp/ptp.go index ad8f986..5655fec 100644 --- a/pkg/ptp/ptp.go +++ b/pkg/ptp/ptp.go @@ -3,7 +3,6 @@ package ptp import ( "context" "encoding/json" - "errors" "fmt" "io/ioutil" "net/http" @@ -11,6 +10,7 @@ import ( "time" "github.com/autobrr/autobrr/internal/domain" + "github.com/autobrr/autobrr/pkg/errors" "golang.org/x/time/rate" ) @@ -87,11 +87,11 @@ func (c *Client) Do(req *http.Request) (*http.Response, error) { ctx := context.Background() err := c.Ratelimiter.Wait(ctx) // This is a blocking call. Honors the rate limit if err != nil { - return nil, err + return nil, errors.Wrap(err, "error waiting for ratelimiter") } resp, err := c.client.Do(req) if err != nil { - return nil, err + return nil, errors.Wrap(err, "error making request") } return resp, nil } @@ -99,7 +99,7 @@ func (c *Client) Do(req *http.Request) (*http.Response, error) { func (c *Client) get(url string) (*http.Response, error) { req, err := http.NewRequest(http.MethodGet, url, http.NoBody) if err != nil { - return nil, fmt.Errorf("ptp client request error : %v", url) + return nil, errors.Wrap(err, "ptp client request error : %v", url) } req.Header.Add("ApiUser", c.APIUser) @@ -108,7 +108,7 @@ func (c *Client) get(url string) (*http.Response, error) { res, err := c.Do(req) if err != nil { - return nil, fmt.Errorf("ptp client request error : %v", url) + return nil, errors.Wrap(err, "ptp client request error : %v", url) } if res.StatusCode == http.StatusUnauthorized { @@ -124,7 +124,7 @@ func (c *Client) get(url string) (*http.Response, error) { func (c *Client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error) { if torrentID == "" { - return nil, fmt.Errorf("ptp client: must have torrentID") + return nil, errors.New("ptp client: must have torrentID") } var r TorrentResponse @@ -133,23 +133,23 @@ func (c *Client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error) v.Add("torrentid", torrentID) params := v.Encode() - url := fmt.Sprintf("%v?%v", c.Url, params) + reqUrl := fmt.Sprintf("%v?%v", c.Url, params) - resp, err := c.get(url) + resp, err := c.get(reqUrl) if err != nil { - return nil, err + return nil, errors.Wrap(err, "error requesting data") } defer resp.Body.Close() body, readErr := ioutil.ReadAll(resp.Body) if readErr != nil { - return nil, readErr + return nil, errors.Wrap(readErr, "could not read body") } err = json.Unmarshal(body, &r) if err != nil { - return nil, err + return nil, errors.Wrap(readErr, "could not unmarshal body") } for _, torrent := range r.Torrents { @@ -169,7 +169,7 @@ func (c *Client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error) func (c *Client) TestAPI() (bool, error) { resp, err := c.get(c.Url) if err != nil { - return false, err + return false, errors.Wrap(err, "error requesting data") } defer resp.Body.Close() diff --git a/pkg/qbittorrent/client.go b/pkg/qbittorrent/client.go index 220a137..ee4b44c 100644 --- a/pkg/qbittorrent/client.go +++ b/pkg/qbittorrent/client.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "fmt" "io" + "log" "mime/multipart" "net/http" "net/http/cookiejar" @@ -14,8 +15,9 @@ import ( "strings" "time" - "github.com/rs/zerolog/log" - "golang.org/x/net/publicsuffix" + "github.com/autobrr/autobrr/pkg/errors" + + publicsuffix "golang.org/x/net/publicsuffix" ) var ( @@ -31,6 +33,8 @@ type Client struct { Name string settings Settings http *http.Client + + Log *log.Logger } type Settings struct { @@ -43,6 +47,7 @@ type Settings struct { protocol string BasicAuth bool Basic Basic + Log *log.Logger } type Basic struct { @@ -51,20 +56,24 @@ type Basic struct { } func NewClient(s Settings) *Client { - jarOptions := &cookiejar.Options{PublicSuffixList: publicsuffix.List} - //store cookies in jar - jar, err := cookiejar.New(jarOptions) - if err != nil { - log.Error().Err(err).Msg("new client cookie error") - } - httpClient := &http.Client{ - Timeout: timeout, - Jar: jar, - } - c := &Client{ settings: s, - http: httpClient, + } + + if s.Log == nil { + c.Log = log.New(io.Discard, "qbittorrent", log.LstdFlags) + } + + //store cookies in jar + jarOptions := &cookiejar.Options{PublicSuffixList: publicsuffix.List} + jar, err := cookiejar.New(jarOptions) + if err != nil { + c.Log.Println("new client cookie error") + } + + c.http = &http.Client{ + Timeout: timeout, + Jar: jar, } c.settings.protocol = "http" @@ -92,8 +101,7 @@ func (c *Client) get(endpoint string, opts map[string]string) (*http.Response, e req, err := http.NewRequest("GET", reqUrl, nil) if err != nil { - log.Error().Err(err).Msgf("GET: error %v", reqUrl) - return nil, err + return nil, errors.Wrap(err, "could not build request") } if c.settings.BasicAuth { @@ -109,14 +117,13 @@ func (c *Client) get(endpoint string, opts map[string]string) (*http.Response, e break } - log.Debug().Msgf("qbit GET failed: retrying attempt %d - %v", i, reqUrl) + c.Log.Printf("qbit GET failed: retrying attempt %d - %v\n", i, reqUrl) time.Sleep(backoff) } if err != nil { - log.Error().Err(err).Msgf("GET: do %v", reqUrl) - return nil, err + return nil, errors.Wrap(err, "error making get request: %v", reqUrl) } return resp, nil @@ -138,8 +145,7 @@ func (c *Client) post(endpoint string, opts map[string]string) (*http.Response, req, err := http.NewRequest("POST", reqUrl, strings.NewReader(form.Encode())) if err != nil { - log.Error().Err(err).Msgf("POST: req %v", reqUrl) - return nil, err + return nil, errors.Wrap(err, "could not build request") } if c.settings.BasicAuth { @@ -158,14 +164,13 @@ func (c *Client) post(endpoint string, opts map[string]string) (*http.Response, break } - log.Debug().Msgf("qbit POST failed: retrying attempt %d - %v", i, reqUrl) + c.Log.Printf("qbit POST failed: retrying attempt %d - %v\n", i, reqUrl) time.Sleep(backoff) } if err != nil { - log.Error().Err(err).Msgf("POST: do %v", reqUrl) - return nil, err + return nil, errors.Wrap(err, "error making post request: %v", reqUrl) } return resp, nil @@ -187,8 +192,7 @@ func (c *Client) postBasic(endpoint string, opts map[string]string) (*http.Respo req, err := http.NewRequest("POST", reqUrl, strings.NewReader(form.Encode())) if err != nil { - log.Error().Err(err).Msgf("POST: req %v", reqUrl) - return nil, err + return nil, errors.Wrap(err, "could not build request") } if c.settings.BasicAuth { @@ -200,8 +204,7 @@ func (c *Client) postBasic(endpoint string, opts map[string]string) (*http.Respo resp, err = c.http.Do(req) if err != nil { - log.Error().Err(err).Msgf("POST: do %v", reqUrl) - return nil, err + return nil, errors.Wrap(err, "error making post request: %v", reqUrl) } return resp, nil @@ -213,8 +216,7 @@ func (c *Client) postFile(endpoint string, fileName string, opts map[string]stri file, err := os.Open(fileName) if err != nil { - log.Error().Err(err).Msgf("POST file: opening file %v", fileName) - return nil, err + return nil, errors.Wrap(err, "error opening file %v", fileName) } // Close the file later defer file.Close() @@ -228,15 +230,13 @@ func (c *Client) postFile(endpoint string, fileName string, opts map[string]stri // Initialize file field fileWriter, err := multiPartWriter.CreateFormFile("torrents", fileName) if err != nil { - log.Error().Err(err).Msgf("POST file: initializing file field %v", fileName) - return nil, err + return nil, errors.Wrap(err, "error initializing file field %v", fileName) } // Copy the actual file content to the fields writer _, err = io.Copy(fileWriter, file) if err != nil { - log.Error().Err(err).Msgf("POST file: could not copy file to writer %v", fileName) - return nil, err + return nil, errors.Wrap(err, "error copy file contents to writer %v", fileName) } // Populate other fields @@ -244,14 +244,12 @@ func (c *Client) postFile(endpoint string, fileName string, opts map[string]stri for key, val := range opts { fieldWriter, err := multiPartWriter.CreateFormField(key) if err != nil { - log.Error().Err(err).Msgf("POST file: could not add other fields %v", fileName) - return nil, err + return nil, errors.Wrap(err, "error creating form field %v with value %v", key, val) } _, err = fieldWriter.Write([]byte(val)) if err != nil { - log.Error().Err(err).Msgf("POST file: could not write field %v", fileName) - return nil, err + return nil, errors.Wrap(err, "error writing field %v with value %v", key, val) } } } @@ -262,8 +260,7 @@ func (c *Client) postFile(endpoint string, fileName string, opts map[string]stri reqUrl := buildUrl(c.settings, endpoint) req, err := http.NewRequest("POST", reqUrl, &requestBody) if err != nil { - log.Error().Err(err).Msgf("POST file: could not create request object %v", fileName) - return nil, err + return nil, errors.Wrap(err, "error creating request %v", fileName) } if c.settings.BasicAuth { @@ -282,14 +279,13 @@ func (c *Client) postFile(endpoint string, fileName string, opts map[string]stri break } - log.Debug().Msgf("qbit POST file failed: retrying attempt %d - %v", i, reqUrl) + c.Log.Printf("qbit POST file failed: retrying attempt %d - %v\n", i, reqUrl) time.Sleep(backoff) } if err != nil { - log.Error().Err(err).Msgf("POST file: could not perform request %v", fileName) - return nil, err + return nil, errors.Wrap(err, "error making post file request %v", fileName) } return resp, nil diff --git a/pkg/qbittorrent/methods.go b/pkg/qbittorrent/methods.go index 081fbad..2893d4a 100644 --- a/pkg/qbittorrent/methods.go +++ b/pkg/qbittorrent/methods.go @@ -2,14 +2,13 @@ package qbittorrent import ( "encoding/json" - "errors" "io/ioutil" "net/http" "net/http/httputil" "strconv" "strings" - "github.com/rs/zerolog/log" + "github.com/autobrr/autobrr/pkg/errors" ) // Login https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#authentication @@ -21,15 +20,12 @@ func (c *Client) Login() error { resp, err := c.postBasic("auth/login", opts) if err != nil { - log.Error().Err(err).Msg("login error") - return err + return errors.Wrap(err, "login error") } else if resp.StatusCode == http.StatusForbidden { - log.Error().Err(err).Msg("User's IP is banned for too many failed login attempts") - return err + return errors.New("User's IP is banned for too many failed login attempts") } else if resp.StatusCode != http.StatusOK { // check for correct status code - log.Error().Err(err).Msgf("login bad status %v error", resp.StatusCode) - return errors.New("qbittorrent login bad status") + return errors.New("qbittorrent login bad status %v", resp.StatusCode) } defer resp.Body.Close() @@ -61,23 +57,20 @@ func (c *Client) GetTorrents() ([]Torrent, error) { resp, err := c.get("torrents/info", nil) if err != nil { - log.Error().Err(err).Msg("get torrents error") - return nil, err + return nil, errors.Wrap(err, "get torrents error") } defer resp.Body.Close() body, readErr := ioutil.ReadAll(resp.Body) if readErr != nil { - log.Error().Err(err).Msg("get torrents read error") - return nil, readErr + return nil, errors.Wrap(readErr, "could not read body") } var torrents []Torrent err = json.Unmarshal(body, &torrents) if err != nil { - log.Error().Err(err).Msg("get torrents unmarshal error") - return nil, err + return nil, errors.Wrap(err, "could not unmarshal body") } return torrents, nil @@ -90,23 +83,20 @@ func (c *Client) GetTorrentsFilter(filter TorrentFilter) ([]Torrent, error) { resp, err := c.get("torrents/info", opts) if err != nil { - log.Error().Err(err).Msgf("get filtered torrents error: %v", filter) - return nil, err + return nil, errors.Wrap(err, "could not get filtered torrents with filter: %v", filter) } defer resp.Body.Close() body, readErr := ioutil.ReadAll(resp.Body) if readErr != nil { - log.Error().Err(err).Msgf("get filtered torrents read error: %v", filter) - return nil, readErr + return nil, errors.Wrap(readErr, "could not read body") } var torrents []Torrent err = json.Unmarshal(body, &torrents) if err != nil { - log.Error().Err(err).Msgf("get filtered torrents unmarshal error: %v", filter) - return nil, err + return nil, errors.Wrap(err, "could not unmarshal body") } return torrents, nil @@ -121,23 +111,20 @@ func (c *Client) GetTorrentsActiveDownloads() ([]Torrent, error) { resp, err := c.get("torrents/info", opts) if err != nil { - log.Error().Err(err).Msgf("get filtered torrents error: %v", filter) - return nil, err + return nil, errors.Wrap(err, "could not get active torrents") } defer resp.Body.Close() body, readErr := ioutil.ReadAll(resp.Body) if readErr != nil { - log.Error().Err(err).Msgf("get filtered torrents read error: %v", filter) - return nil, readErr + return nil, errors.Wrap(readErr, "could not read body") } var torrents []Torrent err = json.Unmarshal(body, &torrents) if err != nil { - log.Error().Err(err).Msgf("get filtered torrents unmarshal error: %v", filter) - return nil, err + return nil, errors.Wrap(readErr, "could not unmarshal body") } res := make([]Torrent, 0) @@ -155,13 +142,15 @@ func (c *Client) GetTorrentsActiveDownloads() ([]Torrent, error) { func (c *Client) GetTorrentsRaw() (string, error) { resp, err := c.get("torrents/info", nil) if err != nil { - log.Error().Err(err).Msg("get torrent trackers raw error") - return "", err + return "", errors.Wrap(err, "could not get torrents raw") } defer resp.Body.Close() - data, _ := ioutil.ReadAll(resp.Body) + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", errors.Wrap(err, "could not get read body torrents raw") + } return string(data), nil } @@ -173,40 +162,35 @@ func (c *Client) GetTorrentTrackers(hash string) ([]TorrentTracker, error) { resp, err := c.get("torrents/trackers", opts) if err != nil { - log.Error().Err(err).Msgf("get torrent trackers error: %v", hash) - return nil, err + return nil, errors.Wrap(err, "could not get torrent trackers for hash: %v", hash) } defer resp.Body.Close() dump, err := httputil.DumpResponse(resp, true) if err != nil { - log.Error().Err(err).Msgf("get torrent trackers dump response error: %v", err) + c.Log.Printf("get torrent trackers error dump response: %v\n", string(dump)) } - log.Trace().Msgf("get torrent trackers response dump: %v", string(dump)) + c.Log.Printf("get torrent trackers response dump: %v\n", string(dump)) if resp.StatusCode == http.StatusNotFound { - //return nil, fmt.Errorf("torrent not found: %v", hash) return nil, nil } else if resp.StatusCode == http.StatusForbidden { - //return nil, fmt.Errorf("torrent not found: %v", hash) return nil, nil } body, readErr := ioutil.ReadAll(resp.Body) if readErr != nil { - log.Error().Err(err).Msgf("get torrent trackers read error: %v", hash) - return nil, readErr + return nil, errors.Wrap(err, "could not read body") } - log.Trace().Msgf("get torrent trackers body: %v", string(body)) + c.Log.Printf("get torrent trackers body: %v\n", string(body)) var trackers []TorrentTracker err = json.Unmarshal(body, &trackers) if err != nil { - log.Error().Err(err).Msgf("get torrent trackers: %v", hash) - return nil, err + return nil, errors.Wrap(err, "could not unmarshal body") } return trackers, nil @@ -217,11 +201,9 @@ func (c *Client) AddTorrentFromFile(file string, options map[string]string) erro res, err := c.postFile("torrents/add", file, options) if err != nil { - log.Error().Err(err).Msgf("add torrents error: %v", file) - return err + return errors.Wrap(err, "could not add torrent %v", file) } else if res.StatusCode != http.StatusOK { - log.Error().Err(err).Msgf("add torrents bad status: %v", file) - return err + return errors.Wrap(err, "could not add torrent %v unexpected status: %v", file, res.StatusCode) } defer res.Body.Close() @@ -240,11 +222,9 @@ func (c *Client) DeleteTorrents(hashes []string, deleteFiles bool) error { resp, err := c.get("torrents/delete", opts) if err != nil { - log.Error().Err(err).Msgf("delete torrents error: %v", hashes) - return err + return errors.Wrap(err, "could not delete torrents: %+v", hashes) } else if resp.StatusCode != http.StatusOK { - log.Error().Err(err).Msgf("delete torrents bad code: %v", hashes) - return err + return errors.Wrap(err, "could not delete torrents %v unexpected status: %v", hashes, resp.StatusCode) } defer resp.Body.Close() @@ -261,11 +241,9 @@ func (c *Client) ReAnnounceTorrents(hashes []string) error { resp, err := c.get("torrents/reannounce", opts) if err != nil { - log.Error().Err(err).Msgf("re-announce error: %v", hashes) - return err + return errors.Wrap(err, "could not re-announce torrents: %v", hashes) } else if resp.StatusCode != http.StatusOK { - log.Error().Err(err).Msgf("re-announce error bad status: %v", hashes) - return err + return errors.Wrap(err, "could not re-announce torrents: %v unexpected status: %v", hashes, resp.StatusCode) } defer resp.Body.Close() @@ -276,23 +254,20 @@ func (c *Client) ReAnnounceTorrents(hashes []string) error { func (c *Client) GetTransferInfo() (*TransferInfo, error) { resp, err := c.get("transfer/info", nil) if err != nil { - log.Error().Err(err).Msg("get torrents error") - return nil, err + return nil, errors.Wrap(err, "could not get transfer info") } defer resp.Body.Close() body, readErr := ioutil.ReadAll(resp.Body) if readErr != nil { - log.Error().Err(err).Msg("get torrents read error") - return nil, readErr + return nil, errors.Wrap(readErr, "could not read body") } var info TransferInfo err = json.Unmarshal(body, &info) if err != nil { - log.Error().Err(err).Msg("get torrents unmarshal error") - return nil, err + return nil, errors.Wrap(readErr, "could not unmarshal body") } return &info, nil diff --git a/pkg/radarr/client.go b/pkg/radarr/client.go index 6726514..cdc503c 100644 --- a/pkg/radarr/client.go +++ b/pkg/radarr/client.go @@ -3,14 +3,12 @@ package radarr import ( "bytes" "encoding/json" - "errors" - "fmt" "io" "net/http" "net/url" "path" - "github.com/rs/zerolog/log" + "github.com/autobrr/autobrr/pkg/errors" ) func (c *client) get(endpoint string) (int, []byte, error) { @@ -20,8 +18,7 @@ func (c *client) get(endpoint string) (int, []byte, error) { req, err := http.NewRequest(http.MethodGet, reqUrl, http.NoBody) if err != nil { - log.Error().Err(err).Msgf("radarr client request error : %v", reqUrl) - return 0, nil, err + return 0, nil, errors.Wrap(err, "could not build request: %v", reqUrl) } if c.config.BasicAuth { @@ -32,15 +29,14 @@ func (c *client) get(endpoint string) (int, []byte, error) { resp, err := c.http.Do(req) if err != nil { - log.Error().Err(err).Msgf("radarr client.get request error: %v", reqUrl) - return 0, nil, fmt.Errorf("radarr.http.Do(req): %w", err) + return 0, nil, errors.Wrap(err, "radarr.http.Do(req): %v", reqUrl) } defer resp.Body.Close() var buf bytes.Buffer if _, err = io.Copy(&buf, resp.Body); err != nil { - return resp.StatusCode, nil, fmt.Errorf("radarr.io.Copy: %w", err) + return resp.StatusCode, nil, errors.Wrap(err, "radarr.io.Copy") } return resp.StatusCode, buf.Bytes(), nil @@ -53,14 +49,12 @@ func (c *client) post(endpoint string, data interface{}) (*http.Response, error) jsonData, err := json.Marshal(data) if err != nil { - log.Error().Err(err).Msgf("radarr client could not marshal data: %v", reqUrl) - return nil, err + return nil, errors.Wrap(err, "could not marshal data: %+v", data) } req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData)) if err != nil { - log.Error().Err(err).Msgf("radarr client request error: %v", reqUrl) - return nil, err + return nil, errors.Wrap(err, "could not build request: %v", reqUrl) } if c.config.BasicAuth { @@ -73,19 +67,15 @@ func (c *client) post(endpoint string, data interface{}) (*http.Response, error) res, err := c.http.Do(req) if err != nil { - log.Error().Err(err).Msgf("radarr client request error: %v", reqUrl) - return nil, err + return nil, errors.Wrap(err, "could not make request: %+v", req) } // validate response if res.StatusCode == http.StatusUnauthorized { - log.Error().Err(err).Msgf("radarr client bad request: %v", reqUrl) return nil, errors.New("unauthorized: bad credentials") } else if res.StatusCode == http.StatusBadRequest { - log.Error().Err(err).Msgf("radarr client request error: %v", reqUrl) return nil, errors.New("radarr: bad request") } else if res.StatusCode != http.StatusOK { - log.Error().Err(err).Msgf("radarr client request error: %v", reqUrl) return nil, errors.New("radarr: bad request") } @@ -100,14 +90,12 @@ func (c *client) postBody(endpoint string, data interface{}) (int, []byte, error jsonData, err := json.Marshal(data) if err != nil { - log.Error().Err(err).Msgf("radarr client could not marshal data: %v", reqUrl) - return 0, nil, err + return 0, nil, errors.Wrap(err, "could not marshal data: %+v", data) } req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData)) if err != nil { - log.Error().Err(err).Msgf("radarr client request error: %v", reqUrl) - return 0, nil, err + return 0, nil, errors.Wrap(err, "could not build request: %v", reqUrl) } if c.config.BasicAuth { @@ -118,19 +106,20 @@ func (c *client) postBody(endpoint string, data interface{}) (int, []byte, error resp, err := c.http.Do(req) if err != nil { - log.Error().Err(err).Msgf("radarr client request error: %v", reqUrl) - return 0, nil, fmt.Errorf("radarr.http.Do(req): %w", err) + return 0, nil, errors.Wrap(err, "radarr.http.Do(req): %+v", req) } defer resp.Body.Close() var buf bytes.Buffer if _, err = io.Copy(&buf, resp.Body); err != nil { - return resp.StatusCode, nil, fmt.Errorf("radarr.io.Copy: %w", err) + return resp.StatusCode, nil, errors.Wrap(err, "radarr.io.Copy") } - if resp.StatusCode < 200 || resp.StatusCode > 299 { - return resp.StatusCode, buf.Bytes(), fmt.Errorf("radarr: bad request: %v (status: %s): %s", resp.Request.RequestURI, resp.Status, buf.String()) + if resp.StatusCode == http.StatusBadRequest { + return resp.StatusCode, buf.Bytes(), nil + } else if resp.StatusCode < 200 || resp.StatusCode > 401 { + return resp.StatusCode, buf.Bytes(), errors.New("radarr: bad request: %v (status: %s): %s", resp.Request.RequestURI, resp.Status, buf.String()) } return resp.StatusCode, buf.Bytes(), nil diff --git a/pkg/radarr/radarr.go b/pkg/radarr/radarr.go index 0c578e6..c3b860a 100644 --- a/pkg/radarr/radarr.go +++ b/pkg/radarr/radarr.go @@ -2,12 +2,14 @@ package radarr import ( "encoding/json" - "errors" + "fmt" + "io" + "log" "net/http" "strings" "time" - "github.com/rs/zerolog/log" + "github.com/autobrr/autobrr/pkg/errors" ) type Config struct { @@ -18,6 +20,8 @@ type Config struct { BasicAuth bool Username string Password string + + Log *log.Logger } type Client interface { @@ -28,6 +32,8 @@ type Client interface { type client struct { config Config http *http.Client + + Log *log.Logger } func New(config Config) Client { @@ -39,6 +45,11 @@ func New(config Config) Client { c := &client{ config: config, http: httpClient, + Log: config.Log, + } + + if config.Log == nil { + c.Log = log.New(io.Discard, "", log.LstdFlags) } return c @@ -65,11 +76,17 @@ type SystemStatusResponse struct { Version string `json:"version"` } +type BadRequestResponse struct { + PropertyName string `json:"propertyName"` + ErrorMessage string `json:"errorMessage"` + AttemptedValue string `json:"attemptedValue"` + Severity string `json:"severity"` +} + func (c *client) Test() (*SystemStatusResponse, error) { status, res, err := c.get("system/status") if err != nil { - log.Error().Stack().Err(err).Msg("radarr client get error") - return nil, err + return nil, errors.Wrap(err, "radarr error running test") } if status == http.StatusUnauthorized { @@ -79,11 +96,10 @@ func (c *client) Test() (*SystemStatusResponse, error) { response := SystemStatusResponse{} err = json.Unmarshal(res, &response) if err != nil { - log.Error().Stack().Err(err).Msg("radarr client error json unmarshal") - return nil, err + return nil, errors.Wrap(err, "could not unmarshal data") } - log.Trace().Msgf("radarr system/status response: %+v", response) + c.Log.Printf("radarr system/status status: (%v) response: %v\n", status, string(res)) return &response, nil } @@ -91,24 +107,35 @@ func (c *client) Test() (*SystemStatusResponse, error) { func (c *client) Push(release Release) ([]string, error) { status, res, err := c.postBody("release/push", release) if err != nil { - log.Error().Stack().Err(err).Msgf("radarr client post error. status: %d", status) - return nil, err + return nil, errors.Wrap(err, "error push release") + } + + c.Log.Printf("radarr release/push status: (%v) response: %v\n", status, string(res)) + + if status == http.StatusBadRequest { + badreqResponse := make([]*BadRequestResponse, 0) + err = json.Unmarshal(res, &badreqResponse) + if err != nil { + return nil, errors.Wrap(err, "could not unmarshal data") + } + + if badreqResponse[0] != nil && badreqResponse[0].PropertyName == "Title" && badreqResponse[0].ErrorMessage == "Unable to parse" { + rejections := []string{fmt.Sprintf("unable to parse: %v", badreqResponse[0].AttemptedValue)} + return rejections, nil + } } pushResponse := make([]PushResponse, 0) err = json.Unmarshal(res, &pushResponse) if err != nil { - log.Error().Stack().Err(err).Msg("radarr client error json unmarshal") - return nil, err + return nil, errors.Wrap(err, "could not unmarshal data") } - log.Trace().Msgf("radarr release/push response status: %v body: %+v", status, string(res)) - // log and return if rejected if pushResponse[0].Rejected { rejections := strings.Join(pushResponse[0].Rejections, ", ") - log.Trace().Msgf("radarr push rejected: %s - reasons: %q", release.Title, rejections) + c.Log.Printf("radarr release/push rejected %v reasons: %q\n", release.Title, rejections) return pushResponse[0].Rejections, nil } diff --git a/pkg/radarr/radarr_test.go b/pkg/radarr/radarr_test.go index 075dcae..239b30d 100644 --- a/pkg/radarr/radarr_test.go +++ b/pkg/radarr/radarr_test.go @@ -1,7 +1,6 @@ package radarr import ( - "errors" "io/ioutil" "net/http" "net/http/httptest" @@ -90,8 +89,6 @@ func Test_client_Push(t *testing.T) { PublishDate: "2021-08-21T15:36:00Z", }}, rejections: []string{"Could not find Some Old Movie"}, - //err: errors.New("radarr push rejected Could not find Some Old Movie"), - //wantErr: true, }, { name: "push_error", @@ -114,8 +111,6 @@ func Test_client_Push(t *testing.T) { PublishDate: "2021-08-21T15:36:00Z", }}, rejections: []string{"Could not find Some Old Movie"}, - //err: errors.New("radarr push rejected Could not find Some Old Movie"), - //wantErr: true, }, { name: "push_parse_error", @@ -137,8 +132,8 @@ func Test_client_Push(t *testing.T) { Protocol: "torrent", PublishDate: "2021-08-21T15:36:00Z", }}, - err: errors.New("radarr: bad request: (status: 400 Bad Request): [\n {\n \"propertyName\": \"Title\",\n \"errorMessage\": \"Unable to parse\",\n \"attemptedValue\": \"Minx 1 epi 9 2160p\",\n \"severity\": \"error\"\n }\n]\n"), - wantErr: true, + rejections: []string{"unable to parse: Minx 1 epi 9 2160p"}, + wantErr: false, }, } for _, tt := range tests { @@ -177,11 +172,11 @@ func Test_client_Test(t *testing.T) { defer srv.Close() tests := []struct { - name string - cfg Config - want *SystemStatusResponse - err error - wantErr bool + name string + cfg Config + want *SystemStatusResponse + expectedErr string + wantErr bool }{ { name: "fetch", @@ -192,9 +187,9 @@ func Test_client_Test(t *testing.T) { Username: "", Password: "", }, - want: &SystemStatusResponse{Version: "3.2.2.5080"}, - err: nil, - wantErr: false, + want: &SystemStatusResponse{Version: "3.2.2.5080"}, + expectedErr: "", + wantErr: false, }, { name: "fetch_unauthorized", @@ -205,9 +200,9 @@ func Test_client_Test(t *testing.T) { Username: "", Password: "", }, - want: nil, - wantErr: true, - err: errors.New("unauthorized: bad credentials"), + want: nil, + wantErr: true, + expectedErr: "unauthorized: bad credentials", }, { name: "fetch_subfolder", @@ -218,9 +213,9 @@ func Test_client_Test(t *testing.T) { Username: "", Password: "", }, - want: &SystemStatusResponse{Version: "3.2.2.5080"}, - err: nil, - wantErr: false, + want: &SystemStatusResponse{Version: "3.2.2.5080"}, + expectedErr: "", + wantErr: false, }, } for _, tt := range tests { @@ -229,7 +224,7 @@ func Test_client_Test(t *testing.T) { got, err := c.Test() if tt.wantErr && assert.Error(t, err) { - assert.Equal(t, tt.err, err) + assert.EqualErrorf(t, err, tt.expectedErr, "Error should be: %v, got: %v", tt.wantErr, err) } assert.Equal(t, tt.want, got) diff --git a/pkg/red/red.go b/pkg/red/red.go index 1c23172..d541668 100644 --- a/pkg/red/red.go +++ b/pkg/red/red.go @@ -3,18 +3,17 @@ package red import ( "context" "encoding/json" - "errors" "fmt" "io/ioutil" "net/http" "net/url" "strconv" "time" - - "github.com/rs/zerolog/log" - "golang.org/x/time/rate" "github.com/autobrr/autobrr/internal/domain" + "github.com/autobrr/autobrr/pkg/errors" + + "golang.org/x/time/rate" ) type REDClient interface { @@ -131,8 +130,7 @@ func (c *Client) Do(req *http.Request) (*http.Response, error) { func (c *Client) get(url string) (*http.Response, error) { req, err := http.NewRequest(http.MethodGet, url, http.NoBody) if err != nil { - log.Error().Err(err).Msgf("red client request error : %v", url) - return nil, err + return nil, errors.Wrap(err, "could not build request") } req.Header.Add("Authorization", c.APIKey) @@ -140,8 +138,7 @@ func (c *Client) get(url string) (*http.Response, error) { res, err := c.Do(req) if err != nil { - log.Error().Err(err).Msgf("red client request error : %v", url) - return nil, err + return nil, errors.Wrap(err, "could not make request: %+v", req) } if res.StatusCode == http.StatusUnauthorized { @@ -159,7 +156,7 @@ func (c *Client) get(url string) (*http.Response, error) { func (c *Client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error) { if torrentID == "" { - return nil, fmt.Errorf("red client: must have torrentID") + return nil, errors.New("red client: must have torrentID") } var r TorrentDetailsResponse @@ -168,23 +165,23 @@ func (c *Client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error) v.Add("id", torrentID) params := v.Encode() - url := fmt.Sprintf("%v?action=torrent&%v", c.URL, params) + reqUrl := fmt.Sprintf("%v?action=torrent&%v", c.URL, params) - resp, err := c.get(url) + resp, err := c.get(reqUrl) if err != nil { - return nil, err + return nil, errors.Wrap(err, "could not get torrent by id: %v", torrentID) } defer resp.Body.Close() body, readErr := ioutil.ReadAll(resp.Body) if readErr != nil { - return nil, readErr + return nil, errors.Wrap(readErr, "could not read body") } err = json.Unmarshal(body, &r) if err != nil { - return nil, err + return nil, errors.Wrap(readErr, "could not unmarshal body") } return &domain.TorrentBasic{ @@ -199,7 +196,7 @@ func (c *Client) GetTorrentByID(torrentID string) (*domain.TorrentBasic, error) func (c *Client) TestAPI() (bool, error) { resp, err := c.get(c.URL + "?action=index") if err != nil { - return false, err + return false, errors.Wrap(err, "could not run test api") } defer resp.Body.Close() diff --git a/pkg/red/red_test.go b/pkg/red/red_test.go index 2c7e591..e9945f9 100644 --- a/pkg/red/red_test.go +++ b/pkg/red/red_test.go @@ -1,17 +1,15 @@ package red import ( - "errors" "io/ioutil" "net/http" "net/http/httptest" "strings" "testing" + "github.com/autobrr/autobrr/internal/domain" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" - - "github.com/autobrr/autobrr/internal/domain" ) func TestREDClient_GetTorrentByID(t *testing.T) { @@ -57,7 +55,7 @@ func TestREDClient_GetTorrentByID(t *testing.T) { fields fields args args want *domain.TorrentBasic - wantErr error + wantErr string }{ { name: "get_by_id_1", @@ -71,7 +69,7 @@ func TestREDClient_GetTorrentByID(t *testing.T) { InfoHash: "B2BABD3A361EAFC6C4E9142C422DF7DDF5D7E163", Size: "527749302", }, - wantErr: nil, + wantErr: "", }, { name: "get_by_id_2", @@ -81,7 +79,7 @@ func TestREDClient_GetTorrentByID(t *testing.T) { }, args: args{torrentID: "100002"}, want: nil, - wantErr: errors.New("bad id parameter"), + wantErr: "could not get torrent by id: 100002: bad id parameter", }, { name: "get_by_id_3", @@ -91,7 +89,7 @@ func TestREDClient_GetTorrentByID(t *testing.T) { }, args: args{torrentID: "100002"}, want: nil, - wantErr: errors.New("unauthorized: bad credentials"), + wantErr: "could not get torrent by id: 100002: unauthorized: bad credentials", }, } for _, tt := range tests { @@ -99,8 +97,8 @@ func TestREDClient_GetTorrentByID(t *testing.T) { c := NewClient(tt.fields.Url, tt.fields.APIKey) got, err := c.GetTorrentByID(tt.args.torrentID) - if tt.wantErr != nil && assert.Error(t, err) { - assert.Equal(t, tt.wantErr, err) + if tt.wantErr != "" && assert.Error(t, err) { + assert.EqualErrorf(t, err, tt.wantErr, "Error should be: %v, got: %v", tt.wantErr, err) } assert.Equal(t, tt.want, got) diff --git a/pkg/sonarr/client.go b/pkg/sonarr/client.go index f6663d4..9a3151b 100644 --- a/pkg/sonarr/client.go +++ b/pkg/sonarr/client.go @@ -3,14 +3,12 @@ package sonarr import ( "bytes" "encoding/json" - "errors" - "fmt" "io" "net/http" "net/url" "path" - "github.com/rs/zerolog/log" + "github.com/autobrr/autobrr/pkg/errors" ) func (c *client) get(endpoint string) (int, []byte, error) { @@ -20,8 +18,7 @@ func (c *client) get(endpoint string) (int, []byte, error) { req, err := http.NewRequest(http.MethodGet, reqUrl, http.NoBody) if err != nil { - log.Error().Err(err).Msgf("sonarr client request error : %v", reqUrl) - return 0, nil, err + return 0, nil, errors.Wrap(err, "could not build request") } if c.config.BasicAuth { @@ -32,15 +29,14 @@ func (c *client) get(endpoint string) (int, []byte, error) { resp, err := c.http.Do(req) if err != nil { - log.Error().Err(err).Msgf("sonarr client.get request error: %v", reqUrl) - return 0, nil, fmt.Errorf("sonarr.http.Do(req): %w", err) + return 0, nil, errors.Wrap(err, "sonarr.http.Do(req): %+v", req) } defer resp.Body.Close() var buf bytes.Buffer if _, err = io.Copy(&buf, resp.Body); err != nil { - return resp.StatusCode, nil, fmt.Errorf("sonarr.io.Copy: %w", err) + return resp.StatusCode, nil, errors.Wrap(err, "sonarr.io.Copy") } return resp.StatusCode, buf.Bytes(), nil @@ -53,14 +49,12 @@ func (c *client) post(endpoint string, data interface{}) (*http.Response, error) jsonData, err := json.Marshal(data) if err != nil { - log.Error().Err(err).Msgf("sonarr client could not marshal data: %v", reqUrl) - return nil, err + return nil, errors.Wrap(err, "could not marshal data: %+v", data) } req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData)) if err != nil { - log.Error().Err(err).Msgf("sonarr client request error: %v", reqUrl) - return nil, err + return nil, errors.Wrap(err, "could not build request") } if c.config.BasicAuth { @@ -73,16 +67,13 @@ func (c *client) post(endpoint string, data interface{}) (*http.Response, error) res, err := c.http.Do(req) if err != nil { - log.Error().Err(err).Msgf("sonarr client request error: %v", reqUrl) - return nil, err + return nil, errors.Wrap(err, "could not make request: %+v", req) } // validate response if res.StatusCode == http.StatusUnauthorized { - log.Error().Err(err).Msgf("sonarr client bad request: %v", reqUrl) return nil, errors.New("unauthorized: bad credentials") } else if res.StatusCode != http.StatusOK { - log.Error().Err(err).Msgf("sonarr client request error: %v", reqUrl) return nil, errors.New("sonarr: bad request") } @@ -97,14 +88,12 @@ func (c *client) postBody(endpoint string, data interface{}) (int, []byte, error jsonData, err := json.Marshal(data) if err != nil { - log.Error().Err(err).Msgf("sonarr client could not marshal data: %v", reqUrl) - return 0, nil, err + return 0, nil, errors.Wrap(err, "could not marshal data: %+v", data) } req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData)) if err != nil { - log.Error().Err(err).Msgf("sonarr client request error: %v", reqUrl) - return 0, nil, err + return 0, nil, errors.Wrap(err, "could not build request") } if c.config.BasicAuth { @@ -115,19 +104,20 @@ func (c *client) postBody(endpoint string, data interface{}) (int, []byte, error resp, err := c.http.Do(req) if err != nil { - log.Error().Err(err).Msgf("sonarr client request error: %v", reqUrl) - return 0, nil, fmt.Errorf("sonarr.http.Do(req): %w", err) + return 0, nil, errors.Wrap(err, "sonarr.http.Do(req): %+v", req) } defer resp.Body.Close() var buf bytes.Buffer if _, err = io.Copy(&buf, resp.Body); err != nil { - return resp.StatusCode, nil, fmt.Errorf("sonarr.io.Copy: %w", err) + return resp.StatusCode, nil, errors.Wrap(err, "sonarr.io.Copy") } - if resp.StatusCode < 200 || resp.StatusCode > 299 { - return resp.StatusCode, buf.Bytes(), fmt.Errorf("sonarr: bad request: %v (status: %s): %s", resp.Request.RequestURI, resp.Status, buf.String()) + if resp.StatusCode == http.StatusBadRequest { + return resp.StatusCode, buf.Bytes(), nil + } else if resp.StatusCode < 200 || resp.StatusCode > 401 { + return resp.StatusCode, buf.Bytes(), errors.New("sonarr: bad request: %v (status: %s): %s", resp.Request.RequestURI, resp.Status, buf.String()) } return resp.StatusCode, buf.Bytes(), nil diff --git a/pkg/sonarr/sonarr.go b/pkg/sonarr/sonarr.go index 3a15e1c..7b88698 100644 --- a/pkg/sonarr/sonarr.go +++ b/pkg/sonarr/sonarr.go @@ -2,12 +2,15 @@ package sonarr import ( "encoding/json" - "errors" + "fmt" + "io" "net/http" "strings" "time" - "github.com/rs/zerolog/log" + "log" + + "github.com/autobrr/autobrr/pkg/errors" ) type Config struct { @@ -18,6 +21,8 @@ type Config struct { BasicAuth bool Username string Password string + + Log *log.Logger } type Client interface { @@ -28,6 +33,8 @@ type Client interface { type client struct { config Config http *http.Client + + Log *log.Logger } // New create new sonarr client @@ -40,6 +47,12 @@ func New(config Config) Client { c := &client{ config: config, http: httpClient, + Log: config.Log, + } + + if config.Log == nil { + // if no provided logger then use io.Discard + c.Log = log.New(io.Discard, "", log.LstdFlags) } return c @@ -62,6 +75,13 @@ type PushResponse struct { Rejections []string `json:"rejections"` } +type BadRequestResponse struct { + PropertyName string `json:"propertyName"` + ErrorMessage string `json:"errorMessage"` + AttemptedValue string `json:"attemptedValue"` + Severity string `json:"severity"` +} + type SystemStatusResponse struct { Version string `json:"version"` } @@ -69,21 +89,19 @@ type SystemStatusResponse struct { func (c *client) Test() (*SystemStatusResponse, error) { status, res, err := c.get("system/status") if err != nil { - log.Error().Stack().Err(err).Msg("sonarr client get error") - return nil, err + return nil, errors.Wrap(err, "could not make Test") } if status == http.StatusUnauthorized { return nil, errors.New("unauthorized: bad credentials") } - log.Trace().Msgf("sonarr system/status response: %v", string(res)) + c.Log.Printf("sonarr system/status status: (%v) response: %v\n", status, string(res)) response := SystemStatusResponse{} err = json.Unmarshal(res, &response) if err != nil { - log.Error().Stack().Err(err).Msg("sonarr client error json unmarshal") - return nil, err + return nil, errors.Wrap(err, "could not unmarshal data") } return &response, nil @@ -92,24 +110,35 @@ func (c *client) Test() (*SystemStatusResponse, error) { func (c *client) Push(release Release) ([]string, error) { status, res, err := c.postBody("release/push", release) if err != nil { - log.Error().Stack().Err(err).Msg("sonarr client post error") - return nil, err + return nil, errors.Wrap(err, "could not push release to sonarr") } - log.Trace().Msgf("sonarr release/push response status: (%v) body: %v", status, string(res)) + c.Log.Printf("sonarr release/push status: (%v) response: %v\n", status, string(res)) + + if status == http.StatusBadRequest { + badreqResponse := make([]*BadRequestResponse, 0) + err = json.Unmarshal(res, &badreqResponse) + if err != nil { + return nil, errors.Wrap(err, "could not unmarshal data") + } + + if badreqResponse[0] != nil && badreqResponse[0].PropertyName == "Title" && badreqResponse[0].ErrorMessage == "Unable to parse" { + rejections := []string{fmt.Sprintf("unable to parse: %v", badreqResponse[0].AttemptedValue)} + return rejections, err + } + } pushResponse := make([]PushResponse, 0) err = json.Unmarshal(res, &pushResponse) if err != nil { - log.Error().Stack().Err(err).Msg("sonarr client error json unmarshal") - return nil, err + return nil, errors.Wrap(err, "could not unmarshal data") } // log and return if rejected if pushResponse[0].Rejected { rejections := strings.Join(pushResponse[0].Rejections, ", ") - log.Trace().Msgf("sonarr push rejected: %s - reasons: %q", release.Title, rejections) + c.Log.Printf("sonarr release/push rejected %v reasons: %q\n", release.Title, rejections) return pushResponse[0].Rejections, nil } diff --git a/pkg/sonarr/sonarr_test.go b/pkg/sonarr/sonarr_test.go index 4dfbf86..f2083da 100644 --- a/pkg/sonarr/sonarr_test.go +++ b/pkg/sonarr/sonarr_test.go @@ -1,8 +1,8 @@ package sonarr import ( - "errors" "io/ioutil" + "log" "net/http" "net/http/httptest" "testing" @@ -15,6 +15,7 @@ import ( func Test_client_Push(t *testing.T) { // disable logger zerolog.SetGlobalLevel(zerolog.Disabled) + log.SetOutput(ioutil.Discard) mux := http.NewServeMux() ts := httptest.NewServer(mux) @@ -119,6 +120,7 @@ func Test_client_Push(t *testing.T) { func Test_client_Test(t *testing.T) { // disable logger zerolog.SetGlobalLevel(zerolog.Disabled) + log.SetOutput(ioutil.Discard) key := "mock-key" @@ -139,11 +141,11 @@ func Test_client_Test(t *testing.T) { defer srv.Close() tests := []struct { - name string - cfg Config - want *SystemStatusResponse - err error - wantErr bool + name string + cfg Config + want *SystemStatusResponse + expectedErr string + wantErr bool }{ { name: "fetch", @@ -154,9 +156,9 @@ func Test_client_Test(t *testing.T) { Username: "", Password: "", }, - want: &SystemStatusResponse{Version: "3.0.6.1196"}, - err: nil, - wantErr: false, + want: &SystemStatusResponse{Version: "3.0.6.1196"}, + expectedErr: "", + wantErr: false, }, { name: "fetch_unauthorized", @@ -167,9 +169,9 @@ func Test_client_Test(t *testing.T) { Username: "", Password: "", }, - want: nil, - wantErr: true, - err: errors.New("unauthorized: bad credentials"), + want: nil, + wantErr: true, + expectedErr: "unauthorized: bad credentials", }, } for _, tt := range tests { @@ -178,7 +180,7 @@ func Test_client_Test(t *testing.T) { got, err := c.Test() if tt.wantErr && assert.Error(t, err) { - assert.Equal(t, tt.err, err) + assert.EqualErrorf(t, err, tt.expectedErr, "Error should be: %v, got: %v", tt.wantErr, err) } assert.Equal(t, tt.want, got) diff --git a/pkg/torznab/client.go b/pkg/torznab/client.go index 4c60595..b4d7baf 100644 --- a/pkg/torznab/client.go +++ b/pkg/torznab/client.go @@ -5,12 +5,11 @@ import ( "encoding/xml" "fmt" "io" - "log" "net/http" "net/url" "time" - "github.com/pkg/errors" + "github.com/autobrr/autobrr/pkg/errors" ) type Response struct { @@ -69,12 +68,12 @@ func (t *Time) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { err := d.DecodeElement(&raw, &start) if err != nil { - return err + return errors.Wrap(err, "could not decode element") } - date, err := time.Parse(time.RFC1123Z, raw) + date, err := time.Parse(time.RFC1123Z, raw) if err != nil { - return err + return errors.Wrap(err, "could not parse date") } *t = Time{date} @@ -115,7 +114,7 @@ func (c *Client) get(endpoint string, opts map[string]string) (int, *Response, e req, err := http.NewRequest("GET", reqUrl, nil) if err != nil { - return 0, nil, err + return 0, nil, errors.Wrap(err, "could not build request") } if c.UseBasicAuth { @@ -128,19 +127,19 @@ func (c *Client) get(endpoint string, opts map[string]string) (int, *Response, e resp, err := c.http.Do(req) if err != nil { - return 0, nil, err + return 0, nil, errors.Wrap(err, "could not make request. %+v", req) } defer resp.Body.Close() var buf bytes.Buffer if _, err = io.Copy(&buf, resp.Body); err != nil { - return resp.StatusCode, nil, fmt.Errorf("torznab.io.Copy: %w", err) + return resp.StatusCode, nil, errors.Wrap(err, "torznab.io.Copy") } var response Response if err := xml.Unmarshal(buf.Bytes(), &response); err != nil { - return resp.StatusCode, nil, fmt.Errorf("torznab: could not decode feed: %w", err) + return resp.StatusCode, nil, errors.Wrap(err, "torznab: could not decode feed") } return resp.StatusCode, &response, nil @@ -149,12 +148,11 @@ func (c *Client) get(endpoint string, opts map[string]string) (int, *Response, e func (c *Client) GetFeed() ([]FeedItem, error) { status, res, err := c.get("?t=search", nil) if err != nil { - //log.Fatalf("error fetching torznab feed: %v", err) - return nil, err + return nil, errors.Wrap(err, "could not get feed") } if status != http.StatusOK { - return nil, err + return nil, errors.New("could not get feed") } return res.Channel.Items, nil @@ -167,11 +165,11 @@ func (c *Client) Search(query string) ([]FeedItem, error) { status, res, err := c.get("&t=search&"+params, nil) if err != nil { - log.Fatalf("error fetching torznab feed: %v", err) + return nil, errors.Wrap(err, "could not search feed") } if status != http.StatusOK { - return nil, err + return nil, errors.New("could not search feed") } return res.Channel.Items, nil diff --git a/pkg/whisparr/client.go b/pkg/whisparr/client.go index 104abfa..2391edc 100644 --- a/pkg/whisparr/client.go +++ b/pkg/whisparr/client.go @@ -3,12 +3,11 @@ package whisparr import ( "bytes" "encoding/json" - "errors" "net/http" "net/url" "path" - "github.com/rs/zerolog/log" + "github.com/autobrr/autobrr/pkg/errors" ) func (c *client) get(endpoint string) (*http.Response, error) { @@ -18,8 +17,7 @@ func (c *client) get(endpoint string) (*http.Response, error) { req, err := http.NewRequest(http.MethodGet, reqUrl, http.NoBody) if err != nil { - log.Error().Err(err).Msgf("whisparr client request error : %v", reqUrl) - return nil, err + return nil, errors.Wrap(err, "could not build request") } if c.config.BasicAuth { @@ -31,8 +29,7 @@ func (c *client) get(endpoint string) (*http.Response, error) { res, err := c.http.Do(req) if err != nil { - log.Error().Err(err).Msgf("whisparr client request error : %v", reqUrl) - return nil, err + return nil, errors.Wrap(err, "could not make request: %+v", req) } if res.StatusCode == http.StatusUnauthorized { @@ -49,14 +46,12 @@ func (c *client) post(endpoint string, data interface{}) (*http.Response, error) jsonData, err := json.Marshal(data) if err != nil { - log.Error().Err(err).Msgf("whisparr client could not marshal data: %v", reqUrl) - return nil, err + return nil, errors.Wrap(err, "could not marshal data: %+v", data) } req, err := http.NewRequest(http.MethodPost, reqUrl, bytes.NewBuffer(jsonData)) if err != nil { - log.Error().Err(err).Msgf("whisparr client request error: %v", reqUrl) - return nil, err + return nil, errors.Wrap(err, "could not build request") } if c.config.BasicAuth { @@ -69,16 +64,13 @@ func (c *client) post(endpoint string, data interface{}) (*http.Response, error) res, err := c.http.Do(req) if err != nil { - log.Error().Err(err).Msgf("whisparr client request error: %v", reqUrl) - return nil, err + return nil, errors.Wrap(err, "could not make request: %+v", req) } // validate response if res.StatusCode == http.StatusUnauthorized { - log.Error().Err(err).Msgf("whisparr client bad request: %v", reqUrl) return nil, errors.New("unauthorized: bad credentials") } else if res.StatusCode != http.StatusOK { - log.Error().Err(err).Msgf("whisparr client request error: %v", reqUrl) return nil, errors.New("whisparr: bad request") } diff --git a/pkg/whisparr/whisparr.go b/pkg/whisparr/whisparr.go index aa6bad6..d37881c 100644 --- a/pkg/whisparr/whisparr.go +++ b/pkg/whisparr/whisparr.go @@ -3,11 +3,12 @@ package whisparr import ( "encoding/json" "io" + "log" "net/http" "strings" "time" - "github.com/rs/zerolog/log" + "github.com/autobrr/autobrr/pkg/errors" ) type Config struct { @@ -18,6 +19,8 @@ type Config struct { BasicAuth bool Username string Password string + + Log *log.Logger } type Client interface { @@ -28,6 +31,8 @@ type Client interface { type client struct { config Config http *http.Client + + Log *log.Logger } func New(config Config) Client { @@ -39,6 +44,11 @@ func New(config Config) Client { c := &client{ config: config, http: httpClient, + Log: config.Log, + } + + if config.Log == nil { + c.Log = log.New(io.Discard, "", log.LstdFlags) } return c @@ -68,26 +78,23 @@ type SystemStatusResponse struct { func (c *client) Test() (*SystemStatusResponse, error) { res, err := c.get("system/status") if err != nil { - log.Error().Stack().Err(err).Msg("whisparr client get error") - return nil, err + return nil, errors.Wrap(err, "could not test whisparr") } defer res.Body.Close() body, err := io.ReadAll(res.Body) if err != nil { - log.Error().Stack().Err(err).Msg("whisparr client error reading body") - return nil, err + return nil, errors.Wrap(err, "could not read body") } response := SystemStatusResponse{} err = json.Unmarshal(body, &response) if err != nil { - log.Error().Stack().Err(err).Msg("whisparr client error json unmarshal") - return nil, err + return nil, errors.Wrap(err, "could not unmarshal data") } - log.Trace().Msgf("whisparr system/status response: %+v", response) + c.Log.Printf("whisparr system/status status: (%v) response: %v\n", res.Status, string(body)) return &response, nil } @@ -95,8 +102,7 @@ func (c *client) Test() (*SystemStatusResponse, error) { func (c *client) Push(release Release) ([]string, error) { res, err := c.post("release/push", release) if err != nil { - log.Error().Stack().Err(err).Msg("whisparr client post error") - return nil, err + return nil, errors.Wrap(err, "could not push release to whisparr: %+v", release) } if res == nil { @@ -107,24 +113,22 @@ func (c *client) Push(release Release) ([]string, error) { body, err := io.ReadAll(res.Body) if err != nil { - log.Error().Stack().Err(err).Msg("whisparr client error reading body") - return nil, err + return nil, errors.Wrap(err, "could not read body") } pushResponse := make([]PushResponse, 0) err = json.Unmarshal(body, &pushResponse) if err != nil { - log.Error().Stack().Err(err).Msg("whisparr client error json unmarshal") - return nil, err + return nil, errors.Wrap(err, "could not unmarshal data") } - log.Trace().Msgf("whisparr release/push response body: %+v", string(body)) + c.Log.Printf("whisparr release/push status: (%v) response: %v\n", res.Status, string(body)) // log and return if rejected if pushResponse[0].Rejected { rejections := strings.Join(pushResponse[0].Rejections, ", ") - log.Trace().Msgf("whisparr push rejected: %s - reasons: %q", release.Title, rejections) + c.Log.Printf("whisparr release/push rejected %v reasons: %q\n", release.Title, rejections) return pushResponse[0].Rejections, nil }