Analyze Failed Build Logs #117
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# .github/workflows/analyze-failed-build-logs.yml | |
name: Analyze Failed Build Logs | |
on: | |
workflow_run: | |
workflows: ["CI"] # Ensure this matches the exact name of your primary workflow | |
types: | |
- completed | |
jobs: | |
analyze-logs: | |
name: Analyze Logs on Failure | |
runs-on: ubuntu-latest | |
if: ${{ github.event.workflow_run.conclusion == 'failure' }} | |
steps: | |
- name: Set up environment | |
run: | | |
sudo apt-get update | |
sudo apt-get install -y jq unzip curl | |
- name: Fetch Failed Jobs | |
id: fetch_failed_jobs | |
env: | |
GH_PAT: ${{ secrets.LOG_ACCESS_TOKEN }} | |
run: | | |
# Extract repository owner and name | |
REPO_OWNER=$(echo "${GITHUB_REPOSITORY}" | cut -d'/' -f1) | |
REPO_NAME=$(echo "${GITHUB_REPOSITORY}" | cut -d'/' -f2) | |
RUN_ID=${{ github.event.workflow_run.id }} | |
echo "Repository Owner: $REPO_OWNER" | |
echo "Repository Name: $REPO_NAME" | |
echo "Run ID: $RUN_ID" | |
# Fetch list of jobs for the run | |
JOBS_JSON=$(curl -s -H "Authorization: token $GH_PAT" \ | |
-H "Accept: application/vnd.github.v3+json" \ | |
"https://api.github.com/repos/$REPO_OWNER/$REPO_NAME/actions/runs/$RUN_ID/jobs") | |
# Extract failed jobs' IDs and names | |
FAILED_JOBS=$(echo "$JOBS_JSON" | jq -r '.jobs[] | select(.conclusion == "failure") | "\(.id) \(.name)"') | |
if [ -z "$FAILED_JOBS" ]; then | |
echo "No failed jobs found." | |
exit 1 | |
fi | |
echo "Failed Jobs:" | |
echo "$FAILED_JOBS" | |
# Save failed jobs info to a temporary file for later use | |
echo "$FAILED_JOBS" > failed_jobs.txt | |
- name: Download Logs for Failed Jobs | |
id: download_failed_logs | |
env: | |
GH_PAT: ${{ secrets.LOG_ACCESS_TOKEN }} | |
run: | | |
# Re-extract repository owner and name | |
REPO_OWNER=$(echo "${GITHUB_REPOSITORY}" | cut -d'/' -f1) | |
REPO_NAME=$(echo "${GITHUB_REPOSITORY}" | cut -d'/' -f2) | |
# Read failed jobs from previous step | |
FAILED_JOBS=$(cat failed_jobs.txt) | |
# Initialize log files | |
> full_build_log.txt | |
> failed_jobs_summary.txt | |
while read -r job_id job_name; do | |
# Sanitize job name by removing or replacing special characters | |
SANITIZED_JOB_NAME=$(echo "$job_name" | tr -cd '[:alnum:]_') | |
echo "- $job_name (Job ID: $job_id)" >> failed_jobs_summary.txt | |
echo "--- Logs for $job_name ---" >> full_build_log.txt | |
# Fetch the log URL for the specific job | |
LOG_URL=$(curl -s -H "Authorization: token $GH_PAT" \ | |
-H "Accept: application/vnd.github.v3+json" \ | |
"https://api.github.com/repos/$REPO_OWNER/$REPO_NAME/actions/jobs/$job_id" | jq -r '.logs_url') | |
echo "Job ID: $job_id" | |
echo "Job Name: $job_name" | |
echo "Log URL: $LOG_URL" | |
if [ "$LOG_URL" == "null" ] || [ -z "$LOG_URL" ]; then | |
echo "Failed to get logs URL for job $job_name (ID: $job_id)" >> full_build_log.txt | |
continue | |
fi | |
echo "Downloading logs for job '$job_name' from $LOG_URL" | |
# Download the job logs using job ID to ensure filename uniqueness | |
LOG_FILENAME="${job_id}.log" | |
curl -s -L -H "Authorization: token $GH_PAT" "$LOG_URL" -o "$LOG_FILENAME" | |
if [ -f "$LOG_FILENAME" ]; then | |
echo "Logs downloaded for job '$job_name' as $LOG_FILENAME." | |
echo "Contents of $LOG_FILENAME:" >> full_build_log.txt | |
cat "$LOG_FILENAME" >> full_build_log.txt | |
echo "--- End of logs for $job_name ---" >> full_build_log.txt | |
# Clean up the individual log file | |
rm "$LOG_FILENAME" | |
else | |
echo "Failed to download logs for job '$job_name'." >> full_build_log.txt | |
fi | |
done <<< "$FAILED_JOBS" | |
# Check if any logs were downloaded (excluding headers) | |
if grep -q "Contents of" full_build_log.txt; then | |
echo "Logs were successfully downloaded." | |
else | |
echo "No logs were downloaded for failed jobs." | |
exit 1 | |
fi | |
# For debugging: Output the contents of full_build_log.txt | |
echo "----- Full Build Log -----" | |
cat full_build_log.txt | |
echo "----- End of Full Build Log -----" | |
- name: Send Logs to DeepSeek API | |
env: | |
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }} | |
run: | | |
# Read the full build log and summary | |
BUILD_LOG=$(cat full_build_log.txt) | |
FAILED_JOBS_SUMMARY=$(cat failed_jobs_summary.txt) | |
# Truncate the log if necessary | |
MAX_LENGTH=50000 | |
if [ ${#BUILD_LOG} -gt $MAX_LENGTH ]; then | |
BUILD_LOG=$(echo "$BUILD_LOG" | head -c $MAX_LENGTH) | |
BUILD_LOG="${BUILD_LOG}... [Truncated]" | |
fi | |
# Prepare the payload for DeepSeek | |
SYSTEM_PROMPT="You are a helpful assistant that analyzes CI build logs to find errors, summarize them, and propose solutions if confident. Focus on the failed jobs and their specific errors." | |
USER_MESSAGE="Here's a summary of failed jobs:\n\n$FAILED_JOBS_SUMMARY\n\nHere are the CI build logs from the failed jobs (truncated if too long):\n\n$BUILD_LOG" | |
# Debugging: Output the messages being sent to DeepSeek | |
echo "## DeepSeek Payload" | |
echo "System Prompt: $SYSTEM_PROMPT" | |
echo "User Message: $USER_MESSAGE" | |
# Send the logs to DeepSeek API | |
RESPONSE=$(curl -s https://api.deepseek.com/chat/completions \ | |
-H "Content-Type: application/json" \ | |
-H "Authorization: Bearer $DEEPSEEK_API_KEY" \ | |
-d @- <<EOF | |
{ | |
"model": "deepseek-chat", | |
"messages": [ | |
{"role": "system", "content": "$SYSTEM_PROMPT"}, | |
{"role": "user", "content": "$USER_MESSAGE"} | |
], | |
"stream": false | |
} | |
EOF | |
) | |
# Extract DeepSeek's response | |
ASSISTANT_REPLY=$(echo "$RESPONSE" | jq -r '.choices[0].message.content // "No response from DeepSeek."') | |
# Output the analysis to the step summary | |
echo "## DeepSeek Analysis" >> $GITHUB_STEP_SUMMARY | |
echo "$ASSISTANT_REPLY" >> $GITHUB_STEP_SUMMARY | |
# Fallback: Also output the analysis to the standard log | |
echo "## DeepSeek Analysis" | |
echo "$ASSISTANT_REPLY" | |
# For debugging: Output the full response from DeepSeek | |
echo "## DeepSeek API Response" | |
echo "$RESPONSE" | |
# Optional: Upload the full_build_log.txt as an artifact for further inspection | |
- name: Upload Full Build Log | |
if: failure() # Upload only if previous steps failed | |
uses: actions/upload-artifact@v3 | |
with: | |
name: full_build_log | |
path: full_build_log.txt |