从事服务器运维这些年,我几乎每天都要跟日志打交道。线上服务报错了、接口变慢了、流量突然异常升高,第一反应就是登上去翻日志。说实话,图形化的日志平台确实好用,但真到了紧急关头,最可靠、最直接的还是那一串Shell命令。你不需要安装任何额外的软件,只要能SSH登录服务器,一行命令就能把问题定位个八九不离十。
这篇内容就是把我日常用到的高频Shell日志分析命令整理出来。我会从最基础的准备说起,再到统计排行、异常定位、实时监控,最后分享几个实战案例和踩过的坑。内容偏实用向,你不需要一次全部记住,建议直接收藏,等真正用到的时候翻开照着敲就行。
1. 日志分析前的三个基础准备
1.1 先搞懂日志字段长什么样
很多朋友一上来就复制命令,结果跑出来的结果完全看不懂,原因就是没先看日志格式。以最常见的Nginx访问日志为例,默认配置一般是这样的:
192.168.1.10 - - [30/Sep/2024:14:23:45 +0800] "GET /index.html HTTP/1.1" 200 5312 "https://example.com" "Mozilla/5.0..."用默认的awk按空格分割后,各字段的位置大概是:
| 字段位置 | 含义 | 示例值 |
|---|---|---|
| $1 | 客户端IP | 192.168.1.10 |
| $4 | 请求时间(含时区) | [30/Sep/2024:14:23:45 |
| $6 | 请求行(含方法、URL、协议) | "GET /index.html HTTP/1.1" |
| $7 | 请求URL | /index.html |
| $9 | 状态码 | 200 |
| $10 | 响应字节数 | 5312 |
| $NF | 请求耗时(秒) | 0.032 |
这个$NF值得单独说。它代表“最后一个字段”,在自定义log_format的Nginx配置里,如果你把$request_time放在了最后,那$NF就是耗时。但如果你的日志模板最后加了别的字段,$NF就不是耗时了。
所以拿到日志的第一步永远是head -n 3看一眼内容,确认字段位置再动手。这是最重要的一步。
1.2 把分析工具备齐
日志分析真正核心的命令其实就这几个:grep、awk、sed、sort、uniq、wc、head、tail、cut。各个工具的定位简单介绍下:
grep:文本搜索,按关键字或正则过滤行,适合“查包含某错误码的请求”。awk:按列处理,适合“提取第几列做统计”,例如统计IP、URL、耗时。sed:按行处理,常用来按行号或时间范围截取日志片段。sort:排序,统计排序的基础。uniq:去重计数,配合sort使用,统计出现次数。wc:计数,wc -l统计行数即请求数。head/tail:取头部/尾部,tail -F用于实时跟踪。cut:按分隔符切分,常用于时间字段的进一步拆分。
这些命令在绝大多数Linux发行版里都是预装的。也就是说,任何一台服务器你都有现成的能力做日志分析,不需要额外装任何包。
1.3 别忽略日志轮转和环境细节
实际操作中还需要留意日志轮转。绝大多数服务器都配了logrotate,访问日志按天或按大小切割。我见过不少人在半夜排查问题,还在tail -f app.log,结果日志早就被轮转了,看了半天看的是旧文件。
所以分析历史日志前,先看看日志目录下有哪些文件:
ls -lh /var/log/nginx/常见的情况是这样的:
access.log # 当天的实时日志 access.log.1 # 昨天的日志(未压缩) access.log.2.gz # 前天的日志(已压缩,需要zcat)处理.gz压缩日志时,不能用普通的grep或awk,得用带z前缀的版本。后续我会在案例里专门说明。
2. 最常用的十大日志分析命令
2.1 总请求量、独立访客与QPS估算
统计当天总共处理了多少请求,最简单的方法:
wc -l /var/log/nginx/access.log访问日志每一行对应一次请求,行数就是总请求数。但要注意,如果日志里包含错误级别的记录(例如某些应用日志)或包含多行堆栈信息,行数不等于请求数。这个命令更适合访问日志场景。
统计独立IP数:
awk '{print $1}' /var/log/nginx/access.log | sort -u | wc -l这里awk '{print $1}'把每行的IP提取出来,sort -u去重,wc -l数行数。什么是独立访客(UV)?你只需要记住一点:多个请求来自同一IP只算一次,所以必须先排序再取唯一值。
如果还想粗估一下QPS(每秒查询数),可以用awk按分钟统计并求最大值。假设日志总时长是15分钟,共9000条请求,平均QPS就是9000 / 900 = 10。真正的峰值QPS计算更复杂,建议用下面的方式按分钟切分:
awk '{print substr($4, 14, 5)}' /var/log/nginx/access.log | sort | uniq -c输出大概长这样:
520 14:23 618 14:24 473 14:25第二列是分钟,第一列是该分钟内的请求数。取最大值就是峰值的分钟级近似。注意,substr($4, 14, 5)提取的是时间字段中的14:23这种格式,前提是时间字段是标准的[30/Sep/2024:14:23:45 +0800]。如果你的时间格式不一样,需要调整起始位置。
2.2 IP排名:谁在频繁访问你的服务
找出访问量最高的Top 10 IP,这是排查恶意爬虫、CC攻击的常用操作:
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10拆解一下这个经典管道组合:
sort:给IP排序,相同的IP排到一起。uniq -c:去重并统计每个IP出现的次数。sort -rn:按次数从大到小排序(-r降序,-n按数值排序)。head -10:取前10条。
输出类似:
10240 192.168.1.100 5203 203.0.113.55看到某个IP请求量异常高,下一步通常就是查看它具体请求了什么:
grep '^203.0.113.55' /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -20grep '^203.0.113.55'匹配以该IP开头的行,然后统计它访问的URL排名。如果全是同一个URL,大概率是爬虫在刷;如果URL分布很散,也可能是某个代理出口IP的集中访问。
2.3 状态码分布:一眼看出错误比例
看整体请求的健康状况,统计所有状态码的分布:
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn输出:
85000 200 3200 404 550 500 120 502一眼就能看出5xx的数量是否异常。如果我只想看4xx和5xx的请求总量,可以用正则:
grep -E 'HTTP/1\.1" 4[0-9]{2}|HTTP/1\.1" 5[0-9]{2}' /var/log/nginx/access.log | wc -l这里-E启用扩展正则,4[0-9]{2}匹配400-499的状态码,5[0-9]{2}匹配500-599。注意这里我把请求行和状态码一起匹配了,避免日志中URL本身包含类似数字造成误判。
更精准的方式是用awk判断字段:
awk '$9 ~ /^[45][0-9][0-9]$/' /var/log/nginx/access.log | wc -l$9 ~ /^[45][0-9][0-9]$/表示第9列匹配以4或5开头且后面跟两位数字的完整字段。这种方式不会误匹配URL。
2.4 URL访问排行:业务热度和流量大头
统计访问量最高的URL Top 20:
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20结果里前几个通常是/、/index.html、/api/xxx之类。我一般会顺手加一个grep把静态资源过滤掉,只看动态接口:
awk '{print $7}' /var/log/nginx/access.log | grep -v -E '\.(js|css|png|jpg|gif|ico|woff2?)$' | sort | uniq -c | sort -rn | head -20grep -v是反向匹配,排除掉以常见静态资源后缀结尾的URL。这样看接口访问量分布会准确很多。
有时接口的URL带了查询参数(比如/api/list?page=1和/api/list?page=2),直接统计时它们是两条记录,聚合度不够。我常用sed先把查询参数去掉:
sed 's/\?.*$//' access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -20sed 's/\?.*$//'把行内从第一个?开始到行尾的内容删掉。这样/api/list?page=1和/api/list?page=2都会归一到/api/list,统计出来的接口热度才是真实分布。
2.5 响应时间分析:平均耗时与最慢请求
日志里藏着耗时数据(前提是access log配置了request_time字段)。先看最慢的10个请求,这是性能排查的起点:
awk '{print $NF, $0}' /var/log/nginx/access.log | sort -rn | head -10awk '{print $NF, $0}'把最后一个字段(耗时)追加到行首,然后sort -rn按耗时降序排列。取前10就是最慢的请求。
看平均耗时:
awk '{sum += $NF; count++} END {print sum/count}' /var/log/nginx/access.log这个写法很经典:sum累加所有耗时,count记录行数,END块在所有行处理完后打印平均值。如果你关心的是P95、P99这类百分位耗时,推荐用sort加awk配合算:
sort -k NF -n access.log | awk 'BEGIN {p95 = NR * 0.95} NR == int(p95) {print "P95:", $NF}'这个方法的核心思路是先把所有耗时从小到大排序,然后取第95%位置的耗时值。直接理解就行。我实测过,对于几万行的日志这个速度完全够用,但如果日志有几千万行,建议还是交给专业监控系统来算。
3. 从统计到定位:真正解决问题的组合用法
3.1 按时间窗口切出“案发”现场
统计命令能告诉你“有问题”,但定位问题需要“在什么时间段出了什么问题”。用sed按时间范围截取日志片段是我最常用的招数。
比如我要看2024年9月30日14:21:00到14:22:30之间的请求:
sed -n '/\[30\/Sep\/2024:14:21:00/,/\[30\/Sep\/2024:14:22:30/p' /var/log/nginx/access.log > time_window.log注意点有两个:
-n表示不默认打印每一行,只有匹配范围内的才输出。- 时间字段中的
[和/在正则里有特殊含义,必须用反斜杠\转义。
截取完之后,把time_window.log当新的分析文件,继续套用各种统计命令。这样就能把问题精确固定在某个时间段内。比如截取15分钟内流量突增的日志,然后统计这段时间内的IP TOP10,往往很快就能抓到元凶。
如果日志是跨天的,时间范围的正则要拆成两段:
sed -n '/\[30\/Sep\/2024:23:50/,/\[30\/Sep\/2024:23:59/p' /var/log/nginx/access.log > part1.log sed -n '/\[01\/Oct\/2024:00:00/,/\[01\/Oct\/2024:00:10/p' /var/log/nginx/access.log > part2.log cat part1.log part2.log > window.log3.2 过滤噪音:排除健康检查和静态资源
日志里最烦人的就是各种监控探活请求,它们会污染统计结果。典型的健康检查长这样:
"GET /healthz HTTP/1.1" 200 "GET /ping HTTP/1.1" 200统计的时候把噪音先剔除掉:
grep -v -E '(/healthz|/ping)' /var/log/nginx/access.log | awk '{print $9}' | sort | uniq -cgrep -v反向匹配,排除包含/healthz或/ping的行,然后再统计状态码分布。
如果内网监控的IP段也需要排除,可以这样:
grep -v '^10\.0\.0\.' /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10^10\.0\.0\.匹配以10.0.0.开头的内网IP。注意IP里的点号在正则里也是特殊字符,必须转义成\.。如果你要排除多个IP段,可以用grep -v -E '^(10\.0\.0\.|192\.168\.)'。
3.3 实时滚动监控的几种写法
半夜被电话叫起来,第一件事通常是挂一个实时监控在终端上。最朴素的写法:
tail -F /var/log/nginx/access.log-F和-f的区别是:-F按文件名跟踪,即使日志被轮转重命名了也能继续跟随新文件。-f按文件描述符跟踪,日志轮转后可能跟丢了。所以我推荐-F。
如果只想看5xx错误实时刷出:
tail -F /var/log/nginx/access.log | grep 'HTTP/1.1" 5'我在实战中最常用的是滚动看5xx和耗时超过3秒的慢请求:
tail -F /var/log/nginx/access.log | awk '($9 ~ /^5[0-9][0-9]$/) || ($NF > 3)'这个awk命令有两个条件,满足其一就打印这行:$9是5xx状态码,或者耗时超过3秒。跑起来之后,终端会持续刷新输出异常请求,信息密度非常高。
如果想统计滚动窗口内的TOP IP,可以配合timeout使用:
timeout 60 tail -F /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -20timeout 60让这条命令60秒后自动退出。60秒内如果有某个IP刷了大量请求,它就会排在最前面。这是临时排查突增流量的快捷方式,不需要专门装攻防工具。
3.4 把常用分析套路封装成脚本
命令行敲多了之后,我发现很多套路是固定的。于是把它们固化成一个脚本。下面这个脚本是我服务器上长期保留的,核心功能是统计指定日志文件在指定时间窗口内的关键指标。
脚本思路很简单:先用sed截出时间窗口,然后依次输出请求数、独立IP、状态码分布、URL Top 10、慢请求Top 10。
#!/bin/bash # usage: ./log_analyze.sh /var/log/nginx/access.log "30/Sep/2024:14:21" "30/Sep/2024:14:30" LOG_FILE="$1" START_TIME="$2" END_TIME="$3" TMP_FILE=$(mktemp) sed -n "/\[${START_TIME}/,/[\]${END_TIME}/p" "$LOG_FILE" > "$TMP_FILE" echo "==== 请求总量 ====" wc -l < "$TMP_FILE" echo "==== 独立IP数 ====" awk '{print $1}' "$TMP_FILE" | sort -u | wc -l echo "==== 状态码分布 ====" awk '{print $9}' "$TMP_FILE" | sort | uniq -c | sort -rn echo "==== URL访问 TOP10 ====" sed 's/\?.*$//' "$TMP_FILE" | awk '{print $7}' | sort | uniq -c | sort -rn | head -10 echo "==== 最慢请求 TOP10 ====" awk '{print $NF, $0}' "$TMP_FILE" | sort -rn | head -10 rm -f "$TMP_FILE"调用方式:
./log_analyze.sh /var/log/nginx/access.log "30/Sep/2024:14:21" "30/Sep/2024:14:30"这个脚本把日常重复劳动压缩成一条命令,出问题的时候执行一下,几秒钟就能得到一轮初步结论。
4. 实战踩坑记录与问题排查速查
4.1 典型排查案例:一次接口变慢的定位过程
这是我之前在某模拟项目X的真实经历。某天监控显示一个核心接口的P95耗时从0.3秒涨到了4秒,页面响应明显卡顿。我当时是这样排查的:
第一步,确认是整体变慢还是局部变慢。执行状态码分布统计:
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn结果发现5xx没有明显增多,说明不是大面积报错,而是某些请求在“慢性死亡”。
第二步,查最慢的请求是哪些:
awk '{print $NF, $7}' /var/log/nginx/access.log | sort -rn | head -20输出显示Top 20的慢请求全部集中在两个URL上,而且耗时普遍在10秒以上。于是我对这两个URL单独统计:
grep '/api/slow_action' /var/log/nginx/access.log | awk '{print $NF}' | sort -n | tail -5这能看到该接口的耗时分布尾部,确实是持续性的缓慢。此时还没法判断是应用代码问题还是下游依赖问题。
第三步,按时间切片定位变化的拐点:
sed -n '/\[30\/Sep\/2024:13:00/,/\[30\/Sep\/2024:13:10/p' /var/log/nginx/access.log | awk '{print $NF}' | sort -n | tail -5 sed -n '/\[30\/Sep\/2024:13:10/,/\[30\/Sep\/2024:13:20/p' /var/log/nginx/access.log | awk '{print $NF}' | sort -n | tail -5对比两个窗口的耗时尾部,发现13:10之前耗时还在毫秒级,13:10之后就飙到秒级了。范围一下子从“最近一段时间”缩小到“某一天的某个10分钟窗口内”。
第四步,查看那个时间窗口内该接口的所有请求,确认是从某个特定来源开始的:
sed -n '/\[30\/Sep\/2024:13:10/,/\[30\/Sep\/2024:13:20/p' /var/log/nginx/access.log | grep '/api/slow_action' | awk '{print $1}' | sort | uniq -c | sort -rn发现大量请求从一个网段涌进来,结合业务时间点,我怀疑是某个数据同步任务被调度到高峰期执行,把数据库连接占满了。随后到应用日志里一查,果真是同步任务启动导致连接池竞争。最终方案是把同步任务改到低峰期执行,问题彻底解决。
整个排查过程用到的全是前面讲的这些Shell命令,没有借助任何图形化平台。
4.2 常见问题速查表
我在评论区也经常看到有人问日志分析命令跑不出结果的坑,整理一个速查表方便对照排查。
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
grep搜不到关键字 | 日志被压缩为.gz | 改用zgrep '关键字' access.log.2.gz |
grep搜不到带[或/的时间 | 正则特殊字符未转义 | 使用grep -F '字符串'做纯文本匹配 |
uniq -c统计结果乱 | uniq之前没有sort,相同值不连续 | 先sort再uniq -c |
awk '{print $NF}'输出不对 | 日志字段被截断或$request_time不在末位 | 用head -1确认字段位置 |
tail -f跟丢日志 | 日志轮转后文件描述符变化 | 改用tail -F |
| 输出乱码或中文乱码 | 日志编码与终端不一致 | 用file access.log确认编码,必要时iconv -f gbk -t utf-8 |
| 日志行数太多,命令卡顿 | 未加时间范围直接全量统计 | 先用head/sed截取时间范围,再统计 |
sed -n '/a/,/b/p'截取范围不符 | 起止正则写法不对,[未转义 | 用grep -F先验证某一个时间点能匹配到 |
| 脚本从Windows上传后报错 | 行尾是\r,命令解释异常 | sed -i 's/\r$//' script.sh |
4.3 我一直保留的几个小习惯
第一,所有统计命令的日志文件路径我都写成变量或参数,不硬编码。因为logrotate之后文件名会变,硬编码意味着每次都要改命令。
第二,危险操作前先备份。有一次我想统计一个大日志文件里的报错行,直接写rm清掉了一个临时文件,结果发现那个临时文件是还没处理的原始日志。现在的习惯是:任何时候生成临时中间文件,文件名都带PID或时间戳:
TMP_FILE=/tmp/access_$$_$(date +%s).log第三,命令跑完一定要看输出量级是否合理。如果总请求量只有几百行,而业务量每天应该是几十万,那就是日志路径不对或者logrotate已经把文件切走了。这种低级错误最容易在半夜犯。
第四,善用history。我把自己敲熟的高频命令都做成了shell别名或者脚本,放在~/bin下。真正遇到故障的时候,人处于紧张状态,手边有一套顺手工具远比临时回忆命令要可靠。
最后再分享一个小技巧:如果你需要把统计结果发到群里或保存下来,最好是直接重定向成文件而不是截图:
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn > /tmp/status_code_report.txt这样后续任何同事都能直接对着文件里的数据再加工,不用从截图里扒数字。我自己的体会是:日志分析命令学到一定程度,拼的就不是“会不会敲某个命令”,而是“能不能快速组合现有命令形成一套高效的排查思路”。希望这篇内容能帮你建立起自己的日志分析套路。