著名的自由软件运动的先驱理查德·斯托曼说过 编程不是科学,编程是手艺。
可见要想真正搞好编程,除了学习理论知识,还需要在实际的工作场景中反复锤炼。
所以今天我们结合实际工作场景,用 Linux 指令分析一份 Web 日志。这里面包含很多小技巧,掌握之后,对你将来分析线上日志、了解用户行为、排查线上问题会有非常大的帮助。
本文用到的是一个大概有 5 万多条记录的 nginx 日志文件(access.log),网上能找到不少公开的样本日志。下面请你和我一起,通过分析这个文件来锤炼手艺。
第一步:先问自己——能不能这样做?
很多人拿到需求就直接上手敲命令,这是线上事故的常见起点。当我们想要分析一个线上文件的时候,首先要思考:能不能这样做?
先用 htop 看一下当前机器的负载(如果没装,用 yum 或 apt 装一下):
htop
我这台机器上 8 个 CPU 都是 0 负载,2G 内存用了一半多,还有富余——说明有余量可以干活。
然后把目标文件下载到本地(同样,没有 wget 就用包管理器装):
wget 某网址 # 换成你自己的日志地址
接着用 ls 看一下文件多大:
ls -l --block-size=M access.log
这里用 --block-size=M 让 ls 以 M 为单位显示文件大小,比数一长串数字的位数舒服多了。
结果发现这只是个 7M 的文件,对线上的影响可以忽略不计。
如果文件很大怎么办? 建议先用 scp 把文件拷到闲置服务器上再分析,别在正在扛流量的机器上折腾。
确定了当前机器的 CPU 和内存允许我们做这件事,才进入第二步。这一步看着啰嗦,但它是「工程师」和「敲命令的人」之间的分水岭。
第二步:less 一下,先看清日志长什么样
在分析日志前给你提个醒:记得先 less 一下,看看日志里到底是什么内容。
less access.log
之前我们说过,尽量使用 less 这种不需要读取全部文件的指令。在线上执行 cat 是一件非常危险的事情,可能直接导致线上服务器资源不足。
less 完之后可以看到,nginx 的 access_log 每一行都是一次用户的访问,从左到右依次是:
IP 地址
时间
HTTP 请求的方法、路径、协议版本,以及返回的状态码
User Agent
大致长这样(补充一条示例,方便对照列号):
180.76.15.31 - - [17/May/2015:10:05:03 +0000] "GET /blog/ HTTP/1.1" 200 3557 "-" "Mozilla/5.0 (compatible; Baiduspider/2.0)"
按空格切分,$1 是 IP,$4 是时间(前面带个 [)。记住这两个列号,后面全靠它们。
第三步:PV 分析
PV(Page View):用户每访问一个页面,就是一次 Page View。
对于 nginx 的 access_log 来说,分析 PV 非常简单——既然每一行就是一次访问,那数行数就行了,直接 wc -l:
wc -l access.log
结果是 51462 条 PV。
一条命令搞定。日志格式的设计,本身就已经把「统计」这件事变简单了。
第四步:PV 按天分组
通常一个日志里可能有好几天的 PV,为了得到更直观的数据,有时候需要按天分组。
为了简化问题,我们先看看日志里都有哪些天:
awk '{print $4}' access.log | less插播:什么是 awk
awk 是一个处理文本的领域专有语言。
这里牵扯到一个概念——领域专有语言(Domain Specific Language,DSL),就是为了处理某个领域而专门设计的语言。比如:
awk 是用来分析处理文本的 DSL;
HTML 是专门用来描述网页的 DSL;
SQL 是专门用来查询数据的 DSL……
你也完全可以根据自己的业务,设计一种针对业务的 DSL。
上面命令里的 $4 就代表文本的第 4 列,也就是时间所在的那一列。
截取日期部分
时间那一列长这样:[17/May/2015:10:05:03,前面有个 [,后面还带时分秒,我们只要日期。用 awk 的字符串截取能力:
awk '{print substr($4, 2, 11)}' access.log | lesssubstr 函数里,数字 2 代表从第 2 个字符开始(跳过那个 [),数字 11 代表截取 11 个字符(正好是 17/May/2015)。
分组统计
接下来就能统计每天的日志条数了:
awk '{print substr($4, 2, 11)}' access.log | sort | uniq -c这里用 sort 排序,然后用 uniq -c 统计。
为什么必须先 sort?(补充)uniq 只能合并相邻的重复行,不排序的话,同一天的记录散落在各处,统计结果就会碎成一堆。sort | uniq 是一对,几乎不分开用。
结果可以看到:日志覆盖了 2015 年 5 月 17 号一直到 6 月 4 号,每天的 PV 量大概在 2000~3000 之间。
一条流水线下来,一个网站近三周的流量曲线就出来了。
第五步:UV 分析
UV(Uniq Visitor),也就是统计访问人数。
通常确定用户的身份是一件复杂的事情,但我们可以用 IP 访问来近似统计 UV:
awk '{print $1}' access.log | sort | uniq | wc -l拆开看这条流水线:
| 环节 | 作用 |
|---|
awk '{print $1}' | 打印第一列,也就是 IP |
sort | 排序,让相同 IP 相邻 |
uniq | 去重 |
wc -l | 数一共剩几条 |
结果:日志文件中一共有 2660 个 IP,也就是 2660 个 UV。
对比一下 PV 51462,人均访问了将近 20 个页面——这个比值本身就是个有意思的指标(补充)。
第六步:UV 按天分组(今天的重头戏)
接下来我们尝试按天分组分析每天的 UV。这个情况比较复杂,需要较多的指令,所以我们先创建一个叫 sum.sh 的 bash 脚本文件,写入如下内容:
#!/usr/bin/bashawk '{print substr($4, 2, 11) " " $1}' access.log |\sort | uniq |\awk '{uv[$1]++;next}END{for (ip in uv) print ip, uv[ip]}'逐行拆解
文件首部使用 #!,表示将使用后面的 /usr/bin/bash 来执行这个文件;
第一次 awk:把第 4 列的日期和第 1 列的 IP 地址拼接在一起(中间用空格隔开);
sort:把整个文件做一次字典序排序,相当于先按日期排序,再按 IP 排序;
uniq:去重,日期 + IP 相同的行就只保留一个——这一步是关键,它保证了「同一个人同一天来了 100 次」只算 1 个 UV;
最后的 awk:再根据第 1 列的时间和第 2 列的 IP 进行统计。
行尾的 \ 是续行符,用来把一条长命令拆成多行写,可读性更好(补充)。
顺便搞懂 awk 的执行原理
为了理解最后那一行,我们简单了解一下 awk 的原理。
awk 本身是逐行进行处理的。 所以:
next 关键字是提醒 awk 跳转到下一行输入;
对每一行输入,awk 会根据第 1 列的字符串(也就是日期)进行累加;
END 关键字代表一个触发器——END 后面用 {} 括起来的语句,会在所有输入都处理完之后才执行;
当所有输入都执行完、结果被累加到 uv 中后,通过 for...in 遍历 uv 里所有的 key,打印 IP(这里是日期)和对应的数量。
一句话概括这个思路:先把「日期+IP」去重,再按日期数行数。 想清楚这一层,脚本就不难了。
执行
编写完脚本、保存退出编辑器,先给它加上执行权限:
chmod +x ./sum.sh
然后执行:
./sum.sh
这样,每天的 UV 就按天统计好了。
一个小优化(补充):上面输出的顺序是 awk 遍历哈希表的顺序,是乱的。想让结果按日期整齐排列,可以在后面再接一个 sort:
./sum.sh | sort
补充:日志里还有两列没被榨干
换个列号,就是一个新指标。掌握了 awk | sort | uniq -c | sort -rn 这套组合,日志里几乎所有维度都能拿下。
第二步里我们看到,日志一共有四类信息,但前面只用到了 IP 和时间。剩下的状态码 / 路径和 User Agent 同样有价值。
1. 热门访问路径 Top 10
awk '{print $7}' access.log | sort | uniq -c | sort -rn | head -10sort -rn 里,-n 是按数值排序(不然 100 会排在 99 前面变成 "100" < "99"),-r 是倒序。这套组合是日志分析里出现频率最高的尾巴。
2. HTTP 状态码分布
awk '{print $9}' access.log | sort | uniq -c | sort -rn一眼就能看出 200 有多少、404 有多少、5xx 有没有异常冒头。
3. 揪出所有 404 的路径
awk '$9 == 404 {print $7}' access.log | sort | uniq -c | sort -rn | head -20注意这里 awk 前面加了条件 $9 == 404——awk 的完整形式是 条件 { 动作 },前面我们一直省略了条件,所以它对每行都执行。这是排查死链和错误引用的利器。
4. 访问量最高的 IP Top 10(找出可疑刷量)
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10如果某个 IP 的访问量远超其他人,基本就是爬虫或者攻击了。
5. 看看是哪些爬虫在爬
grep -i "spider\|bot" access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head配合 User Agent 这一列,就能大致区分「真人流量」和「机器流量」——这直接决定了前面统计出来的 PV/UV 有多少是真实的。
总结
今天我们结合一个简单的实战场景——Web 日志分析与统计,把之前学过的指令都练了一遍,提高熟练程度。此外还学习了新知识:功能强大的 awk 文本处理语言。
在实战中,我们对一个 nginx 的 access_log 做了简单的数据分析,直观地获得了这个网站的访问情况:
| 指标 | 结果 | 用到的核心指令 |
|---|
| 总 PV | 51462 | wc -l |
| 总 UV | 2660 | awk + sort + uniq + wc -l |
| 日期范围 | 2015/05/17 – 2015/06/04 | awk substr + sort + uniq -c |
| 每日 PV | 约 2000~3000 | 同上 |
| 每日 UV | 见 sum.sh 输出 | 双层 awk + 中间 uniq 去重 |
回过头看,整套操作其实只有一个套路:
用 awk 取出你关心的那一列 → sort 让相同的挨在一起 → uniq 完成去重或计数 → 需要排名再 sort -rn。
我们在日常工作中会遇到各种各样的日志:除了 nginx 日志,还有应用日志、前端日志、监控日志等等。你都可以用今天学到的方法去做数据分析,然后从中得出结论。
最后再回到开头那句话:编程是手艺。这些命令你光看是记不住的,找一份日志,亲手敲一遍,才算真的学会了。