告别cat:大数据量日志排查必会技能,tail/grep/awk/sed实战指南
1. 为什么“无脑 cat”在大日志面前必然翻车1.1 cat 的三宗罪内存、IO 与终端卡死先说个真实场景某天线上告警某个核心服务请求量掉了一半我第一反应是登服务器看日志。当时日志文件大概 3GB 多我习惯性敲下cat app.log | grep ERROR结果终端直接卡了十几秒才刷出满屏的报错信息等我想按Ctrl C中断的时候手都按酸了屏幕还在滚。后来我再也不在生产环境随便 cat 大文件了那感觉就像拿消防水管往水杯里灌水——不是不能喝是根本接不住。cat 的问题在于三点读入方式粗暴cat 会把整个文件从头到尾读一遍3GB 文件就是 3GB 的磁盘 IO全压在系统上。同时把全部内容推给终端渲染终端要处理几十万行的文本绘制不卡才怪。内存和带宽双高虽然 cat 本身是流式读取不一次性载入内存但它会把数据源源不断塞进管道和终端缓冲区管道写满后等同于全量 IO 全量渲染对生产机器是一种浪费。信息密度太低GB 级日志里99% 都是正常请求真正想看的错误、慢请求、特定用户记录就那么几千条。用 cat 把整个文件滚一遍等于为了找一根针把整个草垛搬回家。所以我的第一个建议就是在你的服务器上把cat当作“查看小文件”的专用命令超过 100MB 的日志一律换工具、换思路。1.2 从“全量查看”到“先定位、再切片、后分析”的思维转变其实排查大日志本质上是一个“数据检索”问题而不是“文件阅读”问题。你需要的不是把文件读完而是回答几个具体问题这段时间到底报了什么错集中在哪一行到哪一行某个请求 ID 在整个日志里的完整链路是怎样的某个用户/IP 在什么时间点触发了什么接口返回了什么状态码某个时间段内的请求量、错误率、平均耗时有没有明显异常一旦把问题定义清楚你就会发现先缩小范围再精准读取最后做聚合统计才是正确路径。缩小范围可以用grep、awk按关键字过滤精准读取可以用sed按行号切片、用less滚动定位聚合统计可以用awk做分组求和、排序。整个过程根本不需要把大文件完整铺到屏幕上。后面我会按“定位 → 切片 → 分析 → 实时追踪”的顺序把实战里最常用的命令一个个拆开讲每个命令都配套具体场景和参数解释。2. 日志排查的“三板斧”tail / head / less 实战用法2.1 tail从尾部追击默认就能看最后 10 行tail是我在生产环境用得最频繁的命令没有之一。它的设计哲学很朴素日志是追加写的最新内容在文件末尾所以从尾部看是最合理的切入点。最基础用法是直接看最后 10 行tail app.log加-n指定行数tail -n 200 app.log注意-n可以简写成-200效果一样tail -200 app.log生产日志往往按小时或天切割如果你不确定今天写的是哪个文件可以用通配符配合tail一次看多个tail -n 50 app.log.2025-06-01 app.log.2025-06-02输出会自动带文件名头格式是 文件名 方便区分来源。这个技巧在跨天对比时很实用。还有一个高频用法是“跳过 N 行再取”有时候日志头部有一段启动 banner 或配置信息你不关心可以用tail -n N表示从第 N 行开始取但我个人更常用的是配合grep直接过滤这个后面讲。2.2 head看开头、看切割文件的最新头部head和tail正好相反负责看文件头部内容。它在两类场景下很有价值第一排查启动类问题。服务启动失败、初始化卡住问题基本都集中在日志的开头部分head -n 100 app.log第二配合日志切割文件做“最近一次启动检查”。很多日志系统切完文件会在新文件里重新打印启动信息这时候新文件的行数不多用head反而比tail看得全。head还有个冷门但好用的组合用进程 PID 直接看它打开的日志文件描述符对应的真实路径这属于进阶玩法我在第五节讲实时追踪时会提到。2.3 less大日志的“滚动查看神器”/ 搜索、g 定位、F 跟随如果你需要交互式浏览大文件less是绝对的首选它不像cat那样一次性全部输出而是按需分页加载打开一个 GB 级文件瞬间完成随翻随读。less app.log进去之后几个键位必须背下来/关键字向下搜索按n继续下一个匹配按N回上一个。这是最核心的用法。?关键字向上搜索场景是你在某个位置附近想往上找上下文。g跳到文件第一行G跳到文件最后一行。配合N跳行号就是3000G直接到第 3000 行。F进入“跟随模式”效果等同于tail -f文件有新内容会实时滚动。退出用Ctrl C。q退出。less配合搜索定位的场景极其实战。比如你看到一个报错关键字NullPointerException想确认它从哪一行开始出现直接less app.log进入后键入/NullPointerException按n一路看下去看到觉得可疑的上下文再按G跳到末尾或按g回到开头做时间对比。整个过程不需要你知道行号也不需要把日志导到本地服务器上直接操作体验非常流畅。还有一个组合用法值得记住less F app.log这样打开文件就直接进入类似tail -f的实时跟随状态省得进去再按F。我在看实时告警日志时基本都用这种方式。3. 定位到区间后用 sed / awk 精准切片3.1 sed 按行号切片sed -n 100,200p 格式当你通过less、grep定位到问题大致范围后往往需要把“这一整段”日志单独拉出来看或者导出给同事。这时候sed就派上用场了。最经典的切片命令sed -n 100,200p app.log这条命令的意思是打印第 100 行到第 200 行的内容-n是关闭默认输出p是“打印匹配到的行”。我经常先用grep找到目标行号再直接用sed把前后几十行切出来看上下文。比如grep -n OutOfMemoryError app.log | head -5输出可能类似128934:2025-06-01 14:23:11 ERROR ... OutOfMemoryError那我就能立刻切出 OOM 发生前 30 行到发生后 20 行的完整脉络sed -n 128904,128954p app.log另外sed也支持“从某行切到结尾”这在你看完前半段、想看后半段时特别好用sed -n 5000,$p app.log$表示最后一行这条命令把第 5000 行到结尾全部打出来。如果你只想看前 500 行等价于head -n 500sed -n 1,500p app.log在实际排查中我还会把切片结果直接重定向成小文件再用其他工具分析sed -n 128904,128954p app.log oom_section.log3.2 awk 按时间和字段过滤时间戳、耗时、状态码sed是按行号切片awk则是按内容结构做精筛。只要是日志格式里有固定分隔符空格、逗号、制表符、|等awk就能按字段精准提取。最常见的日志格式长这样2025-06-01 14:23:11.123 INFO [http-nio-8080-exec-3] [userId:9527] GET /api/order/list 200 45ms这个格式里按空格切分后$1是日期2025-06-01$2是时间14:23:11.123$3是日志级别$4是线程名$5是用户标识$6是请求方法$7是请求路径$8是状态码$9是耗时想统计某个时间段内的所有请求可以awk $2 14:23:00 $2 14:24:00 app.log注意这里是字符串比较依赖时间格式的规则性只要日志时间固定长度这个方式就有效。要是想过滤某个路径且只看 500 错误awk $7 ~ /\/api\/order/ $8 500 app.log~是正则匹配操作符!~是不匹配。这个组合能直接回答“order 接口在 14:23 到 14:24 之间有多少 500”这类问题。更狠一点的玩法是用awk做聚合统计。比如统计每个接口的平均耗时awk {if ($7 ! ) {sum[$7]$9; count[$7]}} END {for (k in sum) printf %s avg%.2fms count%d\n, k, sum[k]/count[k], count[k]} app.log | sort -t -k2 -nr | head -20这行命令稍长但逻辑很直白按第 7 列路径分组累加第 9 列毫秒耗时最后算平均值和数量再按平均耗时倒序取前 20。用一次就知道awk 在日志分析里根本不只是一个文本工具它是个迷你数据库。3.3 grep 组合多条件过滤、正则、上下文grep算是日志排查里的“万能胶”什么场景都能用它先粗筛一遍。但很多人只知道grep 关键字 app.log其实几个参数配合起来效率能翻好几倍。grep -E pattern1|pattern2扩展正则同时匹配多个关键字。比如grep -E ERROR|Exception。grep -v 关键字反向匹配排除某些行。这在日志里有大量健康检查请求时特别好用直接过滤掉。grep -A 5 -B 10 ERROR打印匹配行之后 5 行、之前 10 行直接给出上下文。我给你一个实际例子。有一次线上有大量 502我先用了grep -E 502|Bad Gateway app.log | head -100发现全是网关层返回服务端日志里没有明细。然后我换了个思路用反向过滤把正常的健康检查刷掉grep -v /health/check app.log | grep -E ERROR|WARN | head -200瞬间就看到了真正的异常链路。所以记住一句话grep 是用来缩小范围的不是用来展示结果的。配合head限制输出量配合-A/-B看上下文配合-E做多条件匹配一套组合拳下来几 GB 日志能快速压缩到几十行可读信息。另外grep的管道用法也是必须掌握的grep userId:9527 app.log | grep ERROR这条命令等于先筛用户再筛错误级别两步精准定位。虽然可以用一条正则实现但两条grep可读性更好也方便临时调整条件。4. 线上真实场景GB 级日志排查的四个实战案例4.1 场景一接口突然变慢定位慢请求与链路耗时有次业务反馈“下单接口很慢”但不是完全不可用。我登录服务器日志已经是 2.6GB。我没有直接去看日志尾部而是先用 awk 统计慢请求占比awk $9 ~ /ms/ $90 3000 {print $2, $7, $9} app.log | awk -F: {print $1} | sort | uniq -c这条命令先把耗时超过 3000ms 的请求按分钟聚合看分布。结果发现集中在某个分钟内突然飙升。接着我直接按时间窗切成一个小文件sed -n /15:31:00/,/15:33:00/p app.log slow_section.log然后看这个时间段内的具体请求发现有大量请求卡在同一个上游调用上日志里打印了下游接口响应时间。这时候我再用 grep 把对应的下游调用日志拉出来确认是依赖服务 GC 导致响应变慢问题定位完毕。整个过程没有一次完整 cat 整个文件。核心思路是“先聚合看整体走势再按时间窗切片看局部最后按请求 ID 追链路”。如果你一上来就 tail 看最后几百行很可能因为慢请求是 15:31 的而日志已经写到 16:20白白错过关键证据。4.2 场景二OOM 前后日志快速捞取异常上下文OOM 是后端最让人头疼的问题之一但日志排查其实有固定套路。当时服务堆内存溢出重启后日志滚动旧的 GB 级日志还在。我先用 grep 找到 OOM 关键字出现的所有行号grep -n OutOfMemoryError app.log输出大概有 6 处。我用sed把第一处 OOM 前 200 行、后 200 行切出来sed -n 89300,89700p app.log oom_debug.log切出来的内容里能看到 OOM 之前有大量GC overhead limit exceeded警告以及某个批量导出接口的并发请求。这说明内存是被一批大查询撑爆的。然后我再 pool 这个接口的耗时分布grep export/batch app.log | awk {print $9} | sort -n | tail -20发现确实有单次请求耗时超过 30 秒的极端情况直接锁定了根因。这里有个关键经验OOM 日志不要只看报错那一行前后 200 行的“前兆”比报错本身更有价值。GC 告警、连接池等待、队列堆积这些信号往往都出现在真正 OOM 之前的几百行内。4.3 场景三统计某个用户/IP 的请求频率与失败率用户反馈“我的请求总是失败”这种问题如果直接在日志尾部找基本找不到因为 GB 级日志里单个用户的请求早就被淹没了。正确做法是按用户标识精确过滤再做频率与错误统计。假设日志里有 userId 字段我可以直接grep userId:9527 app.log user_9527.log这时如果文件已经不小再统计状态码分布awk {print $8} user_9527.log | sort | uniq -c | sort -nr输出会显示这个用户各个状态码的请求次数。如果看到 401/403 偏多大概率是 token 失效或权限问题如果全是 200 但用户仍反馈失败可能就是业务逻辑层面的问题需要再深入查具体返回内容。还有一种场景是按 IP 统计请求量排查是否有异常流量grep -oE client_ip[0-9.] app.log | sort | uniq -c | sort -nr | head -10这条命令用-oE提取 IP 字段然后统计 Top 10。在实际流量排查里这比一条条看日志直观太多了。4.4 场景四对比两个时间段的日志差异排查“为什么今天比昨天慢”这类问题最好的方法是做时间段对比。先把两个时间段分别切片成小文件sed -n /2025-06-02 10:00:00/,/2025-06-02 10:10:00/p app.log today_1010.log sed -n /2025-06-01 10:00:00/,/2025-06-01 10:10:00/p app.log yesterday_1010.log然后分别统计耗时均值和中位数我一般用 awkawk {sum$9; count} END {print avg:, sum/count} today_1010.log对比后如果今天的平均值是昨天的两倍再进一步按路径拆分awk {if ($7 ! ) {sum[$7]$9; count[$7]}} END {for (k in sum) printf %s avg%.2fms count%d\n, k, sum[k]/count[k], count[k]} today_1010.log | sort -t -k2 -nr | head -5这样能直接看到是哪个接口的耗时变化最大。再用diff对比接口列表、错误类型分布等指标。整套流程下来基本能快速圈定问题范围而不是靠猜。5. 实时追踪tail -f 的正确打开方式与进阶技巧5.1 tail -f 与 tail -F 的区别别再傻傻分不清排查生产问题时经常需要“盯”日志看实时输出最常见的就是tail -f app.log-f是 follow跟随的意思文件有新内容就自动打印。但这里有个大坑如果日志发生切割比如 logrotate 每天把app.log重命名成app.log.20250601再新建app.logtail -f会继续盯住已经被重命名的旧文件描述符你就再也看不到新日志了。解决方案是-Ftail -F app.log大写-F会按文件名重新打开文件即使日志被 rotate也会自动切换到新文件继续跟随。生产环境请一律使用tail -F我因为这个坑在半夜盯日志时盯了个寂寞后来才长记性。再看一个组合tail -F配合grep只显示包含关键字的实时日志tail -F app.log | grep --line-buffered ERROR注意--line-buffered必须加否则 grep 会走块缓冲日志不会实时输出而是积攒到一块才打印极易漏看。5.2 多文件实时监控与 awk 实时统计有时候你需要同时盯多个服务的日志比如网关和一个后端服务。tail -F支持一次跟多个文件tail -F app-gateway.log app-order.log输出会带文件名头。但如果文件特别多推荐用multitail需要安装它能在同一屏幕内分栏显示多个文件颜色高亮也更好读multitail -f app-gateway.log app-order.log另外实时流量里想看 QPS 变化可以借助awk做流式窗口统计临时用更专业的建议上监控系统tail -F app.log | awk {print $2} | cut -c1-8 | uniq -c这里的思路是按分钟计数cut -c1-8截取时间字段的前 8 位作为分钟标识uniq -c统计相邻重复行数。每分钟打印一次请求量能看到波峰波谷。原理很简单但足够应急。5.3 日志切割logrotate带来的坑与解决方案日志切割是大日志场景下绕不开的话题。很多系统默认 nightly 切割一次但如果你的服务日志一天能写 10GB夜间一次性 rotate 会导致磁盘 IO 尖峰还可能出现“切割瞬间日志写入阻塞”的现象。常见解决方案有这么几种调整切割频率logrotate.conf里把daily改成hourly错峰切割。压缩旧日志配置compress选项切割后自动 gzip能把磁盘占用降到原来的 1/20 左右。保留份数控制配置rotate 7或按大小maxsize 1G避免无限堆积。复制再清空如果服务不支持 reopen 日志文件用copytruncate先复制再清空原文件缺点是可能丢少量日志。当你排查问题时如果发现日志戛然而止先别急着怀疑代码多半是 logrotate 把旧日志切走了。这时候找到对应时间段的切割文件再排查ls -lh app.log*顺便说一句排查历史日志别只盯着app.log按日期后缀列出来的文件才是完整数据。用通配符过滤、组合分析是常态心态而不是抱怨“日志怎么断了”。6. 我踩过的坑与建议直接抄的“日志排查速查表”6.1 五个最常见的生产日志排查翻车现场我把自己和团队这几年在大日志排查上踩过的坑整理成了一张“黑名单”每条都是真实代价换来的。第一个坑生产环境无脑 cat grep 管道把终端和 CPU 全拖垮。3GB 日志 grep 一次本身就要全文件读一遍你要是再连着 grep 三四次等于把 3GB 读了三四遍。正确做法是用sed切时间窗或先grep -n定位行号再切片一次扫描解决问题。第二个坑用 tail -f 盯日志结果日志被 rotate 后盯了个旧文件。前面已经说了生产环境一律用tail -F尤其是服务端日志有 logrotate 配置时这个教训非常深刻。第三个坑直接看日志尾巴忽略了前半个小时已经刷过去的异常。大日志场景下“最新”不等于“最重要”。问题可能发生在 20 分钟前但日志已经刷了几万行正常请求。排查必须“先检索再阅读”而不是“从尾巴往上翻”。第四个坑误以为格式固定直接按空格 awk结果日志里字段错位。很多日志会包含 JSON、堆栈信息字段数不固定。稳妥做法是先head几行确认格式再用grep过滤到特定行再awk避免直接全文件按字段算。第五个坑在日志文件本身所在磁盘上做大量读写分析影响线上服务。日志所在磁盘通常是数据盘高负载读写可能影响同盘其他日志、甚至数据库文件。条件允许时把要分析的大日志cp或scp到另一台机器或者只切片出小文件分析尽量不在生产机器上跑重型聚合命令。6.2 直接抄的“日志排查速查表”下面是我自己团队内部流传的一张速查表每次排查前先看一眼基本能少走很多弯路。我把它分享出来建议直接收藏或贴到自己的笔记里。需求推荐命令说明看最新日志tail -n 200 app.log默认 10 行按需加行数实时追踪日志tail -F app.log注意大写 F兼容 rotate实时只看 ERRORtail -F app.log | grep --line-buffered ERROR必须加 line-buffered找关键字行号grep -n 关键字 app.log先定位行号再切片按行号切片sed -n 100,200p app.log区间前后各扩几行看上下文按时间窗切片sed -n /2025-06-01 10:00/,/2025-06-01 10:10/p app.log注意时间格式必须与日志一致多关键字匹配grep -E ERROR|Exception app.loghead -100排除干扰行grep -v /health/check app.log过滤健康检查等噪声过滤用户/IPgrep userId:9527 app.log按业务标识精筛状态码统计awk {print $8} app.log | sort | uniq -c快速看错误分布接口耗时 Topawk {print $7, $9} app.log | sort -k2 -nr | head -20按耗时倒序交互浏览大文件less app.log进文件后/搜索F跟随导出切片给同事sed -n 100,200p app.log section.log小文件便于分享这张表不是万能的但它能覆盖 80% 的日常排查需求。剩下的 20%基本要靠你对业务日志格式的理解程度。格式越规范awk 能做的分析就越深格式越混乱前期清洗成本越高。最后分享我个人的一个习惯在排查任何 GB 级日志之前先花 30 秒回答三个问题——“我要找什么、大概在什么时间段、用什么关键字能筛出来”。想清楚这三个问题你基本已经不需要 cat 了。希望大家下次再遇到大日志排查能想起这篇文章少踩几个坑。