3. 日志采集工具:tail、grep、awk、sed 在日志分析中的实战用法

日志分析的第一步是什么?不是写脚本,不是上平台。说白了,是先把日志「拿到手」。

我见过太多人,一上来就想着用 ELK、用 Splunk,结果连日志文件在哪、怎么快速翻看都不清楚。嗯,这其实是个基本功问题。今天我们就聊聊 Linux 下四个最经典的日志处理工具——tail、grep、awk、sed。它们单个拎出来都不难,但组合起来,能解决 90% 的日常日志分析需求。

核心思路:tail 负责「看」,grep 负责「筛」,awk 负责「切」,sed 负责「改」。四者配合,一条命令就能完成从原始日志到结构化数据的转换。

日志采集四件套:各司其职 日志文件 tail grep awk sed 实时追踪 / 查看尾部 正则匹配 / 过滤行 列切割 / 格式化输出 流编辑 / 替换删除 组合使用:tail -f app.log | grep "ERROR" | awk '{print $1, $5}' | sed 's/\[//g'

3.1 tail:实时追踪日志的「眼睛」

tail 是我用得最频繁的命令,没有之一。线上排查问题,第一件事就是 tail -f 挂上日志。

3.1.1 基础用法

  • tail -n 100 app.log:看最后 100 行。默认是 10 行,但日志分析时 10 行通常不够。
  • tail -f app.log:实时追踪。日志有新写入,终端立刻显示。
  • tail -f --pid=12345 app.log:当进程 12345 退出时自动停止追踪。这个我经常在脚本里用。

我的习惯:线上看日志,我一般用 tail -f -n 200 app.log | grep --line-buffered "ERROR"。加上 --line-buffered 能让 grep 实时输出,否则会有延迟。这个坑我踩过,当时排查一个接口超时问题,grep 半天没反应,后来才发现是缓冲区没刷新。

3.1.2 进阶技巧

  • 多文件追踪:tail -f app.log error.log,每个文件前会显示文件名。
  • 从指定行开始:tail -n +500 app.log,从第 500 行开始显示到末尾。适合分段查看大文件。
  • 结合 less:tail -n 10000 app.log | less,先看尾部一万行,再用 less 翻页搜索。

3.2 grep:日志过滤的「筛子」

grep 的核心就一个字——「筛」。从海量日志里把你要的行挑出来。

3.2.1 常用参数

参数 作用 实战场景
-i 忽略大小写 日志里 ERROR 和 error 混用的情况
-v 反向匹配 排除心跳日志:grep -v "heartbeat"
-E 扩展正则 匹配多个关键词:grep -E "ERROR|FATAL"
-A/-B/-C 上下文行 grep -A 3 -B 1 "OOM" 看错误前后
-c 计数 统计错误次数:grep -c "500" access.log
--line-buffered 行缓冲输出 配合 tail -f 实时过滤

我曾经踩过的坑:有一次排查线上频繁的 502 错误,我用 grep "502" access.log 搜了半天,结果一条都没找到。后来才发现,Nginx 的日志格式里状态码前面有空格,我搜的是 "502",但实际日志里是 " 502"。从那以后,我习惯用 grep " 502 " 或者 grep -E "\b502\b" 来精确匹配。

3.2.2 实战组合

# 统计每个小时 5xx 错误的数量
grep " 5[0-9][0-9] " access.log | awk '{print $4}' | cut -d: -f2 | sort | uniq -c

# 找出最近 10 分钟的错误日志
tail -n 5000 app.log | grep -E "ERROR|FATAL" | grep "$(date +'%H:%M' -d '-10 min')"

# 多关键词且关系(同时包含 A 和 B)
grep "A" app.log | grep "B"

3.3 awk:日志解析的「瑞士军刀」

awk 是这四个工具里最强大的,也是学习曲线最陡的。但别怕,日志分析只需要掌握几个核心用法就够了。

3.3.1 核心概念

awk 默认按空格或制表符分割每一行,分割后的字段用 $1、$2... 表示。$0 表示整行。

# 假设日志行:2024-01-15 10:30:45 ERROR [main] NullPointerException
# $1=2024-01-15  $2=10:30:45  $3=ERROR  $4=[main]  $5=NullPointerException

# 只打印时间戳和错误级别
awk '{print $1, $2, $3}' app.log

# 只打印 ERROR 级别的行
awk '$3 == "ERROR" {print $0}' app.log

3.3.2 常用模式

