拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

日志排查别急着问 AI:先用 awk、grep、jq 把日志统计清楚(附可复制命令)

日志排查别急着问 AI:先用 awk、grep、jq 把日志统计清楚(附可复制命令)

目录

    • 一、为什么不直接把日志贴给 AI
      • 数量级对不上
      • 有用的信息被淹没
      • 日志里有不该发出去的东西
      • 我的流程
    • 二、准备一份示例日志
      • 日志格式
      • 生成脚本
    • 三、五个统计,把两万行压成几张表
      • 1. 状态码分布
      • 2. 错误是从什么时候开始的
      • 3. 错误集中在哪个接口
      • 4. 慢在哪:P50 和 P99
      • 5. 错误信息有几类
    • 四、把统计结果交给 AI
      • 先脱敏
      • 可复制 Prompt
      • AI 能推到哪一步
    • 五、几个容易踩的坑
    • 小结

线上一出问题,很多人的第一反应是把日志复制出来,整段贴给 AI 问「这是怎么回事」。

几百行还好。几万行就不行了:一是装不下,二是贵,三是 AI 在大量重复的正常请求里很难抓到重点,还容易被几条偶然的异常行带偏。

我现在的做法是反过来:先用命令行把日志压成几张统计表,再把统计结果和少量样本交给 AI。两万行日志,最后交给 AI 的只有几十行,它的判断反而更准。这篇用一份示例日志把整个过程走一遍,所有命令都能直接复制。


一、为什么不直接把日志贴给 AI

数量级对不上

下面用到的示例 access.log 有 2 万行,大小约 2.7 MB。按一个 token 大约 4 个字符粗估,将近 70 万 token,超过大多数模型一次能读的量。就算能装下,一次请求也很贵。

有用的信息被淹没

这 2 万行里,正常请求占了 95% 以上。真正能说明问题的,是「错误从什么时候开始」「集中在哪个接口」「慢了多少」这几个事实。它们不在任何一行日志里,得统计出来。

日志里有不该发出去的东西

token、手机号、订单号这类信息,原样贴给外部模型并不合适。先统计、再脱敏,发出去的内容会少得多,也干净得多。

我的流程

  1. 用命令行做五个统计,把日志压成几张表
  2. 每类错误挑两三条原始日志当样本
  3. 脱敏之后,连同最近的变更记录一起交给 AI
  4. 让 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.151

app.log 是应用自己打的 JSON 日志,每行有ts、level、path、msg四个字段。

生成脚本

想跟着跑一遍的,可以用这个脚本生成同样的数据。随机种子是固定的,每次生成的结果都一样:

# gen_logs.py:生成一份示例日志(固定随机种子,结果可复现)importjsonimportrandomfromdatetimeimportdatetime,timedelta random.seed(42)start=datetime(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):t=start+timedelta(seconds=i*0.03)# 10 分钟,共 20000 条path=random.choice(paths).format(random.randint(1,9999))status,cost=200,random.uniform(0.01,0.2)ts_iso=t.strftime("%Y-%m-%dT%H:%M:%S+08:00")ift.minute>=5andpath.startswith("/api/orders/")andrandom.random()<0.3:# 14:05 之后,订单接口开始出错status,cost=random.choice([502,504]),random.uniform(3.0,5.0)msg=random.choice([f"upstream timeout after{random.randint(3000,3100)}ms calling inventory-service",f"connection pool exhausted: max=20 in_use=20 waiting={random.randint(30,80)}",])app.write(json.dumps({"ts":ts_iso,"level":"error","path":path,"msg":msg})+"\n")elifrandom.random()<0.01:status=404msg=f"record not found id={random.randint(1,9999)}"app.write(json.dumps({"ts":ts_iso,"level":"warn","path":path,"msg":msg})+"\n")ts=t.strftime("%d/%b/%Y:%H:%M:%S +0800")ip=f"10.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.log(20000 行)和 app.log(945 行)。数据是照着一次常见故障的特征造的:14:05 之后,订单接口开始出错。下面假装我们还不知道这件事,从零开始查。


三、五个统计,把两万行压成几张表

1. 状态码分布

awk'{print $9}'access.log|sort|uniq-c|sort-rn

请求那一段"GET /api/users/410 HTTP/1.1"在 awk 里会被空格拆成三列,所以状态码正好是第 9 列。结果:

状态码次数
20019055
502386
504379
404180

