1. 从人肉翻日志到AI诊断,这条路我走了大半年
先说说前提。过去排查线上日志,大家基本是同一个流程:登录跳板机 → grep关键字 → 定位到报错堆栈 → 再往上翻几十行找上下文 → 实在看不出来就加日志重新跑 → 等复现再抓现场。运气好的话半小时能定位,运气不好,一个诡异的内存溢出能查两三天,最后发现是某个基础组件在新版本里改了默认参数。我自己经历过最夸张的一次,连续三天每天半夜被叫起来看日志,人都快看吐了。
后来我们把AI接进这条链路,配合MCP,也就是Model Context Protocol,模型上下文协议,做了一套半自动化的日志诊断系统。现在同样的故障排查,从拿到日志到输出一份带证据链的诊断结论,基本能压到3到5分钟。这个改动最直观的价值不是省时间那么简单,而是把"排查"从一种依赖个人经验的手艺活,变成了一条可复制、可回源、可迭代的标准流水线。
这套东西适合谁参考?如果你正在维护一套有一定流量规模的服务,日志量已经大到人工翻不动,或者团队里每一次线上问题排查都要靠某个核心同学救火,那这篇文章里的思路和踩坑记录应该能帮你省不少事。我下面讲的内容,全部来自模拟项目X里的实际改造过程,不涉及具体业务数据,方法论可以完全照搬。
先说清楚一个认知:AI+MCP做日志诊断,本质上不是让AI去"读"日志,而是让AI具备"使用工具去查日志"的能力。这两者的差别非常大。前者意味着把海量原始日志全部塞进上下文,预算爆炸,效果还差;后者是把日志系统变成AI可以主动调用的工具集,模型只负责拆解问题、编排查询、归纳证据。MCP就是这层工具调用的标准化通道。
2. 整体方案设计与选型逻辑
2.1 链路总览:从采集到结论的五个环节
整套方案拆解下来,核心链路是五个环节:日志采集 → 规则预筛 → MCP工具封装 → LLM分析推理 → 结论输出与回源。
日志采集我们沿用了团队原有的采集管道,没有重造轮子。这里我一直坚持一个原则:AI诊断系统应该长在现有基础设施之上,而不是推倒重来。你已经有ELK也好、开源的时序存储也好,直接用它的查询接口就行。规则预筛是后来加的,也是整套系统里性价比最高的一层——后面我细讲。MCP工具封装是改造的核心,我们把日志查询、错误码解析、依赖状态检查这些能力分别封装成MCP Server上的工具,LLM通过标准协议调用。最后的结论输出,我们强制要求带证据链,也就是每条结论必须附带对应的日志片段ID和查询语句,没有依据不允许给出判断。
这个链路看起来简单,但每一环都有细节。先说为什么要加规则预筛。早期的原型版本里,我们让AI直接面对全量日志,模型看到什么就分析什么,结果非常不稳定。原因很简单:上下文中噪声太多,模型容易被大量半相关甚至不相关的日志带偏。比如数据库连接池报错的时候,日志里同时混着定时任务的日常心跳、缓存淘汰的INFO、还有某个客户端反复重连产生的WARN,这些都会干扰判断。后来加了预筛层,用正则加规则引擎先把日志按时间窗口、错误码、关键词等级打成片段,再让AI按片段粒度去查,准确率立刻上来了。
2.2 为什么选MCP而不是自己写工具调用
这个决定当时也纠结过。最初的方案里,我们用最朴素的方式实现工具调用:自己定义一个JSON格式的function calling协议,写个调度器,LLM输出结构化指令,我们自己去执行。功能上确实能跑通,但很快就发现两个问题。
第一个问题是每次迭代都在改协议。今天想新增一个"查最近N分钟的异常分布"工具,明天想加一个"对比前后两次发布的日志差异",每加一个工具,客户端的调用逻辑、参数校验、返回解析都要跟着改,牵一发动全身。第二个问题是生态问题。我们自己定义的协议只能在自家系统里用,团队里其他项目想复用,要么copy代码,要么重写一套,成本很高。
MCP的价值在于它把"工具调用"这件事标准化了。工具定义、参数校验、返回值规范、错误处理,都是统一的一套描述方式。LLM侧的SDK只要实现了MCP客户端协议,就能直接连接我们暴露的任意工具服务,不用关心底层是HTTP还是stdio传输。换一个模型,从闭源商业模型换成开源的本地模型,只需要改配置,工具层完全不动。这个可替换性在实践里太重要了,因为模型迭代太快,你不可能让业务系统跟模型的升级节奏绑定。
另外MCP目前的生态已经覆盖了常用的开发语言,Python、TypeScript都有官方SDK。我们后端是Python,直接选Python SDK,部署一个MCP Server进程,注册到客户端配置里就能用,整个过程一小时左右就能跑通最小可用版本。
2.3 一个关键权衡:AI读日志的粒度和范围
这是整套方案里最需要动脑子的设计点。
让AI直接读原始日志行,上下文窗口再大也不够用。假设一个服务每秒产生200条日志,一个5分钟的故障窗口就是6万条,哪怕按行业里比较激进的做法压缩成摘要,信息损失也很大。反过来,如果只让AI读经过预筛后的错误片段,又可能丢失"暴风雨前的宁静"里那些关键的前置异常信号。
我们的做法是三级粒度递进。第一级是摘要层,预筛后按每10秒窗口生成统计摘要,比如"15:32:10-15:32:20,ERROR共12条,集中在支付服务,错误码DB_CONN_001出现9次",这段是给AI建立全局时间感的。第二级是片段层,摘要定位到可疑时间窗之后,我们会把那个窗口内所有WARN和ERROR级别日志,连同前后30秒的上下文INFO日志一起拉出来,形成结构化片段。第三级才是原始行,只有AI判断需要看具体某条日志的完整字段时,通过MCP工具的detail参数按日志ID精确取回。
这个设计解决了两个老难题:一是上下文窗口的预算,系统性日志根本没必要全量进入模型;二是信息保真,AI每一个判断都有可回查的原始依据。说白了,我们不是在训练AI"读日志",而是在训练AI"用工具找日志、带着问题看日志"。这两者的区别,就是详解。
3. 核心细节解析与实操要点
3.1 日志结构化:从正则匹配到模式树
实战中第一个硬骨头是日志解析。大部分历史日志,尤其是不太规范的业务日志,都是非结构化的。比如一行日志可能长这样:"15:32:11.482 ERROR [http-nio-8080-exec-12] service OrderService error, orderId=88213, msg: DB_CONN_001 connection pool exhausted, retry=3"。要按字段抽取,时间、线程号、服务名、业务参数、错误码、错误信息,这个解析逻辑如果全用正则写,过两周就没人敢动了。
我们的做法是分两层。第一层是系统级日志,格式基本稳定,用正则加命名分组就够了。第二层是业务日志,格式自由度高,我们用模式树做解析:先按分隔符和关键词切分出候选字段,再通过样本聚合成模式库。一个模式就对应一类日志模板,字段直接映射到结构化的key。实测下来,解析覆盖率从纯正则的60%左右提升到了95%以上,剩下那5%无法识别的,统一归到"未分类",在预筛阶段也不会被丢掉,而是作为低置信度片段保留。
这块踩过一个坑:模式树不是越细越好。一开始我们试图把每个订单ID、每个用户ID都抽成独立字段,导致同一类日志被拆成了几千个模式,预筛规则根本没法写。后来改成只保留稳定字段,业务标识类参数统一归入"biz_context"这个map字段,模式收敛到几十个,规则维护的工作量才降下来。
3.2 MCP工具定义:三个核心工具的设计思路
MCP Server上我们第一批只暴露了三个工具,不是什么大而全的体系,而是刚好覆盖诊断主链路的最小集。
第一个叫search_logs,接受参数包括时间范围、服务名、日志级别、关键字、limit。返回值不是纯文本,而是结构化JSON,包含命中条数、按时间聚合的分布、以及匹配片段的ID列表。第二个叫get_log_detail,接收片段ID和可选的时间偏移窗口,返回该片段前后N秒的完整原始日志行。第三个叫resolve_error_code,接收错误码,返回这个错误码的含义、常见触发条件、历史出现频率、以及关联的依赖项。三个工具加起来,模型已经具备"定位异常-展开上下文-解析错误码含义"的完整能力链条。
工具返回值的格式是个容易被忽略的细节。我们规定工具返回值的第一版一律是JSON,而且所有字段名保持一致风格。MCP协议本身支持任意content类型,但如果返回的是纯文本,LLM解析起来很不稳定;结构化JSON能显著降低模型理解偏差。另外工具描述一定要写清楚"什么时候该用、什么时候不该用",比如search_logs的描述里明确写了"当需要定位某时间窗口内异常日志时优先使用,不要用于查询全量日志",这个引导信息直接影响了模型工具选择的准确率。
MCP规范要求每个工具都要有输入参数的JSON Schema。一开始我们签名写得很随意,都是直接用"keyword"这种字段名,结果模型经常不传必填参数。改成精确的schema定义,加上"时间范围如未指定则默认为最近15分钟"这种默认值描述之后,调用成功率大幅提升。
3.3 提示词与上下文窗口的平衡艺术
提示词这块,我的经验是:与其花大力气写一个万能的系统提示词,不如把诊断流程拆成阶段性的sub-agent提示词。每个阶段对应一个独立prompt,上下文窗口只保留该阶段需要的信息。
阶段一叫"场景识别",输入只有预筛摘要,任务是判断这次故障大概率属于哪一类:依赖故障、代码异常、资源耗尽、外部调用超时,还是数据问题。阶段二叫"证据收集",带着场景假设去调MCP工具,每次只取一个工具的结果,把证据逐步加入上下文。阶段三叫"根因分析",把所有证据摊开,要求模型像写事故报告一样列出"现象-证据-推断-置信度"。
这样做的好处是,每个阶段的上下文长度都能控制在几千token以内,模型不容易被长上下文干扰。我们实测对比过,同一批故障样本,单次长上下文诊断的准确率只有六成多;拆成三阶段之后,准确率稳定在八成以上。代价是多了一次模型调用延迟,但对诊断场景来说,多几秒的等待完全可接受。
提示词里还有一个重要技巧:对模型判断为"证据不足"的情况,必须允许并鼓励它说"需要更多信息",而不是强行给出结论。我们在prompt里明确写了:"如果现有证据无法支持唯一结论,列出至少两种可能,并补充每种可能对应的进一步排查动作。"这一步大大减少了AI一本正经胡说八道的情况。
3.4 结果输出与证据链:AI诊断必须可回源
这一条我要放在最前面强调:AI诊断结果如果不可回源,就没有任何工程价值。
我们输出的诊断报告固定包含四块:结论摘要、关键事件时间线、核心证据列表、处置建议。关键事件时间线是一条按时间排列的日志片段ID序列,每条都标注了"该事件与结论的关系是原因、结果还是伴随"。核心证据列表里每一项都和MCP工具实际返回的日志行绑定,点击可以直接跳转到日志平台的原始终端,看到那行日志当时的完整上下文。
为了实现这个,技术上主要在MCP Server这一侧做了工作。search_logs和get_log_detail的返回值里都强制带上log_event_id字段,这个ID是日志平台里那条日志的全局唯一标识。AI在推理时自然会把ID写进它的分析文本里,我们再做一次后处理,从模型输出里抽取所有event_id关联,自动拼装时间线和证据列表。这套机制运行了几个月,效果很好:研发同学拿到诊断报告后,第一件事一定是点开原始日志自己验证一遍,证据链完备度直接决定了报告的可信度。
4. 实操过程:从零搭建一套AI+MCP日志诊断流程
4.1 环境准备与依赖清单
先说环境。我们这套方案完全依赖主流的开源组件和公开的AI接口,不涉及任何私有化方案。基本依赖有这几块:一个日志查询服务,能用HTTP查就行,返回JSON是我们要求的硬性条件;一套MCP SDK,Python版本,基于当前主流稳定版;一个LLM服务端,可以是云端的也可以是企业内部自建的推理服务,关键是要支持function calling,也就是自定义函数调用。
硬件上没有特别夸张的要求,MCP Server只是个轻量代理,主要成本在LLM推理端。我们是实验阶段直接用Python虚拟环境跑的,一核2G的容器就能支撑日常验证。如果要把这套东西接到生产环境并支撑每天几十次的自动诊断,建议单独给MCP Server至少开双副本,免得它单点故障连带诊断链路全挂。
4.2 MCP Server搭建:三个核心步骤
MCP Server的搭建,说穿了就是三步:定义工具、实现工具处理函数、启动服务注册到客户端。
第一步定义工具。用装饰器的方式,给每个工具写名字、描述、参数schema。这里我建议schema里一定把type、required、description三个字段都写满。description写清楚每个参数的业务含义和数据样例,模型会根据这些描述来构造参数。
第二步实现处理函数。每个函数的逻辑都直连你的日志查询后端。这里有个工程细节:MCP Server本身不应该做耗时超过几秒的操作。日志查询后端如果比较慢,直接返回工具超时。我们的处理办法是在Server层加了一个轻量缓存,对相同参数的查询做5秒内的短缓存,并且把查询超时设计成10秒兜底。搜索类工具如果命中数据量太大,就限制结果数量并提示用户缩小时间范围。
第三步启动服务注册到客户端。MCP的客户端需要知道Server的Transport地址,stdio模式就是一个本地命令,HTTP模式就是一个base地址。我们在代码里配置的是HTTP模式,因为诊断系统本身是一个常驻的Web服务,不适合每次诊断都拉起一个本地进程。注册完之后,用MCP客户端自带的工具列表接口验证一下能否正确读到三个工具的信息,这一步通了,后面就顺畅了。
4.3 客户端调用链路:日志预筛管道与诊断编排
客户端这边我们需要写一个诊断编排器,核心是三个函数:pull_log_window、build_summary_context、run_diagnosis。
pull_log_window负责从日志平台拉取指定时间窗口内的日志,这时不做任何解析,纯拉取。为了控制数据量,默认窗口是故障报告里用户填的时间点前后各5分钟,如果日志量超过每服务100MB,会提示用户截断按分钟级别细分窗口再查,避免预筛阶段就撑爆内存。
build_summary_context是编排器里逻辑最重的一块。它拿到原始日志后,按前面说的模式树做结构化解析,然后做时间分桶统计,每10秒桶生成统计摘要,再对每个桶做关键词扫描,标记出异常桶。最终生成的摘要上下文字符串会被拼到诊断的第一阶段prompt里。这个函数我建议独立成一个模块,因为它完全可以单独测试:你给它一段日志文件,它就输出一段摘要,纯工程逻辑和AI无关,很容易做回归断言。
run_diagnosis是主流程:先请求LLM做场景识别,拿到结果后循环调用MCP工具收集证据,直到LLM判定证据充分。为了控制成本,我们给每个诊断任务设置了工具调用上限,默认是12次。达到上限还没结论的任务,会进入人工队列,同时自动把中间证据打包发给值班的人。实际用下来,绝大多数任务10次以内就能收敛。
4.4 一次真实诊断过程的逐段还原
下面用一个脱敏后的模拟场景走一遍完整过程。假设某订单服务在15:30-15:40之间出现了大量超时告警,我触发诊断任务,只输入了"下单超时异常,请排查15:30-15:40的日志"这一句话。
第一步,编排器在15:30-15:40窗口内拉取了该服务共约23万行日志。build_summary_context跑了约4秒,产出摘要:这10分钟内ERROR共47条,WARN共890条,异常集中在15:34:20-15:34:50这30秒内,错误码TOP3是DB_CONN_001、TIMEOUT_SVC_008、RATE_LIMIT_002,并自动把这三个异常桶的时间戳和片段标识关联好。
第二步,LLM场景识别结果判定为"依赖故障-数据库连接池耗尽"的高置信度假设,理由异常分布集中在短时间窗口,且DB_CONN_001是数据库连接池相关的错误码。确认之后,诊断进入证据收集阶段。
第三步,AI通过MCP工具依次发了三次请求:第一次search_logs查15:34:20-15:34:50窗口内error_code=DB_CONN_001的分布,返回命中9条;第二次get_log_detail拉取了第一条错误日志附近前后30秒的原始片段,此时看到了前置的WARN日志:"pool stats: active=50, idle=0, wait=25",说明连接池已经打满;第三次resolve_error_code查询DB_CONN_001的常见成因,反馈里有一条和当前环境最匹配的"连接池最大值配置过小,且未配置等待队列上限,高并发下触发拒绝"。
第四步,AI结合三段证据输出报告:结论是"数据库连接池maxActive默认配置与当前高峰期并发不匹配,且等待队列设置缺失,导致连接池耗尽引发连锁超时"。关键事件时间线把15:34:20的WARN、15:34:22和15:34:24的三条ERROR、以及15:34:28的RATE_LIMIT_002全部串了起来。处置建议给了两条:临时调大maxActive并重启,长期建议按高峰期流量重新计算池大小并配置等待超时。整个流程跑完,总耗时3分12秒,其中LLM推理占了两分钟,预筛解析占了一分钟,MCP工具调用本身不到10秒。
5. 常见问题与排查技巧实录
5.1 典型问题速查表
对这套系统感兴趣或者已经在动手搭的同行,大概率会遇到下面几个问题,我把我们踩过的坑整理成了一张表,方便你对号入座。
| 问题现象 | 根因 | 解决办法 |
|---|---|---|
| AI不调用工具直接瞎猜 | 工具描述太模糊,未写明调用优先级 | 在描述里加入"优先使用"字样,并写清使用边界 |
| MCP Server偶发超时 | 日志查询后端耗时长,超过LLM侧等待上限 | Server层加短缓存,限制单次查询量,超时兜底10秒 |
| 诊断结论正确但证据链断裂 | 模型推理时没有保留日志ID,或ID在输出中被改写 | 输出后处理时按时间线和事件标签重排,不依赖模型原样复述ID |
| 预筛把关键前置异常吞掉了 | 摘要窗口过大,异常被平均化 | 缩短桶粒度,对高异常级别日志单独保留原始片段 |
| 反复调用同一个工具拿同样的数据 | 模型在长上下文里丢失了历史工具结果 | 在prompt里要求AI先引用已有证据,并检查工具调用参数去重 |
对总结:绝大多数问题不是模型不行,而是工程封装不够细致。MCP这种标准化协议带来的一个好处是,排查侧的问题基本都能通过调整Server实现解决,不需要动客户端协议。
5.2 日志量太大,MCP工具动不动就超限怎么办
这个问题值得单独说。日志量大的时候,search_logs如果返回几万行原始内容,一是工具调用的响应体太大,二是LLM消化不了。我们最终采用的方案是强制让工具支持"聚合优先"策略。
具体实现是给search_logs增加一个response_mode参数,取值可以是summary、trend、detail。summary模式返回错误码的分布统计,trend模式返回按分钟聚合的异常数量走势,detail模式才返回原始日志行且默认最多50条。AI在证据收集阶段,第一步必然用summary或trend,只有当判断某个时间段需要看具体堆栈内容时才会切detail。这套策略上线之后,工具调用平均响应体从400KB降到了6KB,诊断速度提升非常明显。
5.3 模型幻觉怎么控制:宁可说"不知道",不要编
最后聊一个不那么技术、但非常现实的问题:模型幻觉。AI在日志诊断里最常见的幻觉不是编造日志,而是基于少量证据推导出过度具体的结论。比如只看到一条RATE_LIMIT_002的日志,就推断"上游服务触发了限流,因为某下游节点CPU过高",但日志里根本没有CPU数据。
我们控制幻觉有几层手段。第一层是提示词里显式要求"所有推断必须标注证据级别:直接证据、间接证据、推测",没有直接证据支撑的结论强制标注为"推测"。第二层是在编排器里做证据完整性校验,关键结论字段如果缺少对应的日志event_id引用,输出就会被打回,要求模型补证。第三层是系统运行了一段时间后,我们把历史诊断结果里标注"推测"且后来被证伪的样本收集起来,做成few-shot示例加进提示词,每次都要提醒模型参考反例。
这三层手段叠加,我们的诊断报告里"推测"类结论的占比从一开始的30%降到了10%以内,剩下的推测中,大多数也能在报告里明确说明需要人工验证的方向。我一直觉得,AI诊断系统追求的不是永远正确,而是永远诚实。一个在结论旁标注"这是推测"的AI,比一个自信地给出错误结论的AI有用得多。
这套系统的后续扩展空间还挺大,比如把历史诊断结论沉淀成知识库,用检索增强的方式让AI复用之前的排查经验;再比如支持多服务跨调用链的日志关联分析。但目前这套最小可用版本,已经足够把我们从半夜翻日志的苦海里捞出来了。如果你也每天被人肉排查日志折磨,真的可以照着这个思路先搭个最小的闭环跑起来,从某个高频故障的业务场景开始试,成本不高,收益立竿见影。