目录一、为什么不直接把日志贴给 AI数量级对不上有用的信息被淹没日志里有不该发出去的东西我的流程二、准备一份示例日志日志格式生成脚本三、五个统计把两万行压成几张表1. 状态码分布2. 错误是从什么时候开始的3. 错误集中在哪个接口4. 慢在哪P50 和 P995. 错误信息有几类四、把统计结果交给 AI先脱敏可复制 PromptAI 能推到哪一步五、几个容易踩的坑小结线上一出问题很多人的第一反应是把日志复制出来整段贴给 AI 问「这是怎么回事」。几百行还好。几万行就不行了一是装不下二是贵三是 AI 在大量重复的正常请求里很难抓到重点还容易被几条偶然的异常行带偏。我现在的做法是反过来先用命令行把日志压成几张统计表再把统计结果和少量样本交给 AI。两万行日志最后交给 AI 的只有几十行它的判断反而更准。这篇用一份示例日志把整个过程走一遍所有命令都能直接复制。一、为什么不直接把日志贴给 AI数量级对不上下面用到的示例 access.log 有 2 万行大小约 2.7 MB。按一个 token 大约 4 个字符粗估将近 70 万 token超过大多数模型一次能读的量。就算能装下一次请求也很贵。有用的信息被淹没这 2 万行里正常请求占了 95% 以上。真正能说明问题的是「错误从什么时候开始」「集中在哪个接口」「慢了多少」这几个事实。它们不在任何一行日志里得统计出来。日志里有不该发出去的东西token、手机号、订单号这类信息原样贴给外部模型并不合适。先统计、再脱敏发出去的内容会少得多也干净得多。我的流程用命令行做五个统计把日志压成几张表每类错误挑两三条原始日志当样本脱敏之后连同最近的变更记录一起交给 AI让 AI 给出几个假设再回到日志里逐个验证二、准备一份示例日志日志格式access.log 是常见的 Nginx 访问日志格式最后一列加了请求耗时字段示例awk 里的位置客户端 IP10.0.1.189$1时间[29/Sep/2026:14:00:00$4请求路径/api/users/410$7状态码200$9耗时秒0.151$NF最后一列一行完整的日志长这样10.0.1.189 - - [29/Sep/2026:14:00:00 0800] “GET /api/users/410 HTTP/1.1” 200 1039 “-” “Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0)” 0.151app.log 是应用自己打的 JSON 日志每行有ts、level、path、msg四个字段。生成脚本想跟着跑一遍的可以用这个脚本生成同样的数据。随机种子是固定的每次生成的结果都一样# gen_logs.py生成一份示例日志固定随机种子结果可复现importjsonimportrandomfromdatetimeimportdatetime,timedelta random.seed(42)startdatetime(2026,9,29,14,0,0)paths[/api/users/{},/api/orders/{},/api/products?page{},/api/health]withopen(access.log,w)asacc,open(app.log,w)asapp:foriinrange(20000):tstarttimedelta(secondsi*0.03)# 10 分钟共 20000 条pathrandom.choice(paths).format(random.randint(1,9999))status,cost200,random.uniform(0.01,0.2)ts_isot.strftime(%Y-%m-%dT%H:%M:%S08:00)ift.minute5andpath.startswith(/api/orders/)andrandom.random()0.3:# 14:05 之后订单接口开始出错status,costrandom.choice([502,504]),random.uniform(3.0,5.0)msgrandom.choice([fupstream timeout after{random.randint(3000,3100)}ms calling inventory-service,fconnection pool exhausted: max20 in_use20 waiting{random.randint(30,80)},])app.write(json.dumps({ts:ts_iso,level:error,path:path,msg:msg})\n)elifrandom.random()0.01:status404msgfrecord not found id{random.randint(1,9999)}app.write(json.dumps({ts:ts_iso,level:warn,path:path,msg:msg})\n)tst.strftime(%d/%b/%Y:%H:%M:%S 0800)ipf10.0.{random.randint(0,3)}.{random.randint(1,254)}acc.write(f{ip}- - [{ts}] GET{path}HTTP/1.1{status}{random.randint(200,5000)}f- Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0){cost:.3f}\n)运行python3 gen_logs.py会生成 access.log20000 行和 app.log945 行。数据是照着一次常见故障的特征造的14:05 之后订单接口开始出错。下面假装我们还不知道这件事从零开始查。三、五个统计把两万行压成几张表1. 状态码分布awk{print $9}access.log|sort|uniq-c|sort-rn请求那一段GET /api/users/410 HTTP/1.1在 awk 里会被空格拆成三列所以状态码正好是第 9 列。结果状态码次数20019055502386504379404180502 加 504 一共 765 次这就是要查的问题。404 数量少而且是业务上的「查无此记录」先放一边。2. 错误是从什么时候开始的# 按分钟统计 5xxsubstr 截出「日/月/年:时:分」awk$9 500 {print substr($4, 2, 17)}access.log|sort|uniq-c# 第一条错误日志的时间jq-rselect(.level error) | .tsapp.log|head-1分钟5xx 次数14:0014:04014:0515414:0615914:0715214:0814614:09154第一条错误日志的时间是2026-09-29T14:05:0008:00。错误不是慢慢变多而是在 14:05 这个时间点突然出现之后一直维持在每分钟 150 次左右。突然开始通常意味着有一个明确的触发点一次发布、一次配置变更或者下游某个服务出了状况。3. 错误集中在哪个接口# 把路径里的数字换成 {id}同一个接口的请求才能归到一起awk$9 500 {print $7}access.log|sed-Es/[0-9]/{id}/g|sort|uniq-c|sort-rn结果只有一行765 次 5xx全部来自/api/orders/{id}。用户、商品、健康检查接口一次都没出错。sed这一步很关键。不做归并的话/api/orders/123和/api/orders/456会被当成两个接口结果会拆成几百行反而看不出规律。4. 慢在哪P50 和 P99# 14:05 之后订单接口的耗时分位数把 换成 就是 14:05 之前awk$7 ~ /^\/api\/orders\// substr($4, 14, 5) 14:05 {print $NF}access.log\|sort-n\|awk{a[NR] $1} END {printf P50 %s P99 %s\n, a[int(NR * 0.5)], a[int(NR * 0.99)]}范围P50秒P99秒全部请求0.1094.465订单接口14:05 之前0.1050.197订单接口14:05 之后0.1434.913P50 几乎没变P99 从 0.2 秒涨到了将近 5 秒。意思是大部分请求还是正常的但有一小部分请求卡了很久。这种形态很像是在等什么东西等下游响应、等连接、等锁。5. 错误信息有几类# 把数字换成 N同一类错误才能归到一起jq-rselect(.level error) | .msgapp.log|sed-Es/[0-9]/N/g|sort|uniq-c|sort-rn次数归并后的错误信息385connection pool exhausted: maxN in_useN waitingN380upstream timeout after N ms calling inventory-service几百条错误日志归并之后只有两类一类是调用 inventory-service 超时一类是连接池被占满。两类数量接近而且是同一时间出现的大概率是同一个原因引起的。这五条命令我是在 WES Code 的终端里跑的跑完把输出贴进对话让它整理成上面这样的表。四、把统计结果交给 AI先脱敏就算只发统计结果和几条样本也建议先过一遍脱敏# 把 token、password 的值和手机号替换掉sed-Es/(token|password)[^ ]/\1***/g; s/1[3-9][0-9]{9}/1**********/gsample.log拿一行GET /api/login?tokenabc123u1试一下输出是GET /api/login?token***u1。可复制 Prompt下面是一次线上问题的日志统计结果。原始日志约 2 万行我已经压缩成下面几张表。 【时间线】5xx 按分钟统计粘贴第 2 步结果 【范围】5xx 按接口统计粘贴第 3 步结果 【耗时】订单接口出问题前后的 P50 / P99粘贴第 4 步结果 【错误类型】归并后的错误信息粘贴第 5 步结果 【样本】每类错误各 2 条原始日志已脱敏粘贴 【最近变更】14:00 前后的发布记录、配置变更粘贴 请 1. 按可能性从高到低给出 3 个根因假设每个假设写明是哪几条数据支持它 2. 每个假设给出一条能证实或排除它的检查命令或者需要我补充的数据 3. 只根据给出的数据推断缺少的信息直接说缺什么不要编造最后一条很重要。不加的话它会把「可能是网络抖动」这类没有数据支撑的猜测也和其他假设放在一起。AI 能推到哪一步拿上面几张表去问AI 给出的第一个假设基本都是inventory-service 变慢调用超时3 秒左右的请求一直占着连接连接池最大 20 个很快被占满后面的请求只能排队。这个推断能同时解释上面四个事实只有订单接口出错只有它依赖 inventory-service14:05 突然开始下游在这个时间点出了状况或者这边有一次变更P50 正常、P99 暴涨大部分请求没受影响卡住的那部分要等到超时两类错误数量接近超时和连接池占满是同一件事的两个表现接下来要验证的就很具体了inventory-service 同一时段的耗时监控、订单服务的连接池配置、14:05 前后有没有发布。统计表只能说明「发生了什么」要弄清「为什么」还得看代码和配置。所以验证这一步我会在 WES Code 里把这几张表连同订单接口的 handler 和连接池配置所在的文件一起放进同一个对话对照着看超时时间和连接池大小是怎么配的。五、几个容易踩的坑字段位置会变。日志格式里一旦有带空格的字段或者加了自定义字段$9就不一定是状态码了。统计之前先head -1看一眼格式像耗时这种放在最后一列的用$NF比数第几列更稳。时区对不上。access.log 是 0800如果 app.log 用的是 UTC两边的时间线会差 8 小时。先确认时区再把两份日志放在一起看。只看错误日志会漏。有些故障错误日志并不多但耗时暴涨。所以第 4 步的耗时统计必须做不能只 grep 一下 error。统计口径不一致。5xx 次数和错误日志条数不一定相等一个请求可能打多条日志也可能一条都不打。交给 AI 时说清楚每个数字是按什么统计的。多台机器要合并。只看一台机器的日志可能正好错过出问题的那台。统计分布的话直接把多台的日志 cat 到一起就行要看时间线再按时间排序。小结别把原始日志整段贴给 AI装不下、成本高重点也会被淹没先做五个统计状态码、时间线、接口、耗时分位数、错误类型两万行压成几十行再附上几条脱敏后的样本和最近的变更记录AI 给的是假设要回到数据里逐个验证文中的统计和分析是我在 WES Code 里做的官网是 weisyn.com。你们排查线上问题时最常用的日志命令是哪几条欢迎评论区分享。觉得有用的朋友欢迎点赞、收藏、关注后面会继续分享 AI 编程的实战经验。