From 7f962130edc7addbff2ba2e1bf59744b2fb12f48 Mon Sep 17 00:00:00 2001 From: garethgeorge Date: Sat, 30 Mar 2024 15:10:41 -0700 Subject: [PATCH] fix: include error messages in restic logs --- pkg/restic/error.go | 15 +++++++++++---- pkg/restic/outputs.go | 9 +++++---- pkg/restic/restic.go | 40 ++++++++++++++++++++-------------------- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/pkg/restic/error.go b/pkg/restic/error.go index 6577945b..c6ed5859 100644 --- a/pkg/restic/error.go +++ b/pkg/restic/error.go @@ -1,6 +1,7 @@ package restic import ( + "context" "fmt" "os/exec" ) @@ -31,7 +32,7 @@ func (e *CmdError) Is(target error) bool { } // newCmdError creates a new error indicating that running a command failed. -func newCmdError(cmd *exec.Cmd, output string, err error) *CmdError { +func newCmdError(ctx context.Context, cmd *exec.Cmd, output string, err error) *CmdError { cerr := &CmdError{ Command: cmd.String(), Err: err, @@ -41,14 +42,20 @@ func newCmdError(cmd *exec.Cmd, output string, err error) *CmdError { if len(output) >= outputBufferLimit { cerr.Output = output[:outputBufferLimit] + "\n...[truncated]" } - + if logger := LoggerFromContext(ctx); logger != nil { + logger.Write([]byte(cerr.Error())) + } return cerr } -func newCmdErrorPreformatted(cmd *exec.Cmd, output string, err error) *CmdError { - return &CmdError{ +func newCmdErrorPreformatted(ctx context.Context, cmd *exec.Cmd, output string, err error) *CmdError { + cerr := &CmdError{ Command: cmd.String(), Err: err, Output: output, } + if logger := LoggerFromContext(ctx); logger != nil { + logger.Write([]byte(cerr.Error())) + } + return cerr } diff --git a/pkg/restic/outputs.go b/pkg/restic/outputs.go index fd750e0c..6d4b7175 100644 --- a/pkg/restic/outputs.go +++ b/pkg/restic/outputs.go @@ -2,6 +2,7 @@ package restic import ( "bufio" + "context" "encoding/json" "errors" "fmt" @@ -94,7 +95,7 @@ func (b *BackupProgressEntry) Validate() error { } // readBackupProgressEntries returns the summary event or an error if the command failed. -func readBackupProgressEntries(cmd *exec.Cmd, output io.Reader, callback func(event *BackupProgressEntry)) (*BackupProgressEntry, error) { +func readBackupProgressEntries(ctx context.Context, cmd *exec.Cmd, output io.Reader, callback func(event *BackupProgressEntry)) (*BackupProgressEntry, error) { scanner := bufio.NewScanner(output) scanner.Split(bufio.ScanLines) @@ -110,7 +111,7 @@ func readBackupProgressEntries(cmd *exec.Cmd, output io.Reader, callback func(ev bytes = append(bytes, scanner.Bytes()...) } - return nil, newCmdError(cmd, string(bytes), fmt.Errorf("command output was not JSON: %w", err)) + return nil, newCmdError(ctx, cmd, string(bytes), fmt.Errorf("command output was not JSON: %w", err)) } if err := event.Validate(); err != nil { return nil, err @@ -243,7 +244,7 @@ func (e *RestoreProgressEntry) Validate() error { } // readRestoreProgressEntries returns the summary event or an error if the command failed. -func readRestoreProgressEntries(cmd *exec.Cmd, output io.Reader, callback func(event *RestoreProgressEntry)) (*RestoreProgressEntry, error) { +func readRestoreProgressEntries(ctx context.Context, cmd *exec.Cmd, output io.Reader, callback func(event *RestoreProgressEntry)) (*RestoreProgressEntry, error) { scanner := bufio.NewScanner(output) scanner.Split(bufio.ScanLines) @@ -259,7 +260,7 @@ func readRestoreProgressEntries(cmd *exec.Cmd, output io.Reader, callback func(e bytes = append(bytes, scanner.Bytes()...) } - return nil, newCmdError(cmd, string(bytes), fmt.Errorf("command output was not JSON: %w", err)) + return nil, newCmdError(ctx, cmd, string(bytes), fmt.Errorf("command output was not JSON: %w", err)) } if err := event.Validate(); err != nil { return nil, err diff --git a/pkg/restic/restic.go b/pkg/restic/restic.go index 8e8393c1..289c4d86 100644 --- a/pkg/restic/restic.go +++ b/pkg/restic/restic.go @@ -58,7 +58,7 @@ func (r *Repo) commandWithContext(ctx context.Context, args []string, opts ...Ge addLoggingToCommand(ctx, cmd) if logger := LoggerFromContext(ctx); logger != nil { - fmt.Fprintf(logger, "command: %v %v\n", r.cmd, strings.Join(args, " ")) + fmt.Fprintf(logger, "\ncommand: %v %v\n", r.cmd, strings.Join(args, " ")) } return cmd @@ -99,7 +99,7 @@ func (r *Repo) init(ctx context.Context, opts ...GenericOption) error { if strings.Contains(output.String(), "config file already exists") || strings.Contains(output.String(), "already initialized") { return errAlreadyInitialized } - return newCmdError(cmd, output.String(), err) + return newCmdError(ctx, cmd, output.String(), err) } r.initialized = true @@ -129,7 +129,7 @@ func (r *Repo) Backup(ctx context.Context, paths []string, progressCallback func r.pipeCmdOutputToWriter(cmd, writer, capture) if err := cmd.Start(); err != nil { - return nil, newCmdError(cmd, "", err) + return nil, newCmdError(ctx, cmd, "", err) } var wg sync.WaitGroup @@ -141,7 +141,7 @@ func (r *Repo) Backup(ctx context.Context, paths []string, progressCallback func go func() { defer wg.Done() var err error - summary, err = readBackupProgressEntries(cmd, reader, progressCallback) + summary, err = readBackupProgressEntries(ctx, cmd, reader, progressCallback) if err != nil { readErr = fmt.Errorf("processing command output: %w", err) } @@ -173,7 +173,7 @@ func (r *Repo) Backup(ctx context.Context, paths []string, progressCallback func } if cmdErr != nil || readErr != nil { - return summary, newCmdErrorPreformatted(cmd, capture.String(), errors.Join(cmdErr, readErr)) + return summary, newCmdErrorPreformatted(ctx, cmd, capture.String(), errors.Join(cmdErr, readErr)) } return summary, nil @@ -186,12 +186,12 @@ func (r *Repo) Snapshots(ctx context.Context, opts ...GenericOption) ([]*Snapsho r.pipeCmdOutputToLogger(ctx, cmd) if err := cmd.Run(); err != nil { - return nil, newCmdError(cmd, output.String(), err) + return nil, newCmdError(ctx, cmd, output.String(), err) } var snapshots []*Snapshot if err := json.Unmarshal(output.Bytes(), &snapshots); err != nil { - return nil, newCmdError(cmd, output.String(), fmt.Errorf("command output is not valid JSON: %w", err)) + return nil, newCmdError(ctx, cmd, output.String(), fmt.Errorf("command output is not valid JSON: %w", err)) } for _, snapshot := range snapshots { @@ -211,18 +211,18 @@ func (r *Repo) Forget(ctx context.Context, policy *RetentionPolicy, opts ...Gene r.pipeCmdOutputToWriter(cmd, output) r.pipeCmdOutputToLogger(ctx, cmd) if err := cmd.Run(); err != nil { - return nil, newCmdError(cmd, output.String(), err) + return nil, newCmdError(ctx, cmd, output.String(), err) } var result []ForgetResult if err := json.Unmarshal(output.Bytes(), &result); err != nil { - return nil, newCmdError(cmd, output.String(), fmt.Errorf("command output is not valid JSON: %w", err)) + return nil, newCmdError(ctx, cmd, output.String(), fmt.Errorf("command output is not valid JSON: %w", err)) } if len(result) != 1 { return nil, fmt.Errorf("expected 1 output from forget, got %v", len(result)) } if err := result[0].Validate(); err != nil { - return nil, newCmdError(cmd, output.String(), fmt.Errorf("invalid forget result: %w", err)) + return nil, newCmdError(ctx, cmd, output.String(), fmt.Errorf("invalid forget result: %w", err)) } return &result[0], nil @@ -236,7 +236,7 @@ func (r *Repo) ForgetSnapshot(ctx context.Context, snapshotId string, opts ...Ge r.pipeCmdOutputToWriter(cmd, output) r.pipeCmdOutputToLogger(ctx, cmd) if err := cmd.Run(); err != nil { - return newCmdError(cmd, output.String(), err) + return newCmdError(ctx, cmd, output.String(), err) } return nil @@ -252,7 +252,7 @@ func (r *Repo) Prune(ctx context.Context, pruneOutput io.Writer, opts ...Generic r.pipeCmdOutputToWriter(cmd, pruneOutput) } if err := cmd.Run(); err != nil { - return newCmdErrorPreformatted(cmd, output.String(), err) + return newCmdErrorPreformatted(ctx, cmd, output.String(), err) } return nil } @@ -265,7 +265,7 @@ func (r *Repo) Restore(ctx context.Context, snapshot string, callback func(*Rest r.pipeCmdOutputToLogger(ctx, cmd) if err := cmd.Start(); err != nil { - return nil, newCmdError(cmd, "", err) + return nil, newCmdError(ctx, cmd, "", err) } var wg sync.WaitGroup @@ -277,7 +277,7 @@ func (r *Repo) Restore(ctx context.Context, snapshot string, callback func(*Rest go func() { defer wg.Done() var err error - summary, err = readRestoreProgressEntries(cmd, reader, callback) + summary, err = readRestoreProgressEntries(ctx, cmd, reader, callback) if err != nil { readErr = fmt.Errorf("processing command output: %w", err) } @@ -295,7 +295,7 @@ func (r *Repo) Restore(ctx context.Context, snapshot string, callback func(*Rest wg.Wait() if cmdErr != nil || readErr != nil { - return nil, newCmdErrorPreformatted(cmd, output.String(), errors.Join(cmdErr, readErr)) + return nil, newCmdErrorPreformatted(ctx, cmd, output.String(), errors.Join(cmdErr, readErr)) } return summary, nil @@ -313,12 +313,12 @@ func (r *Repo) ListDirectory(ctx context.Context, snapshot string, path string, r.pipeCmdOutputToLogger(ctx, cmd) if err := cmd.Run(); err != nil { - return nil, nil, newCmdError(cmd, output.String(), err) + return nil, nil, newCmdError(ctx, cmd, output.String(), err) } snapshots, entries, err := readLs(output) if err != nil { - return nil, nil, newCmdError(cmd, output.String(), err) + return nil, nil, newCmdError(ctx, cmd, output.String(), err) } return snapshots, entries, nil @@ -330,7 +330,7 @@ func (r *Repo) Unlock(ctx context.Context, opts ...GenericOption) error { r.pipeCmdOutputToWriter(cmd, output) r.pipeCmdOutputToLogger(ctx, cmd) if err := cmd.Run(); err != nil { - return newCmdError(cmd, output.String(), err) + return newCmdError(ctx, cmd, output.String(), err) } return nil } @@ -342,12 +342,12 @@ func (r *Repo) Stats(ctx context.Context, opts ...GenericOption) (*RepoStats, er r.pipeCmdOutputToLogger(ctx, cmd) if err := cmd.Run(); err != nil { - return nil, newCmdError(cmd, output.String(), err) + return nil, newCmdError(ctx, cmd, output.String(), err) } var stats RepoStats if err := json.Unmarshal(output.Bytes(), &stats); err != nil { - return nil, newCmdError(cmd, output.String(), fmt.Errorf("command output is not valid JSON: %w", err)) + return nil, newCmdError(ctx, cmd, output.String(), fmt.Errorf("command output is not valid JSON: %w", err)) } return &stats, nil