findFirstFailingStep finds the first step that failed in the job logs
(jobLog string)
| 1101 | |
| 1102 | // findFirstFailingStep finds the first step that failed in the job logs |
| 1103 | func findFirstFailingStep(jobLog string) (int, string) { |
| 1104 | auditLog.Printf("Searching for first failing step in job logs (%d bytes)", len(jobLog)) |
| 1105 | lines := strings.Split(jobLog, "\n") |
| 1106 | var stepOutput []string |
| 1107 | inStep := false |
| 1108 | currentStep := 0 |
| 1109 | foundFailure := false |
| 1110 | |
| 1111 | for _, line := range lines { |
| 1112 | // Detect step start |
| 1113 | if strings.Contains(line, "##[group]") { |
| 1114 | if inStep && foundFailure { |
| 1115 | break // We found a complete failing step |
| 1116 | } |
| 1117 | inStep = true |
| 1118 | currentStep++ |
| 1119 | stepOutput = []string{line} |
| 1120 | foundFailure = false |
| 1121 | } else if inStep { |
| 1122 | stepOutput = append(stepOutput, line) |
| 1123 | |
| 1124 | // Detect failure indicators |
| 1125 | if strings.Contains(line, "##[error]") || |
| 1126 | strings.Contains(line, "Error:") || |
| 1127 | strings.Contains(line, "FAILED") || |
| 1128 | strings.Contains(line, "exit code") && !strings.Contains(line, "exit code 0") { |
| 1129 | foundFailure = true |
| 1130 | } |
| 1131 | } |
| 1132 | } |
| 1133 | |
| 1134 | if foundFailure && len(stepOutput) > 0 { |
| 1135 | auditLog.Printf("Found failing step %d with %d lines of output", currentStep, len(stepOutput)) |
| 1136 | return currentStep, strings.Join(stepOutput, "\n") |
| 1137 | } |
| 1138 | |
| 1139 | auditLog.Print("No failing step found in job logs") |
| 1140 | return 0, "" |
| 1141 | } |
| 1142 | |
| 1143 | // fetchWorkflowRunMetadata fetches metadata for a single workflow run |
| 1144 | func fetchWorkflowRunMetadata(ctx context.Context, runID int64, owner, repo, hostname string, verbose bool) (WorkflowRun, error) { |