Appearance
title: Jenkins 构建失败自动分析 description:
Jenkins 构建失败自动分析(v1.5.0)
概述
当 Jenkins trigger job 构建失败时,自动完成:
- 解析构建日志,定位失败根因(第一个 FAILURE 的 sub-job)
- 获取编译服务器 IP、workspace 路径、错误文件
- SSH 到编译服务器,fetch 完整 git 历史,git log 找到引入错误的 commit
- git show 获取 commit 作者、邮箱、时间、message、Change-Id
- 发送企业微信 webhook 通知
- 输出结构化分析报告
触发条件
Jenkins trigger job 构建失败后,PostBuildScript 发 webhook → Hermes 自动执行分析。
新项目接入(无需修改 Hermes 配置)
新项目只需要在 Jenkins trigger job 里配置 PostBuildScript:
配置位置: Jenkins job → 配置 → 构建后操作 → PostBuildScript → 勾选「所有结果(Always)」
curl 命令(通用,任何项目都能用):
bash
HERMES_URL="http://192.168.100.206:8644/webhooks/jenkins-monitor"
curl -s -X POST "${HERMES_URL}" \
-H "Content-Type: application/json" \
-d "{\"job_name\":\"${JOB_NAME}\",\"build_number\":\"${BUILD_NUMBER}\",\"build_result\":\"${BUILD_RESULT}\",\"build_url\":\"${BUILD_URL}\",\"build_user\":\"${BUILD_USER:-unknown}\",\"project\":\"${J_PROJECT:-}\",\"branch\":\"${GIT_BRANCH:-}\",\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}"
路径信息自动从 consoleText 提取,Hermes 不需要任何配置修改:
- 编译服务器 IP:从 sub-job consoleText 第一行
Building remotely on <IP>提取 - workspace 路径:从 sub-job consoleText 的
in workspace /path提取 - 代码子目录:从报错文件绝对路径中拆分
Webhook 传入参数
| 参数 | 说明 | 来源 |
|---|---|---|
job_name | Jenkins job 名称 | ${JOB_NAME} |
build_number | 构建号 | ${BUILD_NUMBER} |
build_result | FAILURE / SUCCESS | ${BUILD_RESULT} |
build_url | 构建 URL | ${BUILD_URL} |
build_user | 触发者 | ${BUILD_USER:-unknown} |
project | 项目名(可选) | ${J_PROJECT:-} |
branch | Git 分支(可选) | ${GIT_BRANCH:-} |
timestamp | ISO 时间戳 | $(date -u +%Y-%m-%dT%H:%M:%SZ) |
环境配置速查
| 配置项 | 值 |
|---|---|
| Jenkins 地址 | http://192.168.100.207:8080 |
| Jenkins 认证 | -u jenkins:ontim123! |
| Hermes Webhook | http://192.168.100.206:8644/webhooks/jenkins-monitor |
| 编译服务器 SSH | ontim / ontim123!,必须用 /usr/bin/python3 |
| 企业微信 Webhook | https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=806d31d0-352e-447a-90d9-d8a624ad589f |
完整分析流程
第 1 步:获取 trigger consoleText,提取基础信息
bash
curl -s -k --noproxy '*' -u jenkins:ontim123! \
"http://192.168.100.207:8080/job/{job_name}/{build_number}/consoleText"
提取字段:
| 字段 | 搜索关键词 | 示例 |
|---|---|---|
| 项目名 | J_Project= | SM68B |
| 发布版本 | J_RLSVer= | 25131 |
| 触发者 | Started by user | zhangwanliang |
| 触发时间 | console 头部时间戳 | Apr 29, 2026, 3:27 PM |
| 构建节点 | Building remotely on | 192.168.100.157 (trigger) |
| 分支名 | remotes/origin/ | b/sm6650/do_25131 |
| 第一个 FAILURE | Finished Build.*FAILURE | sm6650_bp → FAILURE |
关键规则:第一个 FAILURE 的 sub-job 是根因,后续 ABORTED 是被牵连的。
trigger consoleText 示例(尾部):
Finished Build : [... Job: sm6650_bp] of Job : sm6650_bp with status : FAILURE at 15:35:31
Finished Build : [... Job: sm6650_sys_userdebug] of Job : sm6650_sys_userdebug with status : ABORTED at 15:35:33
Finished Build : [... Job: sm6650_vnd_userdebug] of Job : sm6650_vnd_userdebug with status : ABORTED at 15:35:33
第 2 步:获取 sub-job consoleText,获取编译服务器信息
bash
curl -s -k --noproxy '*' -u jenkins:ontim123! \
"http://192.168.100.207:8080/job/{sub_job_name}/{sub_build_number}/consoleText"
sub-job consoleText 第一行包含关键信息:
Building remotely on 192.168.100.167 (amss) in workspace /home/ontim/BP_SPACE
⚠️ 编译服务器 IP 和 workspace 路径必须以 sub-job consoleText 为准,不要用记忆里的旧 IP。
第 3 步:从 sub-job consoleText 尾部提取编译错误
错误模式:
fatal error: 'xxx.h' file not found
make: *** [...path.../File.obj] Error 1
error 7000: Failed to execute command
error F002: Failed to build module
- Failed -
提取三个关键信息:
- 报错文件完整路径
- 错误类型(如
fatal error: 'Uefixx.h' file not found) - 行号
第 4 步:SSH 到编译服务器,fetch 完整历史 + git log
⚠️ 重要:构建时 repo 可能使用 --depth=1 shallow clone,直接 git log 只能看到 1-2 条历史。需要先 fetch 完整历史。
SSH 参数:
- IP:从 sub-job consoleText 获取
- 用户:
ontim - 密码:
ontim123! - Python:必须用
/usr/bin/python3 - SSH 超时:10-30s,
git fetch超时:60-90s
Step 4a:获取分支名
从 sub-job consoleText 搜索 remotes/origin/ 获取当前分支。
Step 4b:定位正确的 git repo
⚠️ 关键陷阱:BP workspace 下有多个独立的 .git 仓库,路径不能搞错!
从报错文件绝对路径反推 git root:
/home/ontim/BP_SPACE/SM6650_do/BP/BOOT.MXF.2.1/boot_images/boot/QcomPkg/.../ChargerLibCommon.c
git root = /home/ontim/BP_SPACE/SM6650_do/BP/BOOT.MXF.2.1/boot_images(.git 在 boot_images 目录下)
验证 git repo 是否正确:
python
stdin, stdout, stderr = client.exec_command(f'test -d "{git_root}/.git" && echo "GIT OK" || echo "NOT A REPO"')
Step 4c:fetch 完整历史
python
cmds = [
f'test -d "{git_root}/.git" && echo "GIT_OK" || echo "NOT_GIT"',
f"cd {git_root} && rm -f .git/index.lock",
f"cd {git_root} && timeout 90 git fetch --depth=500 origin {branch}",
f"cd {git_root} && git log --oneline | wc -l",
]
⚠️ 关键点:
- 必须用具体分支名(如
b/sm6650/do_25131),不能用HEAD git fetch --depth=500比git fetch --unshallow快很多
Step 4d:git log 查找目标 commit
python
cmd = f"cd {git_root} && git log --oneline -10 -- {rel_filepath}"
如果文件 git log 为空(文件可能是生成的):
python
cmd = f"cd {git_root} && git log --oneline -10 -- {dirname}"
第 5 步:git show 获取 commit 详情
python
cmd = f"cd {git_root} && git show {commit_hash} --format='%H%n%an%n%ae%n%ad%n%s%n%B' -s"
输出格式:
9bf7c5c25077bd55c3853792ad445cdc24c0307c ← 完整 commit hash
wanliang.zhang ← 作者名
wanliang.zhang@chino-e.com ← 作者邮箱
Tue Feb 10 09:52:44 2026 +0800 ← 提交时间
[Bsp][25131][Charger]... ← commit message
[Description]
Force charger related temp normal
Change-Id: I6b0789f498b1d97c204ac17a7bd230f066f04601
第 6 步:发送企业微信 Webhook 通知
python
import requests
webhook_url = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=806d31d0-352e-447a-90d9-d8a624ad589f"
content = f"""🚨 **构建失败分析**
**基本信息**
- Trigger:{job_name}
- 项目名:{j_project}
- 构建号:#{build_number}
- 触发者:{build_user}
- 触发时间:{trigger_time}
- Build URL:http://192.168.100.207:8080/job/{job_name}/{build_number}/
**失败范围**
{failed_list}
**编译错误**
- 报错文件:{error_file}
- 错误类型:{error_type}
**根因定位**
- Commit:{commit_hash}
- 作者:{author_name}({author_email})
- 提交时间:{commit_time}
- Commit Message:{commit_msg}
**修复建议**
{fix_suggestion}"""
payload = {"msgtype": "markdown", "markdown": {"content": content}}
r = requests.post(webhook_url, json=payload)
print(r.json()) # 期望 {"errcode": 0, "errmsg": "ok"}
第 7 步:输出结构化分析报告
🚨 **构建失败分析**
**基本信息**
- Trigger:SM68B_do_smt_trigger
- 项目名:SM68B
- 构建号:#283
- 触发者:zhangwanliang
- 触发时间:2026-04-29 15:27
- Build URL: http://192.168.100.207:8080/job/SM68B_do_smt_trigger/283/
**失败范围**
- sm6650_bp → FAILURE(根因)
- sm6650_sys_userdebug → ABORTED(被牵连)
- sm6650_vnd_userdebug → ABORTED(被牵连)
**编译错误**
- 报错文件:ChargerLibCommon.c 第 88 行
- 错误类型:fatal error: 'Uefixx.h' file not found
**根因定位**
- Commit:9bf7c5c25
- 作者:wanliang.zhang(wanliang.zhang@chino-e.com)
- 提交时间:2026-02-10 09:52
- Commit Message:[Bsp][25131][Charger]...
**修复建议**
将 ChargerLibCommon.c 第 88 行的 #include <Uefixx.h> 改回 #include <Uefi.h>,或确认该 include 是否应删除。
注意事项
- 编译服务器 IP 以 sub-job consoleText 第一行
Building remotely on <IP>为准 - workspace 路径以 sub-job consoleText 中的
in workspace /path为准 git fetch必须用具体分支名,不能用 HEAD- 如果
git log结果少于 10 条,说明 fetch 没生效,重新执行 Step 4c - 如果报错文件 git log 为空,对文件所在目录执行
git log - SSH 使用 Python
paramiko,不要用sshpass - Jenkins API 端口:trigger 和 sub-job 都用
192.168.100.207:8080 - 企业微信 webhook
errcode: 0表示发送成功 - 构建成功时(build_result=SUCCESS):只返回简短确认,不需要分析
常见问题处理
| 问题 | 解决方案 |
|---|---|
git fetch 报 Couldn't find remote ref HEAD | 直接用具体分支名,如 b/sm6650/do_25131 |
git fetch 报 index.lock 冲突 | 先 rm -f .git/index.lock |
git log 只有 1-2 条 | 执行 git fetch --depth=500 origin <branch> |
git 命令报 not a git repository | git root 路径不对,需从报错文件路径反推 .git 所在目录 |
PostBuildScript funclib.sh 找不到 | 工作目录导致相对路径解析失败,文件实际存在,不影响分析流程 |