模式 说明 示例
BEGIN{} 处理前执行一次 awk 'BEGIN{print "时间,级别,消息"}'
END{} 处理后执行一次 awk 'END{print "总计:", NR, "行"}'
/pattern/ 正则匹配行 awk '/ERROR|FATAL/'
条件表达式 字段比较 awk '$3 > 500'

3.3.3 实战案例

# 统计每个接口的请求次数和平均响应时间
# 日志格式:/api/user 200 0.032
awk '{count[$1]++; sum[$1]+=$3} END{for(k in count) print k, count[k], sum[k]/count[k]}' access.log

# 找出响应时间超过 1 秒的慢请求
awk '$3 > 1.0 {print $1, $2, $3}' access.log | sort -k3 -rn | head -20

# 按小时统计错误数
awk '{hour=substr($2,1,2); if($3=="ERROR") err[hour]++} END{for(h in err) print h, err[h]}' app.log

我的建议:awk 的数组和 END 块是日志统计的利器。你想想看,一条命令就能完成分组、计数、求平均,比写 Python 脚本快多了。我经常在排查问题时,先用 awk 快速跑一个统计,看看数据分布,再决定要不要深入分析。

3.4 sed:日志清洗的「手术刀」

sed 的定位是「流编辑器」。它不擅长统计,但擅长对文本做精确的增删改查。

3.4.1 核心操作

操作 语法 实战场景
替换 sed 's/旧/新/g' 脱敏 IP、清理特殊字符
删除 sed '/pattern/d' 过滤掉 debug 日志
插入 sed '3i\新行' 在指定行前加标记
行号范围 sed -n '100,200p' 提取日志的某一段

3.4.2 实战用法

# 脱敏:把日志里的 IP 替换成 xxx.xxx.xxx.xxx
sed -E 's/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/xxx.xxx.xxx.xxx/g' app.log

# 删除所有 DEBUG 级别的日志
sed '/DEBUG/d' app.log > clean.log

# 提取第 1000 到 2000 行
sed -n '1000,2000p' app.log

# 在每行末尾添加逗号(方便转 CSV)
sed 's/$/,/' app.log

注意:sed 默认直接输出到终端,不会修改原文件。要原地修改,加 -i 参数:sed -i 's/foo/bar/g' file。但线上环境我建议先不加 -i 试跑一次,确认没问题再改。我曾经手滑把配置文件改坏了,还好有备份。

3.5 四件套组合实战

单独用每个工具都能干活,但组合起来才是真正的生产力。下面是我在项目中常用的几个组合套路。

3.5.1 实时错误监控 + 统计

# 实时监控错误,每 10 秒统计一次
tail -f app.log | grep --line-buffered "ERROR" | awk '{print $1, $2, $5}' | 
while read line; do
  echo "$line"
  # 每 10 行输出一次统计
  count=$((count+1))
  if [ $((count % 10)) -eq 0 ]; then
    echo "--- 已捕获 $count 条错误 ---"
  fi
done

3.5.2 日志清洗 + 结构化输出

# 原始日志:2024-01-15 10:30:45 [http-nio-8080-exec-10] ERROR com.example.Service - 数据库连接超时
# 目标:时间,线程,级别,类,消息

tail -n 1000 app.log | 
grep -E "ERROR|WARN" |
sed -E 's/^([0-9-]+ [0-9:]+) \[([^]]+)\] ([A-Z]+) ([^ ]+) - (.*)/\1,\2,\3,\4,\5/' |
awk -F, '{print $1, $3, $5}'

3.5.3 慢查询 Top 10

# 从 MySQL 慢查询日志中找出最慢的 10 条
grep "Query_time:" slow.log | 
awk '{print $3, $0}' | 
sort -rn | 
head -10 |
sed 's/^[0-9.]* //'

3.6 避坑指南

我总结的几个常见坑:

  1. 管道缓冲区:tail -f 管道给 grep 时,一定要加 --line-buffered,否则输出会延迟。
  2. 正则贪婪匹配:awk 和 sed 的正则默认是贪婪的,用 [^...] 来限制范围。
  3. 大文件处理:超过 1GB 的日志,不要直接用 cat 管道,用 tail 或 sed -n 分段处理。
  4. 编码问题:中文日志可能出现乱码,加 | iconv -f utf-8 -t gbk 转换。
  5. 备份习惯:sed -i 之前,先 cp file file.bak。这个习惯救过我很多次。

好了,这四个工具就讲到这里。你想想看,是不是比想象中简单?tail 看、grep 筛、awk 切、sed 改,一条管道串起来,就能完成从原始日志到分析结果的完整链路。下次遇到线上问题,别急着上平台,先试试这四件套——很多时候,一条命令就能定位到根因。