
从事服务器运维这些年我几乎每天都要跟日志打交道。线上服务报错了、接口变慢了、流量突然异常升高第一反应就是登上去翻日志。说实话图形化的日志平台确实好用但真到了紧急关头最可靠、最直接的还是那一串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客户端IP192.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 20awk {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?page1和/api/list?page2直接统计时它们是两条记录聚合度不够。我常用sed先把查询参数去掉sed s/\?.*$// access.log | awk {print $7} | sort | uniq -c | sort -rn | head -20sed s/\?.*$//把行内从第一个?开始到行尾的内容删掉。这样/api/list?page1和/api/list?page2都会归一到/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.gzgrep搜不到带[或/的时间正则特殊字符未转义使用grep -F 字符串做纯文本匹配uniq -c统计结果乱uniq之前没有sort相同值不连续先sort再uniq -cawk {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.sh4.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这样后续任何同事都能直接对着文件里的数据再加工不用从截图里扒数字。我自己的体会是日志分析命令学到一定程度拼的就不是“会不会敲某个命令”而是“能不能快速组合现有命令形成一套高效的排查思路”。希望这篇内容能帮你建立起自己的日志分析套路。