502 加 504 一共 765 次,这就是要查的问题。404 数量少,而且是业务上的「查无此记录」,先放一边。

2. 错误是从什么时候开始的

# 按分钟统计 5xx;substr 截出「日/月/年:时:分」awk'$9 >= 500 {print substr($4, 2, 17)}'access.log|sort|uniq-c# 第一条错误日志的时间jq-r'select(.level == "error") | .ts'app.log|head-1
分钟5xx 次数
14:00~14:040
14:05154
14:06159
14:07152
14:08146
14:09154

第一条错误日志的时间是2026-09-29T14:05:00+08:00。错误不是慢慢变多,而是在 14:05 这个时间点突然出现,之后一直维持在每分钟 150 次左右。突然开始,通常意味着有一个明确的触发点:一次发布、一次配置变更,或者下游某个服务出了状况。

3. 错误集中在哪个接口

# 把路径里的数字换成 {id},同一个接口的请求才能归到一起awk'$9 >= 500 {print $7}'access.log|sed-E's/[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.913

P50 几乎没变,P99 从 0.2 秒涨到了将近 5 秒。意思是:大部分请求还是正常的,但有一小部分请求卡了很久。这种形态很像是在等什么东西:等下游响应、等连接、等锁。

5. 错误信息有几类

# 把数字换成 N,同一类错误才能归到一起jq-r'select(.level == "error") | .msg'app.log|sed-E's/[0-9]+/N/g'|sort|uniq-c|sort-rn
次数归并后的错误信息
385connection pool exhausted: max=N in_use=N waiting=N
380upstream timeout after N ms calling inventory-service

几百条错误日志,归并之后只有两类:一类是调用 inventory-service 超时,一类是连接池被占满。两类数量接近,而且是同一时间出现的,大概率是同一个原因引起的。

这五条命令我是在 WES Code 的终端里跑的,跑完把输出贴进对话,让它整理成上面这样的表。


四、把统计结果交给 AI

先脱敏

就算只发统计结果和几条样本,也建议先过一遍脱敏:

# 把 token、password 的值和手机号替换掉sed-E's/(token|password)=[^&" ]+/\1=***/g; s/1[3-9][0-9]{9}/1**********/g'sample.log

拿一行GET /api/login?token=abc123&u=1试一下,输出是GET /api/login?token=***&u=1。

可复制 Prompt

下面是一次线上问题的日志统计结果。原始日志约 2 万行,我已经压缩成下面几张表。 【时间线】5xx 按分钟统计:<粘贴第 2 步结果> 【范围】5xx 按接口统计:<粘贴第 3 步结果> 【耗时】订单接口出问题前后的 P50 / P99:<粘贴第 4 步结果> 【错误类型】归并后的错误信息:<粘贴第 5 步结果> 【样本】每类错误各 2 条原始日志(已脱敏):<粘贴> 【最近变更】14:00 前后的发布记录、配置变更:<粘贴> 请: 1. 按可能性从高到低,给出 3 个根因假设,每个假设写明是哪几条数据支持它 2. 每个假设给出一条能证实或排除它的检查命令,或者需要我补充的数据 3. 只根据给出的数据推断,缺少的信息直接说缺什么,不要编造

最后一条很重要。不加的话,它会把「可能是网络抖动」这类没有数据支撑的猜测,也和其他假设放在一起。

AI 能推到哪一步

拿上面几张表去问,AI 给出的第一个假设基本都是:inventory-service 变慢,调用超时(3 秒左右)的请求一直占着连接,连接池(最大 20 个)很快被占满,后面的请求只能排队。这个推断能同时解释上面四个事实:

  • 只有订单接口出错:只有它依赖 inventory-service
  • 14: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 到一起就行;要看时间线,再按时间排序。


小结

  1. 别把原始日志整段贴给 AI:装不下、成本高,重点也会被淹没
  2. 先做五个统计:状态码、时间线、接口、耗时分位数、错误类型
  3. 两万行压成几十行,再附上几条脱敏后的样本和最近的变更记录
  4. AI 给的是假设,要回到数据里逐个验证

文中的统计和分析是我在 WES Code 里做的,官网是 weisyn.com。你们排查线上问题时,最常用的日志命令是哪几条,欢迎评论区分享。

觉得有用的朋友,欢迎点赞、收藏、关注,后面会继续分享 AI 编程的实战经验。

返回列表