查日志查到眼瞎?这几个 Linux 命令教你秒级定位线上 Bug

摘要:线上出故障,还在傻傻地把 10G 日志下载到本地?还在用 vim 打开大文件导致服务器卡死?本文汇集了阿里、腾讯大厂运维都在用的 Linux 日志分析“神技”。从基础的 grep 到“上帝视角”的 awk,一文讲透,建议收藏!

很多新手(甚至工作几年的老手)在排查线上问题时,除了 tail -fcat,几乎只会“肉眼扫描”。面对海量日志,就像大海捞针,查到眼瞎也找不到 Bug 在哪。

排查线上问题,有时候不拼技术深度,拼的是“手速”和“工具箱”。 今天,我们就来盘点一下那些 让运维大佬直呼内行 的 Linux 日志分析“骚操作”。从基础到高阶,教你 秒级定位 线上故障!


第一关:扔掉你的 cat 和 vim

在生产环境,永远不要用编辑器打开大日志文件! 这是铁律。不仅慢,而且容易引发 OOM(内存溢出),直接搞挂线上服务。

1. 优雅的 less

less 是查看大文件的神器,它不会一次性加载整个文件,而是按需加载。但你真的会用它的快捷键吗?大部分人只会按空格翻页。

less application.log

必备“骚操作”快捷键(建议背诵):

  • G (大写):直接跳到文件末尾。排查最新报错时必用。
  • g (小写):跳到文件开头。
  • /keyword:向下搜索关键字(按 n 查找下一个,N 查找上一个)。
  • ?keyword:向上搜索关键字。
  • F (Shift+f):进入“实时滚动模式”。
    • 这是最骚的! 效果等同于 tail -f,日志会实时滚动。
    • 看到可疑日志时,直接按 Ctrl + C,立刻回到普通浏览模式,可以继续上下翻页查找上下文。
    • 相比 tail -f,less 的 F 模式实现了“动静结合”。

2. 更有“上下文”的 grep

grep 报错关键字够吗?不够!

你看到了 NullPointerException,但你不知道是哪个参数导致的,因为参数打印在上一行;你也不知道堆栈信息,因为堆栈在下面几行。

错误示范:

grep "Exception" application.log
# 结果:只显示了一行报错,完全不知道前因后果

正确姿势:使用 -C 参数

# 查找 "Error" 关键字,并显示它 前后各 10 行 的内容
grep -C 10 "Error" application.log

# 只要上面的 10 行(Before)
grep -B 10 "Error" application.log

# 只要下面的 10 行(After)
grep -A 10 "Error" application.log

💡 Tips:加上 --color 参数(大部分系统默认开启),关键字会高亮显示,不仅护眼,还能一眼看到重点。


第二关:精准打击,多重过滤

线上日志往往泥沙俱下。你想找真正的 Error,结果屏幕上全是无关紧要的 Info、Debug 日志,或者是某个已知的报错(噪音)刷屏。

这时候你需要 “排除法”“组合拳”

1. 排除干扰项 (-v)

假设日志里全是心跳检测(HeartBeat)的报错,掩盖了真正的业务错误。

# 查找 Error,但 排除 掉包含 "HeartBeat" 的行
grep "Error" application.log | grep -v "HeartBeat"

2. 逻辑或 (-E)

你想同时查 "Error" 和 "Exception"?写两遍 grep?太 Low 了。

# 使用扩展正则(Extended regex),同时查找 Error 或 Exception
grep -E "Error|Exception|Fatal" application.log

3. 精确匹配次数 (-c)

老板问:“这个问题今天发生了多少次?” 不要傻傻地数行数。

# 直接统计出现次数
grep -c "OutOfMemory" application.log

# 或者配合 wc -l (Word Count lines)
grep "OutOfMemory" application.log | wc -l

第三关:上帝视角,Awk 数据分析

如果你只会 grep,你只是个合格的搬砖工;如果你精通 awk,你就是 数据分析师

Awk 是 Linux 下最强大的文本处理工具,它把每一行日志看作一条记录,每一个空格(默认)分割的字段看作列。

假设你的 Nginx 日志格式如下:

192.168.1.5 - - [27/Oct/2023:10:00:01] "GET /api/user HTTP/1.1" 200 1024 "-" "Chrome" 0.050

(注:最后一列 0.050 是响应时间)

场景一:那个 IP 在攻击我?(统计 Top 10 IP)

发现服务器负载飙高,想看看是哪个 IP 访问量最大(取第1列)。

# 1. awk '{print $1}': 打印第一列(IP)
# 2. sort: 排序(uniq 统计前必须排序)
# 3. uniq -c: 去重并统计出现次数
# 4. sort -nr: 按数量(n)倒序(r)排列
# 5. head -n 10: 取前10名

awk '{print $1}' access.log | sort | uniq -c | sort -nr | head -n 10

输出结果示例:

  40240 219.144.89.39    <-- 抓到了!就是这个IP在刷
   7468 219.144.88.173
   7348 219.144.89.114
   7141 219.144.89.44
   5563 122.246.31.49
   5534 122.246.30.211
   5279 122.246.30.27
   4631 219.144.88.174
   4611 113.219.202.173
   4522 113.219.202.141
 ...

高能预警:这行命令价值千金!在遭遇 DDoS 攻击爬虫刷量 时,这就是你的“照妖镜”。

场景二:谁在拖慢系统?(耗时分析)

假设日志的 最后一列 是响应耗时。我们要找出耗时超过 1秒 的接口,并统计排名。

# 1. 筛选:如果最后一列 ($NF) 大于 1
# 2. 打印:打印第 7 列(URL)
awk '$NF > 1 {print $7}' access.log | sort | uniq -c | sort -nr | head -n 10

