-
Notifications
You must be signed in to change notification settings - Fork 26
[RAPTOR-19727] Don't suggest logs command that won't work #852
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -387,7 +387,13 @@ func WaitForBuild( | |
|
|
||
| if IsTerminalBuildStatus(build.Status) { | ||
| if IsBuildErrorStatus(build.Status) { | ||
| return build, fmt.Errorf("build %s ended with status %s; run 'dr artifact build logs %s' to inspect", buildID, build.Status, buildID) | ||
| // No 'dr artifact build logs' suggestion here: this shared | ||
| // primitive doesn't know whether logs exist, and callers | ||
| // that already fetch them (via BuildSummaryFor) shouldn't be | ||
| // forced into a second fetch just so this function can | ||
| // build a hint. Callers construct the final message | ||
| // themselves via BuildFailureMessage. | ||
| return build, fmt.Errorf("build %s ended with status %s", buildID, build.Status) | ||
| } | ||
|
|
||
| return build, nil | ||
|
|
@@ -401,6 +407,27 @@ func WaitForBuild( | |
| } | ||
| } | ||
|
|
||
| // BuildLogsAvailable reports whether at least one log entry exists for the | ||
| // build. Used to decide whether it's worth pointing the user at | ||
| // 'dr artifact build logs' -- suggesting a command that 404s or prints | ||
| // nothing is worse than not suggesting anything. | ||
| func BuildLogsAvailable(artifactID, buildID string) bool { | ||
| entries, err := GetArtifactBuildLogs(artifactID, buildID) | ||
|
|
||
| return err == nil && len(entries) > 0 | ||
| } | ||
|
|
||
| // BuildFailureMessage formats the error for a build that ended in | ||
| // FAILED/CANCELLED, pointing at 'dr artifact build logs' only when | ||
| // logsAvailable is true. | ||
| func BuildFailureMessage(artifactID, buildID, status string, logsAvailable bool) error { | ||
| if !logsAvailable { | ||
| return fmt.Errorf("build %s ended with status %s; no logs were captured for this build", buildID, status) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fetch errors claimed as missing logsMedium Severity
Additional Locations (2)Reviewed by Cursor Bugbot for commit 5c756c6. Configure here. |
||
| } | ||
|
|
||
| return fmt.Errorf("build %s ended with status %s; see 'dr artifact build logs %s %s'", buildID, status, artifactID, buildID) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [low] question: does this hint hold up for a build that only logged at DEBUG? |
||
| } | ||
|
|
||
| // BuildSummaryFor composes the terminal-state summary RenderBuildSummary | ||
| // renders. Duration comes from the Build timestamps; ImageURI is fetched | ||
| // from the parent artifact's primary container only on COMPLETED (the | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -254,11 +254,25 @@ func RenderBuildSummary(format outputformat.OutputFormat, summary BuildSummary) | |
| return nil | ||
| } | ||
|
|
||
| // RenderBuildLogs prints a build's log entries: one formatted line each in | ||
| // text mode, or a JSON array (always [], never null, when empty). With no | ||
| // entries in text mode, "No logs found." goes to stderr so stdout stays log | ||
| // lines only and a `logs | grep`/pipe is not polluted by a status line. | ||
| func RenderBuildLogs(format outputformat.OutputFormat, entries []BuildLogEntry) error { | ||
| if format == outputformat.OutputFormatJSON { | ||
| if len(entries) == 0 { | ||
| entries = []BuildLogEntry{} | ||
| } | ||
|
|
||
| return printJSON(entries) | ||
| } | ||
|
|
||
| if len(entries) == 0 { | ||
| fmt.Fprintln(os.Stderr, "No logs found.") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P3] "No logs found." is wrong for logs filtered out by the default level Verified end-to-end: Render-side sibling of the unfiltered-availability comment on build.go: since filtering happens in the caller, |
||
|
|
||
| return nil | ||
| } | ||
|
|
||
| for _, entry := range entries { | ||
| fmt.Println(formatLogLine(entry)) | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -481,10 +481,10 @@ func buildImage(artifactID, attachTo string, opts Options, report *reporter) (st | |
| } | ||
|
|
||
| if workload.IsBuildErrorStatus(built.Status) { | ||
| // Said here rather than left to the wait's own wording, because this | ||
| // is the only place that knows which artifact the build belongs to. | ||
| return built.ID, fmt.Errorf("build %s finished as %s; see 'dr artifact build logs %s %s'", | ||
| built.ID, built.Status, artifactID, built.ID) | ||
| // Precheck log availability before suggesting the logs command -- | ||
| // this is the only call site with no already-fetched log info, so | ||
| // it's the one place that needs its own extra fetch. | ||
| return built.ID, workload.BuildFailureMessage(artifactID, built.ID, built.Status, hasLogsFn(artifactID, built.ID)) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [medium] Build log ingestion trails the builder by 20-40s per the |
||
| } | ||
|
|
||
| // A build still running when the wait expires keeps its id too: it is | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -193,6 +193,7 @@ type fakes struct { | |
| build func(string) (*workload.BuildTriggerResponse, error) | ||
| waitBuild func(string, string, time.Duration, time.Duration, func(*workload.Build)) (*workload.Build, error) | ||
| builds func(string, int) ([]workload.Build, error) | ||
| hasLogs func(string, string) bool | ||
|
|
||
| // checkEndpoint is the one GET a deploy ends with. | ||
| checkEndpoint func(string) (int, error) | ||
|
|
@@ -287,6 +288,11 @@ func install(t *testing.T, f fakes) { | |
| swap(t, &waitBuildFn, f.waitBuild) | ||
| swap(t, &listBuildsFn, f.builds) | ||
|
|
||
| // Defaults to "no logs" so a test that does not care does not make a | ||
| // real network call by accident. | ||
| force(t, &hasLogsFn, func(string, string) bool { return false }) | ||
| swap(t, &hasLogsFn, f.hasLogs) | ||
|
|
||
| // Nothing stands in the way of a rollout unless a test says so, because | ||
| // the quiet answer is the one every other roll test wants. | ||
| force(t, &guardReplacementFn, func(string) error { return nil }) | ||
|
|
@@ -1492,6 +1498,8 @@ func TestRun_FailedBuildStopsAndNamesTheLogs(t *testing.T) { | |
| return &workload.Build{ID: id, Status: workload.BuildStatusFailed}, | ||
| fmt.Errorf("build %s ended with status %s", id, workload.BuildStatusFailed) | ||
| } | ||
| // Logs exist for this build, so the failure message should point at them. | ||
| f.hasLogs = func(string, string) bool { return true } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [low] Both tests that reach this path force |
||
|
|
||
| install(t, f) | ||
|
|
||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2] BuildLogsAvailable drains every log page to answer a boolean
BuildLogsAvailablecallsGetArtifactBuildLogs, which hardcodesmaxEntries = 0(build.go:337). IndrainLogPages,maxEntries <= 0means "drain every page" — it follows everynextlink to the end with no early exit. For a failed build with a verbose Docker log stream, that's potentially dozens of HTTP round-trips just to evaluatelen(entries) > 0.The trigger is this PR's only "extra precheck fetch" call site:
buildImageininternal/workload/up/build.gocallshasLogsFnon every faileddr workload up, right afterBuildLogTailalready fetched (much of) the same data during the wait.The fix is cheap because the seam already exists: call
fetchArtifactBuildLogs(artifactID, buildID, 1, "", "", "")directly —limit=1short-circuits after the first page — or giveGetArtifactBuildLogsa limit parameter. (Related to but distinct from the ingestion-lag concern raised in the other thread: even with correct timing, this check is far more expensive than it needs to be.)