ARTICLE DETAIL

资讯详情

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

grep组合拳:高效排查大日志文件的实用技巧

grep组合拳:高效排查大日志文件的实用技巧 上周三下午我正在工位上改脚本隔壁同事探过头来一脸无奈“哥这个日志文件十几个 G我用 VS Code 一打开就卡死等半天只看到转圈。我把报错那几行复制给你行不行”我说你先别急着复制把你终端打开。然后我花十分钟现场给他上了一套grep组合拳从最基础的grep ERROR app.log一路打到tail -f app.log | grep --line-buffered OrderService。他当场从“等文件打开”变成“秒出结果”眼睛都亮了。这篇就是当时那套打法的完整复盘。不管你是运维、后端、客户端开发还是天天跟日志打交道的测试只要你的工作里有过“打开日志文件卡半天”“几千行异常刷屏找不到重点”“想统计某个接口的报错次数却只会肉眼数”的经历这套东西都值得花十分钟看完。我不讲那些花里胡哨的理论就讲怎么用grep把日志问题查明白。1. 先想清楚查日志慢慢在哪很多人查日志慢不是工具不行是思路从一开始就偏了。1.1 日志文件不是给你“打开”的我见过太多人包括曾经的我自己第一反应都是“双击打开日志文件”然后用编辑器或者 IDE 去翻。文件小的时候没事一旦到了几 GB、几十 GB编辑器要渲染整个文件、建立索引、做语法高亮内存直接爆炸鼠标都挪不动。但日志文件本质是什么是一行一行追加写入的文本流。它天生就不是为了“整体打开”设计的而是为了“按行扫描”设计的。就像图书馆里几万本书你不会一本一本地从头翻到尾找某个词而是先去检索系统里查关键词拿到书号和页码再精准定位。日志也一样。你要的不是“看到全部”而是“找到匹配的行”。这个需求命令行工具比任何图形界面都合适。1.2 grep 解决问题的真正思路过滤而不是阅读grep做的事情非常朴素从输入里逐行读取如果某一行匹配你给的模式就输出这一行不匹配的就丢掉。它不需要把整个文件加载进内存内存占用极低速度极快扫描一个几 GB 的文本文件也就是秒级的事。你可以把它理解成一个“漏斗”。文件里的每一行都从漏斗上方倒进去你定好筛子的形状留下来的就是你关心的内容。所谓“查日志”本质就是不断调整筛子缩小范围直到剩下真正有用的十几行。所以核心思维要转变不要想着“我要把日志看完”而是“我要让日志里不相关的内容自动消失”。grep就是干这个的。2. 单兵武器grep 常用参数和标准姿势组合拳的前提是基本功扎实。先把grep最常用的参数过一遍这些是后续所有套路的零件。2.1 必会参数速查我用一张表把高频参数列出来建议直接收藏参数作用典型示例-n显示匹配行在文件中的行号grep -n ERROR app.log-i忽略大小写grep -i timeout app.log-v反向匹配输出不匹配的行grep -v ^# nginx.conf-E使用扩展正则支持 等语法-c只统计匹配行数不输出内容grep -c 500 access.log-l只列出包含匹配内容的文件名grep -l 关键错误 /var/log/*.log-r递归搜索目录下所有文件grep -r OrderService /app/logs/-A n输出匹配行及其后 n 行grep -A 5 NullPointer app.log-B n输出匹配行及其前 n 行grep -B 2 Exception app.log-C n输出匹配行前后各 n 行grep -C 3 ERROR app.log-o只输出匹配到的部分而不是整行grep -oE [0-9] app.log-m n最多匹配 n 行后停止防止刷屏grep -m 20 ERROR app.log--include配合-r只搜指定后缀文件grep -r --include*.log ERROR /data/--colorauto匹配内容高亮显示grep --colorauto ERROR app.log-q安静模式不输出内容常用于脚本判断grep -q ERROR app.log echo 有异常这些参数单独用已经能解决 70% 的问题。剩下的 30%靠的是把它们组合起来。2.2 几个新手最容易忽略的细节用grep查日志有几个小细节特别容易让新手栽跟头。第一模式最好老老实实加引号。日志里经常出现空格、括号、点号这些特殊字符不加引号的话shell 会先对模式做展开轻则匹配不到重则报错。比如查OrderService Timeout这种带空格的必须写grep OrderService Timeout app.log。第二grep的退出码是有含义的。匹配到结果返回 0没匹配到返回 1文件不存在或没有权限返回 2。这在脚本里非常有用比如判断日志里有没有出现某个关键字if grep -q OutOfMemoryError app.log; then echo 检测到内存溢出需要处理 fi这里用-q的原因是我们只关心有没有不关心具体内容让它安静地跑完就行还能省掉大量输出。第三--colorauto和管道的配合要留意。直接在终端里用--colorauto能高亮关键词非常直观。但如果你要把grep结果再管道给其他命令比如grep --colorauto ERROR app.log | head -n 20有时候会带着转义序列干扰后面的处理。所以建议交互式排查时用--colorauto写脚本时尽量不用或者直接去掉颜色。第四大日志文件里别傻乎乎全量输出。一个日志文件里有几十万条 ERROR直接用grep ERROR app.log会把终端刷得密密麻麻你还得 CtrlC 中断。这时候加个-m限制输出行数或者配合head/tail只看头尾体验完全不一样。3. 组合拳多条件过滤与管道协作单条grep是单兵武器真正强大的是把grep和管道、awk、sort、uniq 组合起来形成一套组合拳。这也是“现场教学”的重点部分。3.1 与或非一个文件里筛出你要的报文实际日志里你很少只查一个关键词。更多的情况是我要找某个服务的报错但要排除健康检查的噪音或者我要查某个请求 ID 相关的所有记录但又不能漏掉大小写不一致的情况。这时候就需要“与或非”的思维。多个条件都满足AND用管道把两次grep串起来。grep OrderService app.log | grep ERROR前一个grep先筛出包含OrderService的行后一个grep再从中筛出包含ERROR的行。效果等同于“两个条件同时满足”。多个条件任意满足OR用-E加竖线。grep -E ERROR|WARN|FATAL app.log注意这里必须用扩展正则所以-E不能丢。排除某个条件NOT用-v。grep OrderService app.log | grep -v healthCheck这行的意思是找出所有包含OrderService的日志但不包含healthCheck的。很多系统的健康检查接口会周期性打日志排掉它真实业务报错就露出来了。三个组合在一起就是一条非常经典的排查命令grep OrderService app.log | grep -v healthCheck | grep -iE error|timeout先定向到业务模块再排除噪音再忽略大小写匹配异常关键字。逻辑清晰结果精准。还有一种情况你想匹配同一行里同时出现的多个词且行很长、不方便两次过滤。可以直接用正则描述“前面出现 A后面出现 B”grep -E OrderService.*timeout app.log.*在正则里表示任意字符出现任意次所以这行会匹配“同一行里 OrderService 后面某个位置跟着 timeout”的日志。3.2 时间范围、上下文和频率统计实战日志最常干的一件事就是按时间窗口筛选。大多数日志格式长这样“2025-06-01 10:23:45,678 [线程名] ERROR ...”时间戳就在行首。这时候grep结合字符串前缀匹配可以快速锁定时间范围。比如只看 6 月 1 日上午 10 点到 11 点的 ERRORgrep 2025-06-01 10: app.log | grep ERROR想精确到 10 点 20 分到 29 分 用正则字符组grep -E 2025-06-01 10:2[0-9]: app.log这个[0-9]表示这一位数字可以是 0 到 9 之间的任意一个等于把 20~29 分钟全包了。这个技巧在处理“某个时段异常增多”的问题时极其好用。查看异常上下文是另一个高频场景。日志里的异常往往是堆栈形式抛出 Exception 之后后面跟着十几行堆栈信息只看异常那一行远远不够。这时候-A、-B、-C就派上用场了。grep -A 15 Exception app.log这会把异常行以及它后面的 15 行全部打出来正好覆盖一整个 Java 堆栈。如果你想知道异常出现前发生了什么就用-B前后都想要就用-C。统计某个关键字出现的频率是grep组合拳里的经典套路。配合sort和uniq -c你可以把日志里的同类错误聚合grep -oE [A-Za-z]Exception app.log | sort | uniq -c | sort -rn | head -20拆开来看grep -oE [A-Za-z]Exception只提取匹配到的异常类型名称。sort把相同的异常类型排到一起。uniq -c统计每类异常出现次数。sort -rn按次数从大到小排序。head -20取前 20 名。一秒钟就能告诉你这个日志文件里哪种异常最多占比多少。这比肉眼翻日志可靠太多了。3.3 配合 awk/sed/uniq 处理日志字段grep擅长“选行”但日志里经常需要“选列”。比如 access log 里第一列是客户端 IP第七列是请求路径最后几列是响应时间。这时候把grep输出交给awk按列提取再交给sort/uniq做统计就是完整的统计链路。统计访问量最高的前 10 个 IPawk {print $1} access.log | sort | uniq -c | sort -rn | head -10这里没用到grep但它和grep配合能玩出更多花样。先按状态码过滤再统计 IP 分布grep 500 access.log | awk {print $1} | sort | uniq -c | sort -rn | head -10注意 500 这个匹配模式access log 里状态码两侧一般有引号和空格直接写 500 可以精准匹配 HTTP 500 状态避免误伤别的地方出现的“500”字样。如果日志每行字段不是固定空格分隔而是用逗号或者竖线awk还可以指定分隔符grep ERROR app.log | awk -F , {print $1, $3}-F ,表示把一行按逗号切成多列然后只输出第 1 列和第 3 列。这在查 JSON 格式、CSV 格式的日志时非常顺手。sed也常用来按行号取区间。有时候你已经通过grep -n定位到关键行的行号接下来想看这一行前后 100 行又不想用-A/-B一条条数可以这样grep -n 关键错误 app.log # 假设输出: 23567: 关键错误... sed -n 23467,23667p app.logsed -n 开始行,结束行p会精确打印指定行号区间比一个个数上下文高效得多。4. 真实排障场景实录从“不会查”到“一套带走”光讲参数没用得看实战。我选几个当天现场教同事的真实场景完整走一遍思路和命令。4.1 场景A报错持续刷屏先定位规律同事遇到的第一个问题是应用日志疯狂刷异常他怀疑是某个接口出了问题但打开日志文件后完全不知道从哪看起。我给他的第一句话是先缩小范围再看具体内容。tail -n 5000 app.log | grep -E Exception|ERROR | head -n 30这里用tail -n 5000只取最后 5000 行而不是对着整个十几 G 的文件跑全量grep。原因是报错“正在发生”最近的日志最有价值全量扫描既慢又可能把大量历史噪音也带出来。看到最近的报错内容后紧跟着统计异常类型grep -E Exception|ERROR app.log | grep -oE [A-Za-z]Exception | sort | uniq -c | sort -rn | head -10结果出来前几位全是NullPointerException。这就有了方向不是某个第三方超时而是代码里空指针。接下来再顺着异常堆栈里的类名和方法名回到代码里定位那个可能为 null 的对象。整个过程一分钟不到。同事感叹“我之前都是肉眼往下翻翻得眼睛都花了。”4.2 场景B按照时间窗口揪出一次慢请求另一个常见需求是用户反馈某个时间点系统很卡要查那个时间窗口里到底发生了什么。先按时间缩小范围grep 2025-06-01 10:2[0-9]: app.log | grep -E 耗时|cost|slow|timeout第一条grep把 10 点 20 到 29 分的日志全捞出来第二条再筛出和耗时相关的关键字。如果日志里没有“耗时”这种字段那就退一步直接看这个窗口里的 ERROR 和 WARNgrep 2025-06-01 10:2[0-9]: app.log | grep -E ERROR|WARN | head -n 50如果日志系统有请求 ID 或 traceId那就更简单了。先找到某个慢请求的请求 IDgrep 2025-06-01 10:2[0-9]: app.log | grep 耗时 | head -n 5拿到类似requestId8f3a2b...之后全日志搜这个 ID 的所有记录grep 8f3a2b /app/logs/*.log一条命令把这个请求在多个日志文件里的生命周期完整拉出来。从入口到出口每一步耗时都摊在眼前。这就是链路追踪真正落地的方式——哪怕没有复杂链路追踪系统靠grep照样能查。4.3 场景C统计接口调用次数和来源 IP TopN同事还问过一个很实际的问题运营说某个接口最近调用量暴涨能不能查一下到底是谁在调常规做法是看接入层日志比如 nginx 的 access.log 或网关日志。一行日志一般长这样10.0.12.34 - - [01/Jun/2025:10:23:45 0800] GET /api/v1/order HTTP/1.1 200 1234 - curl/7.68.0先按接口路径过滤再统计来源 IPgrep /api/v1/order access.log | awk {print $1} | sort | uniq -c | sort -rn | head -20这条命令的输出会直接告诉你调用这个接口最多的 20 个 IP 分别调了多少次。如果排前面的全是同一个内网 IP大概率是某个定时任务在刷如果是多个不同 IP 集中出现可能是活动流量也可能是有问题的调用方。想统计状态码分布同理grep /api/v1/order access.log | awk {print $9} | sort | uniq -c | sort -rn$9 是 access log 里的状态码字段位置。这样你能一眼看到 200、500、502 各占多少接口是不是大面积报错清清楚楚。4.4 场景D实时跟踪日志出问题时当场发现线上问题还有一种情况日志在疯狂滚动你要“盯”着最新输出同时只关心某些关键字。用编辑器打开日志文件看实时追加根本做不到。正确姿势是tail -f配合greptail -f app.log | grep ERRORtail -f会持续跟踪文件新增内容新日志一出现就输出再交给grep过滤。这样终端上只会滚动和你关键字相关的行噪音全部消失。但这里有个关键细节一定要加--line-buffered。tail -f app.log | grep --line-buffered ERROR为什么grep往管道输出的时候默认是块缓冲攒够一批数据才往外吐。如果日志产生得慢你可能等半天什么都看不到还以为命令没生效。--line-buffered告诉grep每匹配到一行就立刻输出别攒着。加了它实时性立刻拉满调试体验完全不同。配合上下文参数还能在实时输出里连堆栈一起看tail -f app.log | grep -A 5 --line-buffered Exception只要你盯的日志一出现 Exception它后面 5 行堆栈立刻打出来方便你当场判断问题现场。5. 和日志绕不开的几件事轮转、清空、采集grep是查日志的利器但有些日志运维上的配套思维也得懂不然会遇到“文件太大不想查”“磁盘满了清不掉”的尴尬。5.1 日志轮转、清空和保存的细节日志文件不是越大越好。grep再快在几十 GB 的文件上全量扫描也需要时间。成熟的线上服务都会配日志轮转用logrotate按天或按大小切割旧日志、压缩归档只保留最近 N 份。这样单文件体积可控grep也快。清理日志也要注意方式。很多时候磁盘满了你想清掉某个还在被进程写的大日志。直接rm app.log是不推荐的做法进程依然持有旧文件句柄磁盘空间不会立刻释放而且应用可能写不到新文件。正确做法是截断 app.log # 或者 truncate -s 0 app.log这两条命令都是把文件内容清空但文件本身还在文件句柄也不会失效进程能继续正常写日志磁盘空间立刻释放。实测这是线上清日志最稳的一招。顺便提一句远程调试的场景如果你用的是 MobaXterm 这类终端工具它自带“保存会话日志”功能可以把每次操作的终端输出原样存到本地文件。现场排障时把日志输出保存下来事后复盘或者贴给同事看比截图靠谱得多。5.2 采集与集中检索grep 不是终点单机时代grep就是日志排查的王者。但到了几十台机器、上百个服务逐台登录去grep显然不现实。这时候就需要日志采集链路比如 Filebeat 采集日志文件发给 Kafka、Logstash最终进 Elasticsearch。查日志不再是敲grep而是去 Kibana 里写查询语句。那grep就过时了吗没有。很多场景下你依然得先登录某台机器快速看一眼本地日志确认问题再决定要不要去集中检索平台里做复杂查询。grep是你离日志最近的一把刀永远用得上。集中采集也有简化方案。对于网络设备、交换机这类不好装 agent 的场景可以用 syslog-ng 把它们产生的日志统一收集到一台服务器写到本地文件然后继续用grep检索。原理都是一样的日志最终是文本流只要落到文件里grep就能上场。6. 常见问题速查与避坑指南最后把日常用grep查日志会遇到的坑整理一下。这部分的每一行几乎都是我或者身边同事真实踩过的。6.1 常见问题速查表现象原因解决办法终端里中文乱码日志文件是 GBK 等编码iconv -f gbk -t utf8 app.log | grep 关键字显示Binary file matches文件包含二进制字符加-agrep -a error app.log命令像是没执行没有任何输出模式没匹配到或模式被 shell 转义检查退出码echo $?模式加引号tail -f | grep半天不输出缺少--line-buffered加上--line-buffered正则里的 不生效基础正则不认识 搜索目录提示Is a directory没加-r加-r或指定文件清空日志后磁盘没释放进程还持有旧文件句柄用truncate -s 0而不是rm想查端口对应的进程不是日志场景但经常混在一起问用ss -tulnp | grep 3690或lsof -i:3690表格里最后一行虽然严格说不是查日志但很多人排查问题的时候日志和端口往往是同一个现场。ss -tulnp | grep和grep排查日志是同一个思维模式先过滤再定位不在一大堆输出里翻找。6.2 我踩过的几个坑第一个坑拿到大日志第一件事别全量 grep。我早年间有个习惯拿到文件就是grep ERROR全量扫一遍。后来线上一个 20 GB 的日志全量扫一次要一分多钟急得直跺脚。后来学乖了先tail -n聚焦最近的部分或者先用文件头部的时间戳估算日志覆盖范围再决定扫全量还是扫区间。排查效率提升不止一个量级。第二个坑忘了给模式加引号结果啥也没匹配到。有一次我查日志里的[ERROR]没加引号grep [ERROR] app.log被 shell 当成字符组处理匹配到的全是奇奇怪怪的行真正想找的 ERROR 一条都没有。这就是为什么我后面习惯性所有模式都用引号包起来——哪怕模式里没有特殊字符也费不了多少事但能挡掉一大类问题。第三个坑用cat app.log | grep这种多余管道。我见过很多教程和同事习惯这么写但它本质上多起了一个进程、多读了一遍文件没有任何收益。直接grep xx app.log就行。管道是好东西但要用在该用的地方比如tail、sort、awk和grep的组合而不是为了“看起来像标准姿势”硬加。第四个坑不看日志格式规范上来就猜字段位置。前面用awk {print $1}能取 IP前提是日志字段用空格分隔。实际项目里日志格式五花八门有 JSON、有竖线分隔、有“字段值”模式。拿到一份新日志先head -n 5看几行原始格式再决定用$1还是-F指定分隔符又或者配grep -oE用正则直接提取。先看格式再动手能少走很多弯路。还有个小技巧查 Windows 相关日志时grep并不能直接处理 Event Log。Windows 事件日志可以通过事件查看器、wevtutil或 PowerShell 的Get-WinEvent来查询导出成文本后再交给grep做二次过滤。思路相通工具不同别在一个地方死磕。我个人现在接手任何新服务第一件事就是先看日志格式样例时间戳在第几列、日志级别怎么写的、有没有 traceId。然后给自己定个规矩——排查问题时先想清楚“我要过滤什么”而不是“我要看全部”。这套习惯帮我省下来的时间已经多到数不过来了。希望这套grep组合拳也能让你下次查日志的时候少卡几分钟早几分钟下班。
返回列表