如果你想看具体的耗时和 URL:

awk '$NF > 1 {print $NF, $7}' access.log | sort -nr | head -n 10

第四关:时间穿越,范围搜索

故障发生在 10:00 到 10:30 之间,日志文件有几百万行,怎么只看这段时间的?

1. 笨办法(不推荐)

grep 拼命搜时间字符串。
grep "10:00", grep "10:01"... 缺点是如果是跨多分钟,你需要写一堆正则,非常痛苦。

常见的“坑”:Sed 范围搜索

网上很多教程教你用 sed -n '/10:00/,/10:30/p'
千万别盲目用!
sed 只是机械地匹配字符串。如果你的日志包含好几天的数据,或者“10:00”刚好出现在某行报错信息正文里,sed 会像个开关一样被反复打开,导致你查出一堆无关日期的脏数据。

王者解法:Awk 字符串比较

awk 支持字符串的字典序比较(Lexicographical Comparison)。只要你的日志时间格式是标准的(如 HH:mm:ss 或 ISO8601),就可以直接比大小!

假设日志格式如下(时间在第 4 列,例如 [10:00:00]):

2023-10-27 10:00:01 INFO ...

命令如下:

# 假设时间在第 2 列 ($2)
# 语法: awk '$列号 >= "开始时间" && $列号 <= "结束时间"'
awk '$2 >= "10:00:00" && $2 <= "10:30:59"' application.log

为什么这个更强?

  1. 精准:它是真的在比大小,而不是在“找文字”。
  2. 无视缺失:即使日志里正好没有 10:00:00 这一秒(比如那一秒没人访问),只要有 10:00:01awk 依然能正确识别范围。而 sed 如果找不到起始锚点,可能直接就失效了。
  3. 支持跨天精准定位:如果加上日期,比如 $1" "$2 (日期+时间),就能精准定位到某一天的具体时间段,彻底告别“昨日重现”。
# 组合日期和时间进行比较(假设 $1是日期,$2是时间)
awk '$1" "$2 >= "2023-10-27 10:00:00" && $1" "$2 <= "2023-10-27 10:30:00"' application.log

💡 经验之谈:永远不要相信日志里的每一秒都存在,“比较大小”永远比“精确匹配”更可靠


第五关:Json 时代的利器 jq

现在的微服务日志大多是 JSON 格式。用 awk 或者 grep 处理 JSON 简直是噩梦。这时候你需要 jq(大多数服务器需要单独安装 yum install jq,但绝对值得)。

假设日志是这样的:

{"time": "2023-10-27", "level": "ERROR", "msg": "DB timeout", "meta": {"user_id": 1001, "trace_id": "abc-123"}}

1. 格式化美化

# 让一坨 JSON 变成可读的缩进格式
cat app.json.log | jq .

2. 提取特定字段

老板只想看 报错信息用户ID,不想看其他乱七八糟的。

# 1. grep 筛选 ERROR
# 2. jq 构造新的 JSON 对象输出
grep "ERROR" app.json.log | jq '{message: .msg, user: .meta.user_id}'

输出结果:

{
  "message": "DB timeout",
  "user": 1001
}

终极必杀:组合拳实战 Case

光说不练假把式。我们来看两个真实的线上“灵异” Case。

Case 1:磁盘空间报警,但找不到大文件

现象

  • 监控报警:磁盘使用率 100%。
  • 你登录服务器,执行 df -h 确认满了。
  • 执行 du -sh * 统计各目录大小,结果加起来才占了 50%。
  • 疑问:剩下的 50% 空间被鬼吃了?

原因
通常是有大文件(如日志)被直接删除了(rm),但进程(如 Java/Nginx)还持有该文件的句柄(File Descriptor),导致文件虽然在目录里看不到了,但实际上并没有释放磁盘空间。

骚操作定位

# 列出所有被打开的文件,并筛选出状态为 "deleted" 的
lsof | grep deleted

结果示例

java   1234 root   1w   REG  253,0  10G  123456 /var/log/app.log (deleted)

看到那个 10G 了吗?就是它!

解决

  1. 找到 PID (1234),重启该进程。
  2. 以后别直接 rm,应该用 echo "" > app.log 来清空文件。

Case 2:抓取线上偶发的 500 错误

现象
只有极少数请求报错,刷新就没了,tail -f 刷得太快根本看不过来,眼睛都花了。

骚操作定位

# --line-buffered: 保证 grep 能实时输出,而不是等缓冲区满了再吐出来
tail -f access.log | grep --line-buffered " 500 "

或者把报错日志实时“分流”到单独文件,慢慢分析:

tail -f access.log | grep --line-buffered " 500 " >> error_only.log &

(你可以去喝杯咖啡,过一会回来 cat error_only.log 慢慢看)。


总结

排查线上问题, 就是优雅, 就是专业。

  1. 看大文件:用 less,善用 GF
  2. 查关键字:用 grep -C 看上下文,grep -v 排除噪音。
  3. 做统计awk + sort + uniq 是运维统计学的灵魂。
  4. 查 JSON:装一个 jq,从此不求人。
  5. 查幽灵文件lsof | grep deleted 专治磁盘灵异事件。

命令行不仅是工具,更是一种 工程师文化。当你能在黑色的终端窗口里指点江山,那种掌控感,是用鼠标点点点永远体会不到的。

收藏这篇文章,下次线上报警时,你就是那个全组最靓的仔!


👇 觉得有用?点个「推荐」或「分享」给那个正在查 Bug 的兄弟吧!

Q.E.D.


寻门而入,破门而出