Skip to content

Commit 929cf16

Browse files
fix: enhance pg_restore process with improved error handling and logging
1 parent 7515d79 commit 929cf16

1 file changed

Lines changed: 121 additions & 10 deletions

File tree

internal/restore.go

Lines changed: 121 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,105 @@ import (
2323

2424
// RestoreProcess represents a pg_restore process
2525
type RestoreProcess struct {
26-
cmd *exec.Cmd
27-
stdin io.WriteCloser
26+
cmd *exec.Cmd
27+
stdin io.WriteCloser
28+
stdout io.ReadCloser
29+
stderr io.ReadCloser
2830
}
2931

3032
// Start starts the pg_restore process
3133
func (p *RestoreProcess) Start() error {
32-
p.stdin, _ = p.cmd.StdinPipe()
34+
var err error
35+
36+
// Set up pipes for stdin, stdout, and stderr
37+
p.stdin, err = p.cmd.StdinPipe()
38+
if err != nil {
39+
return fmt.Errorf("failed to create stdin pipe: %w", err)
40+
}
41+
42+
p.stdout, err = p.cmd.StdoutPipe()
43+
if err != nil {
44+
return fmt.Errorf("failed to create stdout pipe: %w", err)
45+
}
46+
47+
p.stderr, err = p.cmd.StderrPipe()
48+
if err != nil {
49+
return fmt.Errorf("failed to create stderr pipe: %w", err)
50+
}
51+
3352
if err := p.cmd.Start(); err != nil {
34-
return err
53+
return fmt.Errorf("failed to start pg_restore process: %w", err)
3554
}
55+
3656
return nil
3757
}
3858

3959
// Wait waits for the pg_restore process to complete
4060
func (p *RestoreProcess) Wait() error {
61+
logger := log.Logger.With().Str("caller", "restore_process_wait").Logger()
62+
63+
// Close stdin to signal we're done sending data
4164
p.stdin.Close()
42-
return p.cmd.Wait()
65+
66+
// Read stdout and stderr concurrently
67+
stdoutChan := make(chan string, 1)
68+
stderrChan := make(chan string, 1)
69+
70+
go func() {
71+
if p.stdout != nil {
72+
if output, err := io.ReadAll(p.stdout); err == nil {
73+
stdoutChan <- string(output)
74+
} else {
75+
stdoutChan <- ""
76+
}
77+
p.stdout.Close()
78+
} else {
79+
stdoutChan <- ""
80+
}
81+
}()
82+
83+
go func() {
84+
if p.stderr != nil {
85+
if output, err := io.ReadAll(p.stderr); err == nil {
86+
stderrChan <- string(output)
87+
} else {
88+
stderrChan <- ""
89+
}
90+
p.stderr.Close()
91+
} else {
92+
stderrChan <- ""
93+
}
94+
}()
95+
96+
// Wait for the process to complete
97+
err := p.cmd.Wait()
98+
99+
// Collect output
100+
stdoutOutput := <-stdoutChan
101+
stderrOutput := <-stderrChan
102+
103+
// Log outputs for debugging
104+
if stdoutOutput != "" {
105+
logger.Debug().Str("pg_restore_stdout", stdoutOutput).Msg("pg_restore stdout output")
106+
}
107+
108+
if stderrOutput != "" {
109+
if err != nil {
110+
logger.Error().Str("pg_restore_stderr", stderrOutput).Msg("pg_restore error output")
111+
} else {
112+
logger.Debug().Str("pg_restore_stderr", stderrOutput).Msg("pg_restore stderr output")
113+
}
114+
}
115+
116+
// Return enhanced error with pg_restore output
117+
if err != nil {
118+
if stderrOutput != "" {
119+
return fmt.Errorf("pg_restore failed: %w\npg_restore stderr: %s", err, stderrOutput)
120+
}
121+
return fmt.Errorf("pg_restore failed: %w", err)
122+
}
123+
124+
return nil
43125
}
44126

45127
// Write writes data to the pg_restore process stdin
@@ -73,8 +155,13 @@ func NewRestore(ctx context.Context, targetDatabase string) (*RestoreProcess, er
73155
argument = append(argument, "--username", *config.Loaded.Postgres.User)
74156
}
75157

76-
// Use the target database name
77-
argument = append(argument, "--dbname", targetDatabase)
158+
// When using --create, connect to a maintenance database (postgres) instead of the target database
159+
// This avoids connection issues when the target database doesn't exist yet
160+
maintenanceDB := "postgres"
161+
if config.Loaded.Postgres.Database != nil && *config.Loaded.Postgres.Database != targetDatabase {
162+
maintenanceDB = *config.Loaded.Postgres.Database
163+
}
164+
argument = append(argument, "--dbname", maintenanceDB)
78165

79166
process.cmd = exec.CommandContext(ctx, "pg_restore", argument...)
80167
if config.Loaded.Postgres.Password != nil {
@@ -87,6 +174,13 @@ func NewRestore(ctx context.Context, targetDatabase string) (*RestoreProcess, er
87174
// Restore performs a complete restore operation from a backup reader to the target database
88175
func Restore(backupReader io.Reader, targetDatabase, backupFilename string) error {
89176
ctx := context.Background()
177+
logger := log.Logger.With().
178+
Str("caller", "restore").
179+
Str("target_database", targetDatabase).
180+
Str("backup_filename", backupFilename).
181+
Logger()
182+
183+
logger.Debug().Msg("starting restore operation")
90184

91185
// Apply decompression if needed
92186
decompressedReader, err := Decompress(backupReader, backupFilename)
@@ -105,20 +199,37 @@ func Restore(backupReader io.Reader, targetDatabase, backupFilename string) erro
105199
return fmt.Errorf("failed to start restore process: %w", err)
106200
}
107201

108-
// Stream backup data to pg_restore
109-
_, err = io.Copy(restoreProcess, decompressedReader)
202+
logger.Debug().Msg("pg_restore process started, beginning data stream")
203+
204+
// Stream backup data to pg_restore with better error handling
205+
bytesStreamed, err := io.Copy(restoreProcess, decompressedReader)
110206
if err != nil {
207+
logger.Error().
208+
Err(err).
209+
Int64("bytes_streamed", bytesStreamed).
210+
Msg("failed to stream backup data to pg_restore")
211+
212+
// Try to get pg_restore error output for better diagnostics
111213
if waitErr := restoreProcess.Wait(); waitErr != nil {
112-
log.Error().Err(waitErr).Msg("failed to wait for restore process cleanup")
214+
logger.Error().Err(waitErr).Msg("pg_restore process terminated with error during cleanup")
215+
return fmt.Errorf("failed to stream backup data to restore process: %w (pg_restore error: %v)", err, waitErr)
216+
} else {
217+
logger.Debug().Msg("pg_restore process terminated cleanly after streaming error")
113218
}
114219
return fmt.Errorf("failed to stream backup data to restore process: %w", err)
115220
}
116221

222+
logger.Debug().
223+
Int64("bytes_streamed", bytesStreamed).
224+
Msg("backup data streaming completed, waiting for pg_restore to finish")
225+
117226
// Wait for restore to complete
118227
if err := restoreProcess.Wait(); err != nil {
228+
logger.Error().Err(err).Msg("pg_restore process failed during completion")
119229
return fmt.Errorf("pg_restore process failed: %w", err)
120230
}
121231

232+
logger.Debug().Msg("restore operation completed successfully")
122233
return nil
123234
}
124235

0 commit comments

Comments
 (0)