从零构建 AI 驱动的日志监控自愈系统

从零构建 AI 驱动的日志监控自愈系统

背景

AI 销售工作台(ai_sales_server)是一个基于 Eino 框架的多 Graph Agent 编排系统,调用链路复杂------多个 Graph 编排、外部 LLM 调用、MCP 工具链。线上错误依赖人工巡检日志平台发现,从发现到修复响应慢。

目标:构建一套全自动的"错误发现→根因分析→代码修复→MR 提交"闭环系统。

架构设计

scss 复制代码
┌─────────────────────────────────────────────────────┐
│                  Spirit Watcher                       │
│                                                      │
│  ┌──────────┐   ┌──────────┐   ┌──────────────────┐ │
│  │ 日志轮询  │──▶│ 指纹去重  │──▶│  SQLite 存储     │ │
│  └──────────┘   └──────────┘   └──────────────────┘ │
│       │                               │              │
│       ▼                               ▼              │
│  ┌──────────┐   ┌──────────┐   ┌──────────────────┐ │
│  │ meta-mcp │   │ Claude   │──▶│  自动修复         │ │
│  │ (TLS日志) │   │ 根因分析  │   │  worktree+patch  │ │
│  └──────────┘   └──────────┘   └──────────────────┘ │
│                                       │              │
│                                       ▼              │
│                               ┌──────────────────┐   │
│                               │  GitLab MR 创建   │   │
│                               └──────────────────┘   │
└─────────────────────────────────────────────────────┘

核心模块

1. 日志轮询

通过 MCP 协议对接公司日志平台(火山云 TLS),每 30 秒拉取 ERROR 级别日志。

go 复制代码
func pollLogs(ctx context.Context, topicID string) {
    now := time.Now().UnixMilli()
    startTime := now - 30*1000

    result, err := mcpClient.CallTool("search_log", map[string]any{
        "topic_id":   topicID,
        "query":      "ERROR",
        "start_time": startTime,
        "end_time":   now,
        "limit":      100,
    })
    // 解析日志,过滤 level=error,存入 incident
}

调用链路:get_log_topics(服务名) → 获取 topic ID → search_log(topic_id, query) → 返回日志条目。

MCP 客户端封装了 JSON-RPC over HTTP + SSE 响应解析 + session 管理,对外只暴露 CallTool(name, args) 一个方法。

2. 指纹归一化去重

同一个错误可能因为动态内容(IP、时间戳、trace ID)不同而看起来不一样。归一化后再算 SHA-256 指纹:

go 复制代码
func normalize(msg string) string {
    msg = regexpIP.ReplaceAllString(msg, "<IP>")
    msg = regexpTimestamp.ReplaceAllString(msg, "<TIME>")
    msg = regexpUUID.ReplaceAllString(msg, "<UUID>")
    msg = regexpNumericID.ReplaceAllString(msg, "<ID>")
    msg = regexpHex.ReplaceAllString(msg, "<TRACE>")
    return msg
}

效果:

arduino 复制代码
"dial tcp 10.0.1.5:3306: connection refused at 2026-05-21T22:58:00"
"dial tcp 10.0.1.8:3306: connection refused at 2026-05-22T01:30:15"
→ 归一化后相同 → 同一个 incident,hit_count +1

3. Claude 根因分析

复用项目已有的 Eino Agent 能力,直接调 chatModel.Generate 做分析:

go 复制代码
chatModel, _ := agentsales.NewChatModelFromEnv(ctx)
resp, _ := chatModel.Generate(ctx, []*schema.Message{
    schema.UserMessage(prompt),
})

分析结果结构化输出:

json 复制代码
{
  "diagnosis": "数据库连接池耗尽,max_open_conns 设置过小",
  "risk_level": "A",
  "fix_plan": "将 max_open_conns 从 10 调整为 50"
}

风险等级:

  • A --- 确定性修复(配置错误、空指针防护),自动修复
  • B --- 需要 review 的逻辑调整,自动修复但不自动合并
  • C --- 架构问题或数据问题,只记录不修复

4. 自动修复

修复在隔离的 Git worktree 中进行,不影响主分支:

markdown 复制代码
1. git worktree add -b spirit/fix-{id} → 创建隔离工作区
2. LLM 生成 search-replace 补丁 → 应用到文件
3. go build ./... → 编译验证
4. 验证失败 → 回滚,incident 回到 open
5. 验证通过 → commit + push + 创建 GitLab MR

补丁格式:

json 复制代码
[{
  "file_path": "config/database.go",
  "old_content": "MaxOpenConns: 10",
  "new_content": "MaxOpenConns: 50"
}]

5. Incident 状态机

arduino 复制代码
open → analyzing → analyzed → fixing → resolved → closed
                                  ↓
                              (验证失败回退到 analyzed)

使用 SQLite 本地存储,单进程无并发问题,零运维。通过 HTTP 接口暴露查询:

bash 复制代码
curl http://localhost:9090/incidents

技术选型

组件 选型 原因
日志采集 meta-mcp (HTTP JSON-RPC) 公司已有统一日志查询接口
LLM 分析 Eino + DeepSeek 项目已有成熟的 Agent 能力
存储 SQLite (modernc.org/sqlite) 纯 Go 实现,零依赖,单进程够用
Git 操作 os/exec + git CLI 轻量,不引入额外依赖
MR 创建 GitLab REST API v4 直接 HTTP 调用
部署 --script 模式 复用现有 CI/CD,不加新二进制

部署方式

作为 ai_sales_server 的 script 模式运行,和主服务共享同一个二进制,不同启动参数:

bash 复制代码
# 主服务
./ai_sales_server run -c config.yaml -p 2345

# Spirit Watcher
./ai_sales_server run --script=spirit_watcher -c config.yaml

后续可拆为独立入口 cmd/watcher/main.go,独立编译部署。

文件结构

bash 复制代码
ai_sales_server/
├── config/job.go                  # 注册 spirit_watcher 脚本
├── job/
│   ├── spirit_watcher.go          # 主循环:轮询 + 去重 + 调度
│   ├── spirit_analyzer.go         # Claude 根因分析
│   ├── spirit_fixer.go            # 自动修复 + MR 创建
│   └── spirit/store/store.go      # SQLite 存储层
└── util/meta_mcp_client.go        # MCP 客户端封装

降级策略

场景 处理
MCP 连接失败 启动中止,等待下次重启
日志查询超时 跳过本轮,30 秒后重试
LLM 分析失败 incident 回到 open,下轮重试
补丁应用失败 回退到 analyzed,不提交
编译验证失败 回退到 analyzed,不提交
risk_level=C 只记录,不自动修复

总结

整个系统用约 1000 行 Go 代码实现了从错误发现到 MR 提交的全自动闭环。核心设计思路:

  1. 复用已有基础设施 --- 日志平台、LLM 能力、GitLab,不重复造轮子
  2. 渐进式处理 --- 每个阶段独立,失败可回退,不会产生脏数据
  3. 安全优先 --- worktree 隔离、编译验证、MR 不自动合并,人始终是最后一道关
相关推荐
李小狼lee8 小时前
《spring如此简单》第四节--IOC思想的实现,spring启动后发生了什么
后端·面试
2301_800895108 小时前
计算机网络保研面试(自用版h)
计算机网络·面试
SamDeepThinking8 小时前
面试官问Bean线程安全,你该从架构角度回答
java·后端·面试
Tsuki_tl8 小时前
【总结】Java的线程状态
java·后端·面试·多线程·并发编程·线程状态
xiaoxue..8 小时前
Node.js 笔试题讲解
后端·面试·node.js
西安邮电大学8 小时前
SpringMVC执行流程
java·后端·spring·面试
Nikluas9 小时前
彻底搞懂 Vue 运行时的四大核心谜题:Render、Effect、Diff 算法与 Block Tree 演进
vue.js·面试
AI人工智能+电脑小能手10 小时前
【大白话说Java面试题 第67题】【JVM篇】第27题:生产环境服务器变慢,诊断思路和性能评估谈谈?
java·服务器·jvm·面试
闪电悠米11 小时前
黑马点评短信登录01_session_sms_login
java·spring boot·redis·git·spring·面试