Repository: cli/cli
File: pkg/cmd/run/view/view.go (Go)

Code before:
//	├── 0_jobname1.txt
//	├── 1_jobname2.txt
//	└── -9999999999_jobname3.txt
//
// It iterates through the list of jobs and tries to find the matching
// log in the zip file. If the matching log is found it is attached
// to the job.
//
// The top-level .txt files include the logs for an entire job run. Note that
// the prefixed number is either:
//   - An ordinal and cannot be mapped to the corresponding job's ID.
//   - A negative integer which is the ID of the job in the old Actions service.
//     The service right now tries to get logs and use an ordinal in a loop.
//     However, if it doesn't get the logs, it falls back to an old service
//     where the ID can apparently be negative.
func attachRunLog(rlz *zip.Reader, jobs []shared.Job) {
	for i, job := range jobs {
		// the normal job run log file is preferred over the legacy one. So, we
		// try to find the normal log file, and if we couldn't find it then we
		// look for the legacy one, if any.
		jobLog := matchFileInZIPArchive(rlz, jobLogFilenameRegexp(job))
		if jobLog == nil {
			jobLog = matchFileInZIPArchive(rlz, legacyJobLogFilenameRegexp(job))
		}
		jobs[i].Log = jobLog

		for j, step := range job.Steps {
			jobs[i].Steps[j].Log = matchFileInZIPArchive(rlz, stepLogFilenameRegexp(job, step))
		}
	}
}

func matchFileInZIPArchive(zr *zip.Reader, re *regexp.Regexp) *zip.File {
	for _, file := range zr.File {
		if re.MatchString(file.Name) {
			return file
		}
	}
	return nil
}

func displayRunLog(w io.Writer, jobs []shared.Job, failed bool) error {
	for _, job := range jobs {
		// To display a run log, we first try to compile it from individual step
		// logs, because this way we can prepend lines with the corresponding
		// step name. However, at the time of writing, logs are sometimes being
		// served by a service that doesn’t include the step logs (none of them),
		// in which case we fall back to print the entire job run log.
		var hasStepLogs bool

		steps := job.Steps

Review comment (suggestion):
Not for this PR but when `jobLog == nil` after both attempts, this really feels like we should try to do:

https://github.com/cli/cli/issues/7643

Very annoying that things are silently not there.

Code after:
//	├── 0_jobname1.txt
//	├── 1_jobname2.txt
//	└── -9999999999_jobname3.txt
//
// It iterates through the list of jobs and tries to find the matching
// log in the zip file. If the matching log is found it is attached
// to the job.
//
// The top-level .txt files include the logs for an entire job run. Note that
// the prefixed number is either:
//   - An ordinal and cannot be mapped to the corresponding job's ID.
//   - A negative integer which is the ID of the job in the old Actions service.
//     The service right now tries to get logs and use an ordinal in a loop.
//     However, if it doesn't get the logs, it falls back to an old service
//     where the ID can apparently be negative.
func attachRunLog(rlz *zip.Reader, jobs []shared.Job) {
	for i, job := range jobs {
		// As a highest priority, we try to use the step logs first. We have seen zips that surprisingly contain
		// step logs, normal job logs and legacy job logs. In this case, both job logs would be ignored. We have
		// never seen a zip containing both job logs and no step logs, however, it may be possible. In that case
		// let's prioritise the normal log over the legacy one.
		jobLog := matchFileInZIPArchive(rlz, jobLogFilenameRegexp(job))
		if jobLog == nil {
			jobLog = matchFileInZIPArchive(rlz, legacyJobLogFilenameRegexp(job))
		}
		jobs[i].Log = jobLog

		for j, step := range job.Steps {
			jobs[i].Steps[j].Log = matchFileInZIPArchive(rlz, stepLogFilenameRegexp(job, step))
		}
	}
}

func matchFileInZIPArchive(zr *zip.Reader, re *regexp.Regexp) *zip.File {
	for _, file := range zr.File {
		if re.MatchString(file.Name) {
			return file
		}
	}
	return nil
}

func displayRunLog(w io.Writer, jobs []shared.Job, failed bool) error {
	for _, job := range jobs {
		// To display a run log, we first try to compile it from individual step
		// logs, because this way we can prepend lines with the corresponding
		// step name. However, at the time of writing, logs are sometimes being
		// served by a service that doesn’t include the step logs (none of them),
		// in which case we fall back to print the entire job run log.
		var hasStepLogs bool