Skip to content

Commit 7332c5c

Browse files
authored
feat: Add more logs for downloading codex (#329)
1 parent 7b7abbd commit 7332c5c

2 files changed

Lines changed: 122 additions & 3 deletions

File tree

internal/codexcli/installer.go

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"errors"
1010
"fmt"
1111
"io"
12+
"log/slog"
1213
"net/http"
1314
"net/url"
1415
"os"
@@ -233,23 +234,68 @@ func (i *Installer) install(ctx context.Context) (InstallStatus, error) {
233234
if err := os.MkdirAll(filepath.Dir(targetPath), 0o755); err != nil {
234235
return InstallStatus{}, fmt.Errorf("create Codex install directory: %w", err)
235236
}
237+
started := time.Now()
238+
logURL := downloadLogURL(downloadURL)
239+
slog.InfoContext(ctx, "Codex CLI download started",
240+
"url", logURL,
241+
"os", i.goos,
242+
"arch", i.goarch,
243+
"target_path", targetPath,
244+
"max_attempts", defaultInstallAttempts,
245+
)
236246
for attempt := 1; attempt <= defaultInstallAttempts; attempt++ {
237-
status, err := i.installAttempt(ctx, platform, downloadURL, targetPath)
247+
attemptStarted := time.Now()
248+
slog.InfoContext(ctx, "Codex CLI download attempt started",
249+
"url", logURL,
250+
"attempt", attempt,
251+
"max_attempts", defaultInstallAttempts,
252+
)
253+
status, err := i.installAttempt(ctx, platform, downloadURL, targetPath, attempt)
238254
if err == nil {
255+
slog.InfoContext(ctx, "Codex CLI download completed",
256+
"url", logURL,
257+
"attempt", attempt,
258+
"duration", time.Since(started),
259+
"installed_path", status.Path,
260+
)
239261
return status, nil
240262
}
241263
var retryable *retryableInstallError
242264
if !errors.As(err, &retryable) || attempt == defaultInstallAttempts {
265+
slog.InfoContext(ctx, "Codex CLI download failed",
266+
"url", logURL,
267+
"attempt", attempt,
268+
"max_attempts", defaultInstallAttempts,
269+
"attempt_duration", time.Since(attemptStarted),
270+
"duration", time.Since(started),
271+
"error", err,
272+
)
243273
return InstallStatus{}, err
244274
}
275+
retryDelay := installRetryDelay(attempt)
276+
slog.InfoContext(ctx, "Codex CLI download attempt failed; retrying",
277+
"url", logURL,
278+
"attempt", attempt,
279+
"max_attempts", defaultInstallAttempts,
280+
"attempt_duration", time.Since(attemptStarted),
281+
"retry_in", retryDelay,
282+
"error", err,
283+
)
245284
if err := waitForInstallRetry(ctx, attempt); err != nil {
285+
slog.InfoContext(ctx, "Codex CLI download retry canceled",
286+
"url", logURL,
287+
"attempt", attempt,
288+
"duration", time.Since(started),
289+
"error", err,
290+
)
246291
return InstallStatus{}, fmt.Errorf("retry Codex CLI download: %w", err)
247292
}
248293
}
249294
return InstallStatus{}, errors.New("install Codex CLI: exhausted download attempts")
250295
}
251296

252-
func (i *Installer) installAttempt(ctx context.Context, platform downloadPlatform, downloadURL, targetPath string) (InstallStatus, error) {
297+
func (i *Installer) installAttempt(ctx context.Context, platform downloadPlatform, downloadURL, targetPath string, attempt int) (InstallStatus, error) {
298+
requestStarted := time.Now()
253299
req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadURL, nil)
254300
if err != nil {
255301
return InstallStatus{}, fmt.Errorf("create Codex download request: %w", err)
@@ -259,12 +305,27 @@ func (i *Installer) installAttempt(ctx context.Context, platform downloadPlatfor
259305
resp, err := i.httpClient.Do(req)
260306
if err != nil {
261307
err = fmt.Errorf("download Codex CLI: %w", err)
308+
slog.InfoContext(ctx, "Codex CLI download request failed",
309+
"url", downloadLogURL(downloadURL),
310+
"attempt", attempt,
311+
"duration", time.Since(requestStarted),
312+
"error", err,
313+
)
262314
if ctx.Err() == nil {
263315
err = &retryableInstallError{err: err}
264316
}
265317
return InstallStatus{}, err
266318
}
267319
defer resp.Body.Close()
320+
slog.InfoContext(ctx, "Codex CLI download response received",
321+
"url", downloadLogURL(downloadURL),
322+
"attempt", attempt,
323+
"status", resp.StatusCode,
324+
"protocol", resp.Proto,
325+
"content_length", resp.ContentLength,
326+
"accept_ranges", resp.Header.Get("Accept-Ranges"),
327+
"duration", time.Since(requestStarted),
328+
)
268329
if resp.StatusCode != http.StatusOK {
269330
message, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
270331
detail := strings.TrimSpace(string(message))
@@ -303,6 +364,14 @@ func (i *Installer) installAttempt(ctx context.Context, platform downloadPlatfor
303364
err = installUnixArchive(temp, limited, platform.archiveBinary)
304365
}
305366
if err != nil {
367+
downloaded := maxDownloadSize + 1 - limited.N
368+
slog.InfoContext(ctx, "Codex CLI download body failed",
369+
"url", downloadLogURL(downloadURL),
370+
"attempt", attempt,
371+
"bytes", downloaded,
372+
"duration", time.Since(requestStarted),
373+
"error", err,
374+
)
306375
if body.err != nil {
307376
err = &retryableInstallError{err: err}
308377
}
@@ -314,6 +383,12 @@ func (i *Installer) installAttempt(ctx context.Context, platform downloadPlatfor
314383
if limited.N <= 0 {
315384
return InstallStatus{}, fmt.Errorf("download Codex CLI: package exceeds limit %d", maxDownloadSize)
316385
}
386+
slog.InfoContext(ctx, "Codex CLI download body completed",
387+
"url", downloadLogURL(downloadURL),
388+
"attempt", attempt,
389+
"bytes", maxDownloadSize+1-limited.N,
390+
"duration", time.Since(requestStarted),
391+
)
317392
if err := temp.Chmod(0o755); err != nil {
318393
return InstallStatus{}, fmt.Errorf("make Codex binary executable: %w", err)
319394
}
@@ -342,8 +417,23 @@ func retryableHTTPStatus(status int) bool {
342417
status >= http.StatusInternalServerError && status < 600
343418
}
344419

420+
func downloadLogURL(value string) string {
421+
parsed, err := url.Parse(value)
422+
if err != nil {
423+
return ""
424+
}
425+
parsed.User = nil
426+
parsed.RawQuery = ""
427+
parsed.Fragment = ""
428+
return parsed.String()
429+
}
430+
431+
func installRetryDelay(attempt int) time.Duration {
432+
return installRetryBaseDelay << (attempt - 1)
433+
}
434+
345435
func waitForInstallRetry(ctx context.Context, attempt int) error {
346-
delay := installRetryBaseDelay << (attempt - 1)
436+
delay := installRetryDelay(attempt)
347437
timer := time.NewTimer(delay)
348438
defer timer.Stop()
349439
select {

internal/codexcli/installer_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ import (
88
"encoding/binary"
99
"errors"
1010
"fmt"
11+
"log/slog"
1112
"net/http"
1213
"net/http/httptest"
1314
"os"
1415
"path/filepath"
16+
"strings"
1517
"sync"
1618
"sync/atomic"
1719
"testing"
@@ -259,6 +261,11 @@ func TestInstallerEnsureCoalescesConcurrentDownloads(t *testing.T) {
259261
}
260262

261263
func TestInstallerEnsureAutomaticallyRetriesTemporaryDownloadFailure(t *testing.T) {
264+
var logs bytes.Buffer
265+
previousLogger := slog.Default()
266+
slog.SetDefault(slog.New(slog.NewTextHandler(&logs, &slog.HandlerOptions{Level: slog.LevelInfo})))
267+
t.Cleanup(func() { slog.SetDefault(previousLogger) })
268+
262269
target := filepath.Join(t.TempDir(), "codex")
263270
t.Setenv(EnvBinaryPath, target)
264271
payload := unixCodexArchive(t, []archiveEntry{{name: "codex-x86_64-unknown-linux-musl", body: string(testELFBinary("amd64"))}})
@@ -281,6 +288,20 @@ func TestInstallerEnsureAutomaticallyRetriesTemporaryDownloadFailure(t *testing.
281288
if got := requests.Load(); got != 2 {
282289
t.Fatalf("download requests = %d, want 2", got)
283290
}
291+
for _, expected := range []string{
292+
"Codex CLI download started",
293+
"Codex CLI download attempt started",
294+
"Codex CLI download response received",
295+
"status=502",
296+
"Codex CLI download attempt failed; retrying",
297+
"retry_in=100ms",
298+
"Codex CLI download body completed",
299+
"Codex CLI download completed",
300+
} {
301+
if !strings.Contains(logs.String(), expected) {
302+
t.Fatalf("download logs missing %q:\n%s", expected, logs.String())
303+
}
304+
}
284305
}
285306

286307
func TestInstallerEnsureCanRetryAfterAutomaticRetriesAreExhausted(t *testing.T) {
@@ -352,6 +373,14 @@ func TestInstallerDefaultHTTPClientNegotiatesHTTP2OnEveryPlatform(t *testing.T)
352373
}
353374
}
354375

376+
func TestDownloadLogURLRedactsCredentialsAndQuery(t *testing.T) {
377+
got := downloadLogURL("https://user:secret@example.test/codex-cli/latest/windows/amd64?package=codex-cli&token=secret#fragment")
378+
want := "https://example.test/codex-cli/latest/windows/amd64"
379+
if got != want {
380+
t.Fatalf("downloadLogURL() = %q, want %q", got, want)
381+
}
382+
}
383+
355384
func TestInstallerEnsureRejectsInvalidPackageWithoutPartialBinary(t *testing.T) {
356385
targetDir := t.TempDir()
357386
target := filepath.Join(targetDir, "codex")

0 commit comments

Comments
 (0)