Install
openclaw skills install deploy-fault-analyzer部署故障分析及解决助手 — 接收日志文件(.json/.log)或报错文本,优先查询 MySQL 故障知识库,再自动分析故障原因并生成"部署故障分析及解决方案"Word文档,同时将问题录入Excel知识库并可同步到数据库。
openclaw skills install deploy-fault-analyzer当用户提供日志文件(.json .log)或包含 error/错误/异常/故障/报错/failed 等关键词的文字内容时,自动分析故障并生成 Word 文档。分析前必须优先查询 MySQL 故障知识库 fault_knowledge_base.fault_records,用历史案例提高定位准确率;所有新问题继续记录到 Excel 知识库中累积沉淀,并可同步到数据库。
⚠️ 交付规则: Word 报告生成后必须在同一条回复中附带故障分析摘要 + 发送 Word 文件给用户,不能只生成到本地而不发送。
满足以下任一条件即触发:
| 触发方式 | 判定标准 |
|---|---|
| 日志文件 | 用户上传/拖入 .json、.log 文件,或指明文件路径 |
| 报错文本 | 用户消息中包含 error/错误/异常/故障/报错/failed/failure/exception/失败 等关键词 |
| 主动请求 | 用户说"帮我分析这个故障"/"这是什么错"/"帮我看下日志"等 |
排除:用户只是在陈述中顺带提到"没有错误"/"没问题"/"成功了"时不触发。
故障知识库来自部署排期表第 10 个 sheet「故障库」,由 /data/work/scripts/sync_fault_knowledge_base.py 同步到 MySQL。
| 项 | 值 |
|---|---|
| 数据库 | fault_knowledge_base |
| 主表 | fault_records |
| 连接命令 | docker exec resource_pool_mysql mysql -upool_user -ppool_password_2024 fault_knowledge_base |
| 同步脚本 | /data/work/scripts/sync_fault_knowledge_base.py |
| 默认 Excel | /data/work/bom/基础平台部署排期表-2026年度.xlsx |
当用户说“同步故障库”“更新故障库”“把 Excel 故障库入库”等含义时,执行以下流程:
# 1. 先检查 Excel 可解析和有效行数
python3 /data/work/scripts/sync_fault_knowledge_base.py --dry-run
# 2. 正式同步,重复执行不会重复插入同一条故障
python3 /data/work/scripts/sync_fault_knowledge_base.py
# 3. 验证行数和去重
docker exec resource_pool_mysql mysql -upool_user -ppool_password_2024 fault_knowledge_base \
-e "SELECT COUNT(*) AS total, COUNT(DISTINCT content_hash) AS unique_hashes FROM fault_records;"
如数据库未初始化,先执行:
docker exec -i resource_pool_mysql mysql -uroot -proot_password_2024 \
< /data/work/sql/create_fault_knowledge_base.sql
| 字段 | 含义 | 查询用途 |
|---|---|---|
module_name | 模块 | 第一层收窄范围,优先从报错中的产品/组件/任务名推断 |
issue_type | 问题类型 | 第二层收窄范围,优先从故障现象推断 |
issue_description | 问题描述 | 核心相似度匹配字段 |
solution_summary | 解决方法概要 | 给出历史解决路径 |
product_version | 交付产品集版本 | 版本相关问题过滤 |
delivery_branch | 交付分支 | 架构/OS/分支差异过滤 |
resource_pool | 资源池 | 判断是否为特定现场案例 |
任何输入在处理前必须先存档,防止后续分析覆盖原始数据:
from pathlib import Path
from datetime import datetime
import shutil
RAW_DIR = Path.home() / '.hermes/skills/openclaw-imports/deploy-fault-analyzer/data/raw'
RAW_DIR.mkdir(parents=True, exist_ok=True)
# 文件输入 → 复制到 raw/
ts = datetime.now().strftime('%Y-%m-%d_%H%M%S')
raw_path = RAW_DIR / f'{ts}_{Path(src_file).name}'
shutil.copy2(src_file, raw_path)
# 文本输入 → 写入 raw/
raw_path = RAW_DIR / f'{ts}_user_input.txt'
raw_path.write_text(user_text, encoding='utf-8')
按文件格式选择解析策略:
1A. 结构化 JSON 日志(特征:顶层有 logs 数组,每项有 type/message 字段)
import json
with open(filepath) as f:
data = json.load(f)
logs = data['logs'] # 日志数组
# 提取字段:
# log['type'] → 'error'|'warning'|'info'|'success'
# log['message'] → 日志内容(含时间戳 `[HH:MM:SS]`)
# log['timestamp']→ ISO 时间(JSON字段,可能缺失)
# log['task_id'] → 任务ID(如 PREP_UPLOAD_33B3DC)
errors = [l for l in logs if l['type'] == 'error']
warnings = [l for l in logs if l['type'] == 'warning']
infos = [l for l in logs if l['type'] == 'info']
1B. 纯文本 .log 文件
# 逐行读取,按关键词提取 ERROR/WARN/CRITICAL/FAIL 行
# 使用正则: ^\d{4}-\d{2}-\d{2}.*(ERROR|WARN|CRITICAL|FAIL)
1C. 用户粘贴文本 → 直接作为分析输入
使用关键词正则匹配,按优先级从高到低匹配:
| 优先级 | 匹配模式(关键词) | 类别 |
|---|---|---|
| 1 | 日期格式不正确 日期格式错误 格式yyyy-mm-dd | 数据异常 — 日期格式 |
| 2 | 资源池名称不正确 名称不一致 sheet.*≠ | 配置错误 — 资源池名 |
| 3 | 未填写 必填项.*未 必填.*缺失 | 配置错误 — 必填字段 |
| 4 | 缺失 不存在 not found No such file | 资源不足 — 文件/目录 |
| 5 | 格式不正确.*IP IP地址格式 格式不合法 | 配置错误 — IP格式 |
| 6 | already installed is already 冲突 conflict | 服务异常 — 安装冲突 |
| 7 | 已终止 用户.*终止 用户.*取消 | 用户操作 — 部署终止 |
| 8 | Verifying verification 校验失败 验证失败 | 服务异常 — 验证超时 |
| 9 | 连接.*拒绝 Connection refused timeout unreachable | 网络/连接故障 |
| 10 | host.*not found DNS.*fail 解析失败 | 网络/连接故障 |
| 11 | bms.*pri hostname.*invalid | 配置错误 — 主机名 |
| 12 | Permission denied 权限不足 访问被拒 | 权限问题 |
| 13 | OOM out of memory 磁盘空间不足 No space | 资源不足 — 系统资源 |
| 14 | ModuleNotFoundError ImportError 依赖.*缺失 | 依赖缺失 |
实现:
def categorize_error_message(msg: str) -> str:
"""根据中文关键词自动归类错误"""
patterns = [
(r'日期格式不正确|日期格式错误|格式yyyy-mm-dd', '数据异常'),
(r'资源池名称不正确|名称不一致', '配置错误'),
(r'未填写|必填项.*未填|必填.*缺失', '配置错误'),
(r'缺失|不存在|not found|No such file|no such file', '资源不足'),
(r'格式不正确.*IP|IP地址格式|格式不合法', '配置错误'),
(r'already installed|is already|冲突|conflict', '服务异常'),
(r'已终止|用户.*终止|用户.*取消', '用户操作'),
(r'Connection refused|timeout|unreachable|连接.*拒绝', '网络/连接故障'),
(r'Permission denied|权限不足|访问被拒', '权限问题'),
(r'OOM|out of memory|磁盘空间不足|No space', '资源不足'),
(r'ModuleNotFoundError|ImportError|依赖.*缺失', '依赖缺失'),
]
for pattern, category in patterns:
if re.search(pattern, msg):
return category
return '待分类'
真实日志中同根因错误通常大量重复。必须先去重再分析:
已终止/用户终止)→ 不计入故障,仅在报告中标注message 中 ERROR 关键字后的核心文本去重fault_data 字典def deduplicate_errors(errors: list) -> list:
"""归并重复错误 → 根因列表"""
# 1. 分离用户操作
real_faults = [e for e in errors if '用户已终止' not in e['message'] and '用户终止' not in e['message']]
# 2. 按消息核心去重
seen = {}
for e in real_faults:
msg = e['message']
# 提取核心:ERROR 后的文本或消息中独特部分
core = re.sub(r'\[.*?\]', '', msg).strip()[:100]
cat = categorize_error_message(core)
key = f"{cat}:{core[:50]}"
if key not in seen:
seen[key] = {'count': 1, 'sample': e, 'category': cat, 'task_ids': {e.get('task_id','?')}}
else:
seen[key]['count'] += 1
seen[key]['task_ids'].add(e.get('task_id', '?'))
return list(seen.values())
在进入根因分析前,必须先用去重后的核心错误到 fault_knowledge_base.fault_records 查询历史案例。查询目标是找到相同或相近的 module_name、issue_type、issue_description,并把命中的 solution_summary 作为解决方案候选,而不是直接凭经验生成结论。
module_name优先从日志里的产品名、任务名、服务名、报错路径、部署阶段推断模块;无法确定时再不带模块查询。
高频 module_name 候选(按历史库频次和故障定位价值排序):
| 候选模块 | 常见关键词 |
|---|---|
基座 | bootstrap、基础包、yum、repo、平台基础服务、部署脚本公共步骤 |
主机交付问题 | 主机、host、IPMI、root密码、操作系统、lldp、网络不通 |
环境检查 | precheck、前置检查、端口检查、连通性检查、环境检查 |
块存储 | cinder、ceph、块存储、volume、存储池、磁盘 |
VPP | vpp、dpdk、转发、网卡绑定、HugePage |
监控 | telegraf、prometheus、grafana、监控节点、采集 |
SDN | sdn、neutron、网络控制、云内sdn、云间sdn |
门户 | portal、控制台、门户导入表、规格族 |
对象存储 | obs、s3、对象存储、bucket |
CSK / 容器 / 云原生 | csk、k8s、kube、容器、master、worker |
Trove | mysql、redis、trove、数据库服务 |
虚拟化 / 裸金属 | nova、ecs、bms、ironic、裸金属 |
DNS | dns、域名、解析失败 |
ECR | ecr、镜像仓库、registry |
网络交付问题 | 交换机、路由、vlan、外部网络、防火墙 |
issue_typeissue_type 用于第二层收窄。优先候选:
| 候选类型 | 常见关键词 |
|---|---|
脚本问题 | script、脚本、执行失败、返回码、命令失败、already installed |
环境问题 | 网络不通、端口不通、系统版本、依赖环境、服务状态 |
新建部署问题 | 新建、首次部署、安装、部署失败 |
新建验收问题 | 验收、验证、verification、检查失败 |
部署包问题 | 包缺失、rpm、tar、镜像、目录不存在、版本包 |
部署表问题 | Excel、部署表、必填、格式、资源池名称、规格族 |
网络交付问题 | DNS、路由、交换机、连通性、防火墙、VLAN |
主机交付问题 | 主机名、IPMI、操作系统、root密码、lldp、网卡 |
部署文档问题 | 文档、步骤、章节、说明不一致 |
操作问题 | 人工操作、输入错误、误操作、顺序错误 |
扩容问题 / 扩容验收问题 | 扩容、增加节点、扩容后验收 |
优化建议 / 其他问题 | 无明确故障但存在改进项 |
按“强约束 → 放宽”的顺序查询,避免一开始全库模糊搜索导致误命中:
module_name + issue_type + issue_description 关键词查询module_name + issue_description 查询issue_type + issue_description 查询issue_description 全文/LIKE 查询推荐命令模板:
docker exec resource_pool_mysql mysql -upool_user -ppool_password_2024 fault_knowledge_base -e "
SELECT
id, module_name, issue_type, found_at, product_version, delivery_branch,
LEFT(issue_description, 220) AS issue_preview,
LEFT(solution_summary, 220) AS solution_preview
FROM fault_records
WHERE module_name = '基座'
AND issue_type = '脚本问题'
AND (
issue_description LIKE '%bootstrap%'
OR solution_summary LIKE '%bootstrap%'
OR MATCH(issue_description, solution_summary, remark) AGAINST('bootstrap 脚本 失败' IN NATURAL LANGUAGE MODE)
)
ORDER BY
(module_name = '基座') DESC,
(issue_type = '脚本问题') DESC,
updated_at DESC
LIMIT 5;"
如果无法稳定判断模块,使用关键词全库检索:
docker exec resource_pool_mysql mysql -upool_user -ppool_password_2024 fault_knowledge_base -e "
SELECT id, module_name, issue_type,
LEFT(issue_description, 200) AS issue_preview,
LEFT(solution_summary, 200) AS solution_preview
FROM fault_records
WHERE issue_description LIKE '%关键报错%'
OR solution_summary LIKE '%关键报错%'
OR remark LIKE '%关键报错%'
LIMIT 10;"
module_name、issue_type、核心报错关键词均匹配,解决方案可作为首选,但仍要结合当前日志证据验证。从去重后的错误列表,为每个根因提取结构化信息:
提取方法:
| 提取项 | JSON 日志提取方法 | 文本日志提取方法 |
|---|---|---|
| 故障时间 | e['timestamp'](ISO格式)或从 message 中提取 [HH:MM:SS] | 正则 \d{4}-\d{2}-\d{2}.*\d{2}:\d{2}:\d{2} |
| 错误类型 | task_id + 异常描述拼接,如 CHECK_DEPLOY_F5B45D: checkTableImpl.DateFormatError | 异常类名或错误码 |
| 错误消息 | e['message'] 前200字符 | 紧跟在 ERROR 后的描述文本 |
| 出现次数 | dedup_count | 行数统计 |
| 堆栈跟踪 | 从 message 中的 checkTableImpl.py:318 等提取调用链 | Traceback 段落前20行 |
| 影响组件 | task_id 前缀 + message 中的模块路径(如 /home/conf/) | 服务名、主机名 |
| 影响范围 | 从部署流程推断(阻塞/警告/跳过) | 从错误推断 |
| 关联配置 | message 中的文件路径、参数名 | 配置文件引用 |
任务ID解析(JSON日志特有):
# task_id 前缀含义:
# PREP_UPLOAD_* → 上传检查步骤
# PREP_TASK_* → 前置任务
# CHECK_DEPLOY_* → 部署表检查
# PREP_DEPLOY_* → 部署准备
def parse_task_id(task_id: str) -> dict:
"""解析 task_id → 任务类型和阶段"""
if task_id is None:
return {'phase': '全局', 'type': '系统'}
parts = task_id.split('_')
if len(parts) >= 2:
return {'phase': parts[0], 'type': parts[1], 'id': parts[-1] if len(parts) > 2 else ''}
return {'phase': task_id, 'type': 'UNKNOWN'}
基于提取的信息,执行以下分析步骤:
错误分类:归入以下类别之一
历史案例对齐:把 Step 2.5 命中的故障库记录与当前日志证据逐项对比,确认模块、问题类型、关键词、版本/分支是否一致
因果关系链:从堆栈自底向上追溯,找到最初触发点
关键证据:摘录日志中能佐证根因的2-3条关键行
优先采用高置信历史案例中的 solution_summary,再结合当前日志、环境和标准排查路径细化;没有命中时才完全按通用框架生成方案。
| 错误类别 | 标准排查路径 | 典型解决步骤 |
|---|---|---|
| 网络/连接 | telnet/ping/curl 测试连通性 → 防火墙规则 → DNS解析 | 检查目的端口可达性、放行防火墙规则、修正IP配置 |
| 配置错误 | 对比配置模板 → 校验参数值 | 修正配置文件、重启服务、验证参数生效 |
| 资源不足 | free/df -h/ulimit -n 检查 → top 定位占用进程 | 清理磁盘、扩容、调大 ulimit、重启服务释放泄漏 |
| 服务异常 | systemctl status → journalctl 查崩溃原因 | 重启服务、检查依赖服务状态、排查 OOM killer |
| 权限问题 | ls -l / id / 检查 sudo | 修正文件权限(chmod/chown)、添加 sudo 授权 |
| 依赖缺失 | rpm -qa / pip list → 对比版本要求 | 安装缺失包、版本降级/升级 |
| 数据异常 | 检查数据完整性 → 对比 schema | 数据修复、迁移、回滚 |
输出解决方案时遵循的结构:
【解决方案】
1. 排查步骤
- 第一步:xxx
- 第二步:xxx
2. 修复操作
- 操作命令(可复制执行)
3. 验证方法
- 验证命令 + 预期结果
4. 回滚方案(如适用)
if len(root_causes) == 1:
mode = 'single' # 单故障 → 标准五段式报告
else:
mode = 'multi' # 多故障 → 总览 + 逐项分析 + 综合建议
生成 部署故障分析及解决方案_YYYY-MM-DD_HHmmss.docx,标准五段式结构(同原版)。
当 mode == 'multi' 时,增加总览页和综合建议:
封面
├── 总体概况表
│ ├── 文档编号 / 分析时间 / 部署目标 / 部署计划
│ ├── 总日志数 / 总错误数 / 归并根因数
│ └── 最高故障级别
├── 错误全景图(表格)
│ ├── # / 故障 / 类别 / 级别 / 出现次数
├── 逐项详细分析
│ ├── 故障 1:...(完整五段式)
│ ├── 故障 2:...
│ └── ...
└── 综合建议与整改清单
└── 按优先级排列的改进项
重点:每个故障页有明确的 故障 N / 总N 标注,分隔线分隔。
from scripts.generate_report import generate_fault_report, generate_multi_fault_report, append_to_excel
# 单故障
if len(faults) == 1:
docx_path = generate_fault_report(output_path, faults[0])
# 多故障
else:
docx_path = generate_multi_fault_report(output_path, faults)
# 批量追加 Excel(所有故障都写一行)
for fault in faults:
append_to_excel(xlsx_path, {...})
文档保存路径: ~/.hermes/skills/openclaw-imports/deploy-fault-analyzer/output/
⚠️ 分析完成后必须在同一条回复中做两件事:
message 工具发送文件给用户# 发送 .docx 文件给用户(以当前会话的 chat 通道发送)
# 使用 message 工具: action=send, filePath=docx_path, caption='部署故障分析报告'
发送规则:
message 工具发送 Word 文件NO_REPLY 避免重复消息摘要模板(回复消息中贴出):
🔍 部署故障分析结果
概览:N条日志,M条错误 → 归并 K 个根因
| # | 故障 | 类别 | 级别 | 次数 | 定位 |
F1 | xxx | 数据异常 | P1 | 44 | xxx
F2 | ...
🎯 核心结论:xxx
📄 Word 报告已同步发送,请查收。
from docx import Document
from docx.shared import Inches, Pt, Cm, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
from datetime import datetime
import os
def generate_fault_report(output_path, fault_data):
"""
fault_data = {
'fault_time': '2026-05-09 10:23:45',
'error_type': 'ConnectionError',
'error_message': '...',
'stack_trace': '...',
'affected_component': 'nova-api',
'affected_scope': 'AZ-1 计算节点',
'related_config': '/etc/nova/nova.conf',
'error_category': '网络/连接故障',
'fault_level': 'P1',
'root_cause': '...',
'evidence_lines': ['...', '...'],
'solution_steps': [...],
'verification': '...',
'prevention': '...',
}
"""
doc = Document()
# 标题
title = doc.add_heading('部署故障分析及解决方案', level=0)
title.alignment = WD_ALIGN_PARAGRAPH.CENTER
# 文档信息表
doc.add_paragraph('')
info_table = doc.add_table(rows=3, cols=2, style='Light Grid Accent 1')
info_data = [
('文档编号', f"DOC-{datetime.now().strftime('%Y%m%d')}-{fault_data.get('fault_level','P2')}"),
('分析时间', datetime.now().strftime('%Y-%m-%d %H:%M:%S')),
('故障级别', fault_data.get('fault_level', 'P2')),
]
for i, (k, v) in enumerate(info_data):
info_table.rows[i].cells[0].text = k
info_table.rows[i].cells[1].text = v
doc.add_paragraph('')
# 一、故障概述
doc.add_heading('一、故障概述', level=1)
doc.add_paragraph(f"故障时间:{fault_data.get('fault_time', '未知')}")
doc.add_paragraph(f"影响组件:{fault_data.get('affected_component', '未知')}")
doc.add_paragraph(f"影响范围:{fault_data.get('affected_scope', '未知')}")
doc.add_paragraph(f"错误类型:{fault_data.get('error_type', '未知')}")
# 二、故障详情
doc.add_heading('二、故障详情', level=1)
doc.add_heading('错误消息', level=2)
doc.add_paragraph(fault_data.get('error_message', ''))
if fault_data.get('stack_trace'):
doc.add_heading('堆栈跟踪', level=2)
p = doc.add_paragraph()
p.style = doc.styles['No Spacing']
for line in fault_data['stack_trace'].split('\n')[:20]:
doc.add_paragraph(line, style='No Spacing')
if fault_data.get('related_config'):
doc.add_paragraph(f"关联配置:{fault_data['related_config']}")
# 三、根因分析
doc.add_heading('三、根因分析', level=1)
doc.add_paragraph(f"错误分类:{fault_data.get('error_category', '未分类')}")
doc.add_paragraph(f"根本原因:{fault_data.get('root_cause', '待进一步分析')}")
if fault_data.get('evidence_lines'):
doc.add_heading('关键证据', level=2)
for i, line in enumerate(fault_data['evidence_lines'], 1):
doc.add_paragraph(f"{i}. {line}")
# 四、解决方案
doc.add_heading('四、解决方案', level=1)
for step in fault_data.get('solution_steps', []):
doc.add_paragraph(step, style='List Bullet')
if fault_data.get('verification'):
doc.add_heading('验证方法', level=2)
doc.add_paragraph(fault_data['verification'])
# 五、预防措施
doc.add_heading('五、预防措施', level=1)
doc.add_paragraph(fault_data.get('prevention', '—'))
doc.save(output_path)
return output_path
如果日志/文本中找不到明确的错误信息,不要强行编造分析结果。此时:
流程:
已终止/用户终止)→ 不计入故障清单module_name、issue_type、issue_description 查询 MySQL 故障知识库以下是从实际部署日志中提取的典型错误模式,用于指导分类:
模式 1 — 部署表日期格式错误(44条 → 1个根因)
[CHECK_DEPLOY_F5B45D] 导入表:日期格式不正确45677.7661111, 格式yyyy-mm-dd hh:mm:ss
→ 类别:数据异常 | 根因:Excel日期序列号未转换 | 级别:P1
模式 2 — 资源池名称不一致(35条 → 1个根因)
[CHECK_DEPLOY_F5B45D] 资源池名称不正确 sheet11:呼和交付验证云池; sheet1:呼和国产化...
→ 类别:配置错误 | 根因:多sheet资源池名未统一 | 级别:P1
模式 3 — 部署包目录缺失(6条 → 1个根因)
[PREP_UPLOAD_33B3DC] /opt/ImageManager: 缺失
→ 类别:资源不足 | 根因:部署包未上传或不在此次部署范围 | 级别:P1
模式 4 — RPM 安装误报(1条 → 不阻塞)
[PREP_TASK_53658B] rpm -ivh → already installed → exit code ≠ 0
→ 类别:服务异常 | 根因:安装脚本未处理"已安装"状态 | 级别:P3
以下消息类型不归类为故障,仅在报告中备注:
用户已终止部署流程 → 用户主动操作部署已自动暂停,等待用户确认 → 流程机制,非故障本身用户确认继续 → 流程恢复所有输出统一在 skill 目录下:
~/.hermes/skills/openclaw-imports/deploy-fault-analyzer/
├── SKILL.md
├── output/ # Word 文档输出
├── data/
│ ├── problems.xlsx # Excel 知识库
│ └── raw/ # 原始输入存档
MySQL 故障知识库不在 skill 目录下,固定查询 fault_knowledge_base.fault_records;Excel 到数据库的同步脚本固定为 /data/work/scripts/sync_fault_knowledge_base.py。
| 用户说什么 | 你做什么 |
|---|---|
| 上传 xxxx.log | 读取 → 提取ERROR行 → 推断 module_name/issue_type → 查询 MySQL 故障库 → 分析 → 生成 docx + 写入 xlsx → 发送 docx 给用户 + 贴摘要 |
| 粘贴报错文本 | 同日志流程,必须先查 fault_knowledge_base.fault_records 再给结论 |
| "帮我看下这个报错" | 识别附带文本 → 查 MySQL 历史案例 → 分析 → 生成 docx + 写入 xlsx → 发送 docx 给用户 + 贴摘要 |
| "查一下之前类似问题" | 优先查询 fault_knowledge_base.fault_records,按模块、问题类型、问题描述返回历史案例 |
| "这个错之前遇到过吗" | 在 MySQL 故障库中搜索匹配的 issue_description 和 solution_summary |
| "同步故障库" / "更新故障库" | 运行 /data/work/scripts/sync_fault_knowledge_base.py --dry-run,确认后运行正式同步脚本 |