Rust AI 运维助手:让 agent 读懂系统日志并给出排障建议的工程方案

📅 2026/7/22 1:40:30
Rust AI 运维助手:让 agent 读懂系统日志并给出排障建议的工程方案
Rust AI 运维助手让 agent 读懂系统日志并给出排障建议的工程方案一、半夜被叫醒的瞬间我决定做一个能自己排障的 agent做过后端或者运维的兄弟应该都有这种体验凌晨两三点手机亮了你一看——生产环境磁盘使用率 95%。迷迷糊糊爬起来SSH 连到服务器du -sh /* | sort -rh、journalctl -u nginx --since 1 hour ago、一通排查最后发现是某个服务的日志轮转没配好access.log写了 80G。这个流程里90% 的操作是有规律的。读日志、找关键词、根据经验匹配到已知的故障模式然后再给出修复命令。那为什么不让程序来做这些呢当然可以但传统做法是写一堆if-else匹配规则维护起来噩梦一样。所以我把思路转向了 LLM让 AI 来理解日志、分析根因、给出建议。上图是我设计的完整流程。核心思路不是让 AI 替代运维而是做第一道过滤——把那些有规律可循的故障自动处理掉真正的疑难杂症才转到人工。二、日志采集与上下文构建 —— agent 的眼睛agent 要读懂日志首先得有高质量的上下文。直接扔 500MB 的日志文件给 LLM 是不现实的必须先做采集和筛选。我最开始用Command直接调 shell 命令后来发现这样错误处理太脆弱改成了用std::process::Command加完善的超时和错误捕获。use std::process::Command; use std::time::Duration; use anyhow::{Context, Result}; /// 日志采集器 —— agent 的眼睛 /// 负责从目标服务器拉取最近一段时间的系统日志 pub struct LogCollector { /// SSH 连接目标格式: userhost target: String, /// 单次采集的超时时间 timeout: Duration, /// 最大返回行数防止日志过大撑爆内存 max_lines: usize, } impl LogCollector { /// 创建新的日志采集器 /// target: 远程主机地址如 root10.0.1.100 /// timeout_secs: 单次采集超时秒数建议 30 秒以内 /// max_lines: 最大日志行数超过会被截断 pub fn new(target: str, timeout_secs: u64, max_lines: usize) - Self { Self { target: target.to_string(), timeout: Duration::from_secs(timeout_secs), max_lines, } } /// 采集 systemd journal 日志 /// service: 服务名如 nginx 或 docker /// since: 时间范围如 1 hour ago 或 30 minutes ago pub fn collect_journal(self, service: str, since: str) - ResultString { // 构造 journalctl 命令 // -u: 指定服务单元 -S: 起始时间 -n: 最大行数 --no-pager: 禁用分页 let output Command::new(ssh) .arg(self.target) .arg(format!( journalctl -u {} -S {} -n {} --no-pager 21, service, since, self.max_lines )) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) .output() .context(format!(无法连接到主机 {}, self.target))?; if !output.status.success() { // 如果命令执行失败把 stderr 内容也返回供分析 let err_msg String::from_utf8_lossy(output.stderr); return Ok(format!([采集错误] journalctl 执行失败: {}, err_msg)); } let log_content String::from_utf8_lossy(output.stdout).to_string(); Ok(log_content) } /// 采集系统级错误日志dmesg、/var/log/syslog 等 /// 用于排查硬件故障、内核 OOM 等问题 pub fn collect_system_errors(self) - ResultString { // 组合多条命令一次性采集多种系统日志 let commands format!( dmesg --levelerr,warn | tail -n {}; echo ---SYSLOG---; \ tail -n {} /var/log/syslog 2/dev/null || echo syslog 不可读, self.max_lines / 2, self.max_lines / 2 ); let output Command::new(ssh) .arg(self.target) .arg(commands) .output() .context(采集系统错误日志失败)?; Ok(String::from_utf8_lossy(output.stdout).to_string()) } }这里有两个设计细节值得说。一是max_lines的限制——你没法保证生产环境日志不会瞬间膨胀到几万行所以必须在采集层面就做截断。二是错误处理——即使journalctl执行失败了也要把 stderr 的内容返回因为命令失败本身就是一条有价值的信息LLM 可以从错误输出里判断是权限问题还是服务不存在。三、Prompt 工程 —— agent 的大脑日志拿到了接下来就是构建发给 LLM 的 prompt。这一块我也是踩了好多坑才找到感觉的。最早我写 prompt 特别随意就是这是日志你看看有什么问题结果 LLM 开始天马行空地猜测有时候甚至建议我升级内核来解决一个配置文件写错了的问题。后来我总结了一个结构化 prompt 模板效果好了很多/// LLM 分析引擎 —— agent 的大脑 pub struct AnalysisEngine { /// OpenAI 兼容的 API 端点可以是本地部署的 vLLM api_url: String, /// API 密钥 api_key: String, /// 使用的模型名称如 gpt-4o 或本地部署的 qwen-72b model: String, /// HTTP 客户端复用连接池 client: reqwest::Client, } impl AnalysisEngine { /// 构建结构化的分析 prompt /// log_content: 过滤后的日志内容 /// host_info: 主机基本信息OS 版本、内核版本等 fn build_prompt(self, log_content: str, host_info: str) - String { format!( r#你是一名资深 Linux 运维工程师。请根据以下系统日志进行故障分析。 ## 主机信息 {} ## 系统日志 {} ## 分析要求 请严格按照以下格式输出 1. **故障类型**[磁盘满/内存泄漏/服务崩溃/网络故障/权限问题/配置错误/其他] 2. **根因分析**用 2-3 句话说明导致故障的直接原因 3. **影响范围**该故障影响了哪些服务/功能 4. **修复建议**按优先级列出 3 条可执行的修复步骤每条必须是具体的命令或操作 5. **预防措施**给出 2 条避免再次发生同类问题的建议 6. **置信度**[high/medium/low] 请基于日志中的确定信息进行分析不要做无依据的推测。如果日志信息不足以判断根因请在置信度中标注 low 并说明需要哪些额外信息。 #, host_info, log_content ) } /// 调用 LLM 进行分析 /// 返回格式化的分析结果文本 pub async fn analyze(self, log_content: str, host_info: str) - ResultString { let prompt self.build_prompt(log_content, host_info); let body serde_json::json!({ model: self.model, messages: [ { role: system, content: 你是一名专业的运维工程师请用简洁专业的中文回答问题。 }, { role: user, content: prompt } ], temperature: 0.3, // 低温度保证输出稳定可复现 max_tokens: 1024 // 限制输出长度防止废话太多 }); // 发送请求到 LLM 服务 let resp self.client .post(self.api_url) .header(Authorization, format!(Bearer {}, self.api_key)) .json(body) .timeout(Duration::from_secs(30)) // LLM 调用也设超时 .send() .await?; let json: serde_json::Value resp.json().await?; let analysis json[choices][0][message][content] .as_str() .unwrap_or(LLM 返回了空内容) .to_string(); Ok(analysis) } }prompt 里最关键的改造是要求 LLM 输出固定格式。故障类型让我可以按类型路由到不同的自动修复策略置信度决定了是自动执行还是转人工。temperature: 0.3也是特意调低的——在排障场景我要的是稳定可复现的判断不是创意写作。四、自动修复与告警分流 —— agent 的手LLM 返回分析结果后最后一步是根据置信度分流。这块我用了一个简单的规则引擎高置信度直接执行、中置信度生成工单、低置信度告警推送。use serde::Deserialize; /// LLM 返回的结构化分析结果 #[derive(Debug, Deserialize)] struct AnalysisResult { fault_type: String, // 故障类型 confidence: String, // 置信度: high/medium/low fix_commands: VecString, // LLM 建议的修复命令 } /// 自动修复执行器 —— agent 的手 /// 根据置信度决定是自动执行还是转人工 pub struct AutoFixer { collector: LogCollector, // 复用日志采集器来执行修复命令 } impl AutoFixer { /// 根据分析结果执行对应的修复策略 pub async fn execute(self, result: AnalysisResult) - ResultString { match result.confidence.as_str() { high { // 高置信度自动执行修复命令 println!([AUTO-FIX] 置信度高自动执行修复); for cmd in result.fix_commands { // 安全白名单检查只允许执行预设的安全命令 if !self.is_safe_command(cmd) { println!([WARN] 拒绝执行不安全命令: {}, cmd); continue; } // 在实际项目中这里应该 SSH 到目标机器执行 println!([EXEC] 执行: {}, cmd); } 自动修复已执行.to_string() } medium { // 中置信度生成工单附上 LLM 的建议 println!([TICKET] 生成运维工单附带 LLM 建议); format!( 已自动生成工单。建议修复方案{}, result.fix_commands.join(; ) ) } _ { // 低置信度推送到 oncall 人员 println!([ALERT] 推送告警到 oncall 值班人员); 已推送告警通知等待人工介入.to_string() } } } /// 命令安全白名单检查 /// 只允许 systemctl restart/reload、清理日志等安全的运维操作 fn is_safe_command(self, cmd: str) - bool { let safe_prefixes [ systemctl restart , systemctl reload , systemctl status , journalctl --vacuum-size, docker restart , logrotate , rm -rf /var/log/, // 注意只清理日志目录 ]; safe_prefixes.iter().any(|prefix| cmd.starts_with(prefix)) } }safe_prefixes这个白名单设计非常重要。在实际生产环境中不能让 LLM 直接执行任意 shell 命令。你永远不知道模型会在什么情况下幻觉出一个rm -rf /。白名单既能在大多数常规场景下自动修复又能防止灾难性操作。五、总结这篇文章复盘了用 Rust 构建 AI 运维助手的完整方案日志采集用std::process::Command SSH 拉取远端日志Prompt 工程用结构化模板引导 LLM 输出固定格式的分析结果自动修复用置信度 命令白名单实现安全可靠的自动执行。从自学编程的角度看这类工程化项目是成长最快的。它逼着你去理解 Linux 系统管理、网络通信、API 设计、安全考量还有最重要的——生产环境的真实边界情况。下个月我打算把这个 agent 接到我们的企业微信机器人上到时候再写一篇线上运行的效果复盘。