diff --git a/internal/app/app.go b/internal/app/app.go index 888f6a4..f904f71 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -26,6 +26,7 @@ import ( soundcloudprovider "streamrip-go/internal/provider/soundcloud" tidalprovider "streamrip-go/internal/provider/tidal" "streamrip-go/internal/store" + "streamrip-go/internal/verbose" ) type Main struct { @@ -110,7 +111,7 @@ func New(cfg *config.Config) (*Main, error) { "soundcloud": soundcloudprovider.New(cfg), } - return &Main{ + m := &Main{ Config: cfg, Providers: providers, Store: db, @@ -118,7 +119,9 @@ func New(cfg *config.Config) (*Main, error) { Tagger: tag.New(), Pending: []media.Pending{}, Media: []media.Media{}, - }, nil + } + verbose.SetSink(func(msg string) { m.DL.Logf("%s", msg) }) + return m, nil } // downloaderMaxConnsPerHost picks the per-host idle connection cap for the @@ -133,6 +136,7 @@ func downloaderMaxConnsPerHost(maxConnections int) int { } func (m *Main) Close() error { + verbose.SetSink(nil) m.DL.Close() artwork.CleanupTempDirs() for _, p := range m.Providers { diff --git a/internal/download/downloader.go b/internal/download/downloader.go index 4daa53d..c0dcd9d 100644 --- a/internal/download/downloader.go +++ b/internal/download/downloader.go @@ -20,6 +20,7 @@ import ( "golang.org/x/term" "streamrip-go/internal/netutil" + "streamrip-go/internal/verbose" "golang.org/x/crypto/blowfish" ) @@ -67,6 +68,7 @@ func (d *Downloader) FileVideo(ctx context.Context, sourceURL, outputPath string } func (d *Downloader) FileDeezerEncrypted(ctx context.Context, sourceURL, outputPath, trackID string) error { + logDownloadStart(sourceURL, outputPath) if err := os.MkdirAll(filepath.Dir(outputPath), 0o755); err != nil { return err } @@ -176,6 +178,7 @@ func (d *Downloader) FileDeezerEncrypted(ctx context.Context, sourceURL, outputP } func (d *Downloader) file(ctx context.Context, sourceURL, outputPath string, allowProgress bool, includeVideo bool) error { + logDownloadStart(sourceURL, outputPath) if err := os.MkdirAll(filepath.Dir(outputPath), 0o755); err != nil { return err } @@ -309,6 +312,16 @@ func (d *Downloader) Logf(format string, args ...any) { fmt.Print(msg) } +// logDownloadStart emits the source URL and destination filename when the +// user passed -v or higher. The transport-level logger covers the same +// requests at -vv, but this line gives a friendlier per-track summary. +func logDownloadStart(sourceURL, outputPath string) { + if !verbose.Enabled(verbose.V) { + return + } + verbose.Printf(verbose.V, "download %s -> %s\n", sourceURL, filepath.Base(outputPath)) +} + func shortenName(name string, max int) string { if max <= 0 { return name diff --git a/internal/netutil/http.go b/internal/netutil/http.go index 1e9e3fe..9f5cbfc 100644 --- a/internal/netutil/http.go +++ b/internal/netutil/http.go @@ -3,7 +3,10 @@ package netutil import ( "crypto/tls" "net/http" + "net/url" "time" + + "streamrip-go/internal/verbose" ) const defaultMaxConnsPerHost = 16 @@ -40,6 +43,63 @@ func NewHTTPClient(timeout time.Duration, verifySSL bool, maxConnsPerHost int) * return &http.Client{ Timeout: timeout, - Transport: transport, + Transport: &loggingTransport{base: transport}, } } + +// loggingTransport emits one verbose line per HTTP request when verbose +// level >= VV. The check is per-call so toggling the level at runtime +// affects subsequent requests without rebuilding clients. +type loggingTransport struct { + base http.RoundTripper +} + +func (t *loggingTransport) RoundTrip(req *http.Request) (*http.Response, error) { + if !verbose.Enabled(verbose.VV) { + return t.base.RoundTrip(req) + } + start := time.Now() + resp, err := t.base.RoundTrip(req) + elapsed := time.Since(start).Round(time.Millisecond) + target := redactURL(req.URL) + if err != nil { + verbose.Printf(verbose.VV, "http %s %s -> error %v (%s)\n", req.Method, target, err, elapsed) + return resp, err + } + verbose.Printf(verbose.VV, "http %s %s -> %d (%s)\n", req.Method, target, resp.StatusCode, elapsed) + return resp, err +} + +// redactURL hides values for query parameters that commonly carry +// credentials so -vv output is safe to paste in an issue. +func redactURL(u *url.URL) string { + if u == nil { + return "" + } + if u.RawQuery == "" { + return u.String() + } + q := u.Query() + redacted := false + for k := range q { + if isSensitiveParam(k) { + q.Set(k, "REDACTED") + redacted = true + } + } + if !redacted { + return u.String() + } + cp := *u + cp.RawQuery = q.Encode() + return cp.String() +} + +func isSensitiveParam(name string) bool { + switch name { + case "user_auth_token", "api_token", "access_token", "refresh_token", + "request_sig", "signature", "password", "secret", "token", "code", "auth", "key": + return true + } + return false +}