同事查日志太慢,我现场教他一套 grep 组合拳!
我那天是真被气笑了。
那是前两天,晚上九点多,我在工位啃外卖,准备摸鱼刷会儿视频,突然运维在旁边喊:“谁懂下你们这个订单服务啊,日志查半天看不出来问题啊,人都麻了。”
我一看,是我们组那个刚转正的小同事,小王,正对着一台 8G 内存的小服务器,tail -f 一路狂刷,屏幕上全是日志雨,眼睛瞪得跟铜铃一样。
我走过去一看,他的操作大概是这样的:
tail -f app.log
# 然后一行一行用眼睛找 "ERROR" "超时" 之类
我说哥,你这是肉身 grep 呀?
他还挺委屈:“我不就想看看报错嘛,tail 一直跟着看,就…有点慢。”
我当时没忍住,直接现场给他上了一节“grep 组合拳实战课”,十几分钟,把问题捞出来,那叫一个干净利落。
下面就把那天的“实战回放”跟你说说,顺便穿插点我们 Java 服务平时怎么配合 grep 查日志的,回头你遇到线上故障,别再像他那样肉眼扫屏了…
一、先说场景:别一上来就全量 tail -f
我先让小王把命令停了,问他几个问题:
现在是哪个接口报错? 大概什么时间? 有没有 traceId 或者订单号?
他挠了挠头:“就订单创建那个接口,大概 20:10 左右吧,traceId 也有,就是没想到用它查…”
我直接让他把 traceId 贴给我,比如这样一串:
traceId=7f2a9a0f1234567890abcdef
我跟他说:以后查日志,第一步就两件事——“锁时间 + 锁标识”。
时间范围先把日志文件缩小,标识(traceId / orderId / userId)把内容缩小,不要一上来就全量 tail -f。
我们 Java 代码里日志得这么打才好查,给他看了个简化版:
@Slf4j
@RestController
@RequestMapping("/order")
publicclassOrderController{
@PostMapping("/create")
public OrderCreateResponse create(@RequestBody OrderCreateRequest req){
String traceId = MDC.get("traceId"); // 一般在过滤器里放进来的
log.info("create order start, traceId={}, userId={}, amount={}",
traceId, req.getUserId(), req.getAmount());
// ... 业务逻辑,略
log.info("create order success, traceId={}, orderId={}",
traceId, order.getId());
returnnew OrderCreateResponse(order.getId());
}
}
你只要保证每条关键日志里都带上 traceId / orderId 等字段,后面 grep 才有拳可以打,不然真就只能人肉扫描。
二、第一招:单关键字 + 行号,先把错误捞出来
我先让他把那一段时间的 ERROR 全拉出来看看。
grep "ERROR" app.log | head
他一看,脸都白了:“哥,这得多少啊…”
那没办法,线上服务跑一天,日志几百 MB 很正常。 我说:那就加时间、加 traceId、加行号,精准打一拳。
比如我们知道报错大概在 2025-12-26 20:10 左右,又有 traceId,那就:
grep "2025-12-26 20:10" app.log | grep "7f2a9a0f1234567890abcdef" -n
-n 是打印行号,这个线下排查特别好用——因为有了行号,就可以用上下文了,下一招就用得到。
小王看到屏幕上只剩下几行日志,立马来劲了:“哎,这清爽多了嘛。”
顺手再教他一个统计的小花活,快速看某个错误出现了多少次:
grep "OrderTimeoutException" app.log | wc -l
屏幕上蹦出来一个数字,比如 127,告诉他: “你看到 127 这个数没,这才是说服产品和领导的证据,别老说‘好像经常超时’。”
三、第二招:多关键字“套娃”过滤,噪音一层一层剥掉
很多人一说 grep 多条件,就开始研究 grep -E "A|B" 这种写法,当然也可以。 但实战里我更喜欢 管道套娃:一层层往下筛。
那天日志里情况是这样的:
很多 ERROR其实是重试失败但无关紧要真正的核心问题都是 OrderCreateException这个类抛出来的
我就直接让他这么搞:
grep "ERROR" app.log \
| grep "OrderCreateException" \
| grep "7f2a9a0f1234567890abcdef" -n
你看,几层过滤一叠:
第一层:先从文件里把 ERROR 全拉出来 第二层:只要订单创建异常的 第三层:只要那次请求的 traceId
小王看那几行日志刷一下就出来,忍不住“卧槽”了一句。 我说这还只是单向过滤,看我加一层排除的:
grep "ERROR" app.log \
| grep "OrderCreateException" \
| grep -v "Retrying" \
| grep "7f2a9a0f1234567890abcdef" -n
-v 是取反,排除掉带 Retrying 的日志,把重试噪音先干掉。
他问我:“为啥不用一个正则写完?” 我就一句话:实战里越简单越好改,线上排查的时候,你没空调今天才学的高级正则。
四、第三招:上下文 -C/-A/-B,一把把调用链拉出来看
光看一行 ERROR,很多时候你也不知道前因后果, 这时候多半就会有人开始用 less 上下翻,效率也不高。
我直接让他在刚刚那条错误日志的基础上,加一个 -C 5:
grep "7f2a9a0f1234567890abcdef" app.log -n -C 5
-C 5 就是前后各带 5 行上下文。 还有两个变体:
-A 5:after,只要后面的 5 行-B 5:before,只要前面的 5 行
这个命令一跑,整个调用链就被拉出来了:
上面几行是 Controller 收到请求 中间几行是 Service 调用下游 最下面几行是异常栈
我顺便给他看我们 Java 里配的日志 pattern,大概类似这样,方便上下文阅读:
<pattern>
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level
traceId=%X{traceId} userId=%X{userId}
%logger{36} - %msg%n
</pattern>
配成这样,grep 出来的一片上下文,信息就很规整:时间、线程、级别、traceId 一目了然。
五、第四招:tail -f + grep 实时盯,只看你关心的那一小撮
讲到这,小王又问:“那我刚刚 tail -f 看实时日志是不是也能用 grep?”
当然能,而且必须用,不然实时日志真的是看心态。
比如你想盯线上实时的订单错误:
tail -f app.log | grep "OrderCreateException"
如果你怕 grep 自己挂掉,把 tail 一起干掉,可以来一个小花招:
tail -F app.log | stdbuf -oL grep "OrderCreateException"
-F 会自动跟踪文件,日志滚动也不会断,stdbuf -oL 是让 grep 行缓冲,避免缓存太久才输出(这个按需用,不懂也可以先不加)。
要盯某个用户的操作也一样,前提是你日志里打印了 userId:
Java 代码里这么打日志:
log.info("user login, userId={}, ip={}", userId, clientIp);
命令行就:
tail -f app.log | grep "userId=123456"
你会发现,有时候你根本不需要什么“可视化日志系统”, 一行 grep 就能帮你把关键行为盯起来。
六、第五招:多文件 / 目录递归搜,一次查一整天的日志
小王又问:“那我现在这个问题,是八点多开始的,我这个 app.log 只能看到一会儿,昨天的都切在 app.log.2025-12-25 里了,得一个个查吗?”
我说这就轮到 -R 出场了,整目录打包查一遍:
grep "7f2a9a0f1234567890abcdef" /data/logs/app/*.log
如果日志多,还按日期建了目录,比如 /data/logs/app/2025-12-26/app.log, 就这么搞:
grep -R "7f2a9a0f1234567890abcdef" /data/logs/app
要注意两点:
日志多的时候配合
--include/--exclude,不然可能把 gc.log、慢 SQL 日志全扫进去:grep -R "OrderCreateException" /data/logs/app \
--include="app.log*" \
--exclude="*gc.log"想看哪天最爱出错,可以简单粗暴来一个:
grep "OrderCreateException" /data/logs/app/app.log.* \
| awk '{print $1}' \
| sort | uniq -c | sort -nr | head这里简单说下:
有时你跟老板说“最近几天错误变多了”, 不如直接给他一个“某某一天 532 次”的数字,更有说服力。
awk '{print $1}'取日志里第一列(通常是日期),uniq -c统计次数,sort -nr按数量倒序排序。
七、第六招:结合 Java 错误栈,只看顶部关键信息
那天真正的问题,其实就是一个下游接口超时, 异常栈大概是这样的:
java.net.SocketTimeoutException: Read timed out
at java.base/java.net.SocketInputStream.socketRead0(Native Method)
at java.base/java.net.SocketInputStream.socketRead(SocketInputStream.java:115)
...
at com.demo.payment.client.PaymentClient.pay(PaymentClient.java:87)
小王说:“我 grep 出来的时候,一大串栈太长了,屏幕滚得我头晕。”
我就让他这么用:
grep -n "SocketTimeoutException" app.log -A 3
只看异常行 + 后面 3 行,一般足够看到责任方法在哪一层。
如果你想把所有异常栈里最顶上那一行统计一下,看哪个方法最爱报错,可以再骚一点:
grep "Exception" app.log -A 1 \
| grep "at com.demo" \
| awk '{$1=""; print $0}' \
| sort | uniq -c | sort -nr | head
这个命令稍微复杂点,你不用记死,大概知道思路就行: 就是通过 grep + awk 把“异常类型”对应的“首个业务栈”拉出来,做一个 Top N。
八、第七招:把常用组合拳写成 alias,查日志快到飞起
讲到最后,小王已经有点上头了:“感觉你这十几分钟讲的,比我之前一年自己瞎查强多了。”
我说:要想真正用顺手,把常用的组合拳封装一下,不然下次你还得想半天。
比如我们团队每台机子上都约定一个 ~/.bashrc 里的 alias:
# 按 traceId 查一整天日志
alias glt='f(){ grep -R --color=auto "traceId=$1" /data/logs/app; }; f'
# 实时看订单错误
alias gerr='tail -f /data/logs/app/app.log | grep --color=auto "OrderCreateException"'
改完记得 source ~/.bashrc 一下。
这样小王下次只需要敲:
glt 7f2a9a0f1234567890abcdef
屏幕上就把所有机器、所有日志文件里这条调用链给他拉干净了。
再配合我们 Java 统一的日志格式,traceId 放在固定位置, 整个排查过程就非常顺畅。
说了这么一大圈,其实那天的问题本身一点都不复杂: 就是下游支付服务偶尔超时,我们的重试次数又配得有点激进, 导致同一个请求被重复打了好几次,最后积压了一堆超时。
但如果不用 grep 组合拳,你看到的就只是一堆 ERROR, 甚至可能会怀疑是数据库、是网络、是 GC,乱猜一通。
有时候差的就是这几条命令带来的视角变化。
行了先这样吧,我这会儿还得去催一下小王把他那段乱七八糟的日志格式改了, 要不下次再出问题,他又得喊我过去救火…
以为能躺赚,结果“养虾”变成了“养雷”,第一批“养虾人”已经失眠了……
DeepSeek被针对,Anthropic指控三家中国AI蒸馏剽窃,马斯克硬刚“贼喊抓贼”!
明明大厂裁员滚滚,为什么运维还这么难招?
在 SQL 中写了 in 和 not in,技术总监让我明天不用来了
年底了!系统稳如狗,甲方觉得我们没工作量,怎么收运维费?
为什么DeepSeek火之后,人们想到的是大量裁员,而不是实行上三休四?
《AI数据分析之ChatBI发展与应用实践》白皮书(附下载)正式上线啦
号外!《核心系统分布式数据库选型指南》电子书(附下载)正式上线
解锁数据架构现代化密码,《实时数仓选型指南》电子书(附下载)正式上线啦