Add structured logging and better error handling
This commit is contained in:
1 parent
db4e3c9611
commit
68c7dd76f2
31 files changed
+640
-109
No files matched your search
@@ -3,6 +3,7 @@ package fetch
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -34,6 +35,14 @@ type Request struct {
|
||||
State *State
|
||||
Progress Progress
|
||||
Checkpoint func(State)
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
func (request Request) logger() *slog.Logger {
|
||||
if request.Logger != nil {
|
||||
return request.Logger
|
||||
}
|
||||
return slog.Default()
|
||||
}
|
||||
|
||||
type Downloader struct {
|
||||
@@ -90,11 +99,19 @@ func (downloader *Downloader) Download(ctx context.Context, request Request) (st
|
||||
}
|
||||
|
||||
func (downloader *Downloader) prepare(ctx context.Context, source *remoteFile, request Request, partPath string) (State, *http.Response, error) {
|
||||
if request.State != nil && request.State.valid() && fileExists(partPath) {
|
||||
state := *request.State
|
||||
source.validator = state.Validator
|
||||
source.totalBytes = state.TotalBytes
|
||||
return state, nil, nil
|
||||
if request.State != nil {
|
||||
if request.State.valid() && fileExists(partPath) {
|
||||
state := *request.State
|
||||
source.validator = state.Validator
|
||||
source.totalBytes = state.TotalBytes
|
||||
request.logger().Info("resuming from saved progress",
|
||||
"bytes_done", state.DoneBytes(),
|
||||
"bytes_total", state.TotalBytes,
|
||||
"parts", len(state.Segments),
|
||||
)
|
||||
return state, nil, nil
|
||||
}
|
||||
request.logger().Warn("saved progress is unusable, starting over", "part_file_exists", fileExists(partPath))
|
||||
}
|
||||
|
||||
probe, err := downloader.open(ctx, source, 0, 0)
|
||||
@@ -113,6 +130,13 @@ func (downloader *Downloader) prepare(ctx context.Context, source *remoteFile, r
|
||||
TotalBytes: source.totalBytes,
|
||||
Segments: planSegments(source.totalBytes, connections),
|
||||
}
|
||||
request.logger().Info("connected to server",
|
||||
"file", state.FileName,
|
||||
"bytes_total", state.TotalBytes,
|
||||
"supports_ranges", probe.Header.Get("Accept-Ranges") == "bytes",
|
||||
"has_validator", state.Validator != "",
|
||||
"parts", len(state.Segments),
|
||||
)
|
||||
return state, probe, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -123,6 +123,8 @@ func (session *session) saveCheckpoint() {
|
||||
return
|
||||
}
|
||||
state := session.snapshot()
|
||||
session.file.Sync()
|
||||
if err := session.file.Sync(); err != nil {
|
||||
session.request.logger().Warn("could not flush partial file to disk", "error", err)
|
||||
}
|
||||
session.request.Checkpoint(state)
|
||||
}
|
||||
@@ -68,11 +68,26 @@ func (worker *segmentWorker) record(count int64) error {
|
||||
|
||||
func (worker *segmentWorker) reconnect(ctx context.Context, cause error) (*http.Response, error) {
|
||||
progress := worker.session.request.Progress
|
||||
logger := worker.session.request.logger().With("part_start", worker.segment.start)
|
||||
for {
|
||||
worker.failedAttempts++
|
||||
if ctx.Err() != nil || !isRetryable(cause) || worker.failedAttempts > maxRetriesWithoutProgress {
|
||||
if ctx.Err() != nil {
|
||||
return nil, cause
|
||||
}
|
||||
if !isRetryable(cause) {
|
||||
logger.Debug("error cannot be retried", "error", cause)
|
||||
return nil, cause
|
||||
}
|
||||
if worker.failedAttempts > maxRetriesWithoutProgress {
|
||||
logger.Error("giving up after repeated connection failures", "attempts", maxRetriesWithoutProgress, "error", cause)
|
||||
return nil, fmt.Errorf("gave up after %d reconnect attempts: %w", maxRetriesWithoutProgress, cause)
|
||||
}
|
||||
|
||||
logger.Warn("connection lost, reconnecting",
|
||||
"attempt", worker.failedAttempts,
|
||||
"position", worker.segment.position(),
|
||||
"error", cause,
|
||||
)
|
||||
progress.SetNotice(fmt.Sprintf("Connection lost, reconnecting (attempt %d of %d)", worker.failedAttempts, maxRetriesWithoutProgress))
|
||||
if err := waitBeforeRetry(ctx, worker.failedAttempts); err != nil {
|
||||
return nil, err
|
||||
@@ -80,6 +95,7 @@ func (worker *segmentWorker) reconnect(ctx context.Context, cause error) (*http.
|
||||
|
||||
response, err := worker.open(ctx)
|
||||
if err == nil {
|
||||
logger.Info("reconnected", "position", worker.segment.position())
|
||||
progress.SetNotice("")
|
||||
return response, nil
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user