上周在外网搭建了一个服务,主要是用来对 Mori 的各种处理状态做测试.
今天在看网站统计的时候,发现请求的次数不太正常,主要表现就是出现了下面这种画风的请求.
INFO: 174.49.25.36:51271 - "GET / HTTP/1.1" 200 OK
INFO: 14.139.155.142:41680 - "GET /currentsetting.htm HTTP/1.1" 404 Not Found
INFO: 91.241.19.84:58868 - "GET /wp-content/plugins/wp-file-manager/readme.txt HTTP/1.1" 404 Not Found
INFO: 91.241.19.84:58844 - "GET /?XDEBUG_SESSION_START=phpstorm HTTP/1.1" 200 OK
INFO: 91.241.19.84:42642 - "GET /console/ HTTP/1.1" 404 Not Found
INFO: 91.241.19.84:36206 - "POST /api/jsonws/invoke HTTP/1.1" 404 Not Found
INFO: 91.241.19.84:55124 - "POST /vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php HTTP/1.1" 404 Not Found
INFO: 91.241.19.84:36174 - "GET /vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php HTTP/1.1" 404 Not Found
INFO: 91.241.19.84:42156 - "GET /index.php?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=md5&vars[1][]=HelloThinkPHP21 HTTP/1.1" 404 Not Found
INFO: 91.241.19.84:52646 - "GET /?a=fetch&content=<php>die(@md5(HelloThinkCMF))</php> HTTP/1.1" 200 OK
INFO: 91.241.19.84:53606 - "GET /solr/admin/info/system?wt=json HTTP/1.1" 404 Not Found
INFO: 203.205.34.139:47518 - "GET / HTTP/1.1" 200 OK
INFO: 182.185.14.56:62446 - "GET /currentsetting.htm HTTP/1.1" 404 Not Found
随手摘了一个 ip (219.149.212.74) 查询了一下,竟然是国内的 = =|||
又想到从大二下接触 web 开发,到现在两年多,部署了几个服务,但是却没有好好分析过网站的日志文件,正好就趁着这个机会看看吧.
为什么是 awk
通过 python,我们同样可以实现对日志分析的功能,所以,为什么要舍近求远,去使用 awk 呢?我个人的理由如下.
- 性能
- 炫酷
- 简单
Is AWK faster than Python? 关于这一点的讨论并不鲜见.从直觉来讲,与原生 linux 系统更加贴近的 awk 应该比万金油语言 python 拥有更好的性能.
不过这位使用的数据量级太低,并且他本人也建议使用大文本再进行一次测试.于是,我使用了公开数据集SYB62_309_201906_Education,将其膨胀到 220M,然后又做了一次测试.
在阿里云 1c2g 的轻量级应用服务器上,得到的结果如下.
awk 代码:
awk 'BEGIN { FPAT = "[^,]*|\"[^\"]+\"" } NR>2 { print $2 "," $3 }' SYB62_309_201906_Education.csv > awk_result.csv
python 代码:
import re
with open('../dataSet/SYB62_309_201906_Education.csv', 'r') as f:
res = []
for line in f.readlines()[2:]:
re_ = re.findall(r'(?:\s*(?:(\"[^\"]*\")|([^,]+))\s*,?)+?', line)
res.append((re_[1][0] or re_[1][1]) + ',' +
(re_[2][0] or re_[2][1])+'\n')
with open('../dataSet/reader_normal.csv', 'w') as f:
f.writelines(res)
在进行了相同的数据集分析之后,awk 能够以 更少的代码量和更高的运行效率 完成数据集的分割工作.当然,性能只是一个考虑方面,更主要的是,awk 够 geek,而我够闲.
PS1:
之所以说 在原生 linux 系统上,是因为我在 wsl 上面同样做过一次测试,得到的结果如下.
PS2:
对于简单的 CSV(comma-separated values) 文件,也就是没有 “ 和 , 等干扰项的文件,使用分割比使用正则有高得多的效率.
awk 分析网站访问日志
使用的日志是测试 api 的访问记录 access.log,具体的文件信息如下.
File: ‘access.log’
Size: 4063334 Blocks: 7944 IO Block: 4096 regular file
Device: fd01h/64769d Inode: 155109 Links: 1
Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2020-11-25 18:06:33.577884826 +0800
Modify: 2020-11-25 18:06:23.101795585 +0800
Change: 2020-11-25 18:06:23.101795585 +0800
Birth: -
日志格式
format : ip - - [time] “url” statuscode “-“ “UA” “-“
114.220.205.222 - - [10/Aug/2020:09:05:18 +0000] "GET /api/items/5?q=somequery HTTP/1.1" 200 29 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36" "-"
89.248.174.166 - - [10/Aug/2020:09:07:56 +0000] "GET / HTTP/1.1" 200 15 "-" "Mozilla/5.0 zgrab/0.x" "-"
114.220.205.222 - - [10/Aug/2020:09:10:52 +0000] "GET /api/docs HTTP/1.1" 404 555 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36" "-"
114.220.205.222 - - [10/Aug/2020:09:13:10 +0000] "GET /api/docs HTTP/1.1" 404 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36" "-"
114.220.205.222 - - [10/Aug/2020:09:13:49 +0000] "GET /api/docs HTTP/1.1" 404 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36" "-"
80.82.78.85 - - [10/Aug/2020:09:39:13 +0000] "GET / HTTP/1.1" 200 15 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:76.0) Gecko/20100101 Firefox/76.0" "-"
39.107.127.149 - - [10/Aug/2020:10:14:54 +0000] "GET / HTTP/1.1" 200 15 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:36.0) Gecko/20100101 Firefox/36.0" "-"
180.163.220.68 - - [10/Aug/2020:10:17:12 +0000] "GET / HTTP/1.1" 200 15 "-" "Mozilla/5.0 (Linux; U; Android 8.1.0; zh-CN; EML-AL00 Build/HUAWEIEML-AL00) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 baidu.sogo.uc.UCBrowser/11.9.4.974 UWS/2.13.1.48 Mobile Safari/537.36 AliApp(DingTalk/4.5.11) com.alibaba.android.rimet/10487439 Channel/227200 language/zh-CN" "-"
180.163.220.68 - - [10/Aug/2020:10:17:14 +0000] "GET / HTTP/1.1" 200 15 "http://baidu.com/" "Mozilla/5.0 (Linux; U; Android 8.1.0; zh-CN; EML-AL00 Build/HUAWEIEML-AL00) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 baidu.sogo.uc.UCBrowser/11.9.4.974 UWS/2.13.1.48 Mobile Safari/537.36 AliApp(DingTalk/4.5.11) com.alibaba.android.rimet/10487439 Channel/227200 language/zh-CN" "-"
42.236.10.78 - - [10/Aug/2020:10:17:27 +0000] "GET / HTTP/1.1" 200 15 "http://baidu.com/" "Mozilla/5.0 (Linux; U; Android 8.1.0; zh-CN; EML-AL00 Build/HUAWEIEML-AL00) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 baidu.sogo.uc.UCBrowser/11.9.4.974 UWS/2.13.1.48 Mobile Safari/537.36 AliApp(DingTalk/4.5.11) com.alibaba.android.rimet/10487439 Channel/227200 language/zh-CN" "-"
(normal_test)
统计访问的人数
在不考虑使用代理的情况下,可以假定一个 ip 代表一个独立访客.
统计某个 ip 的总访问次数
首先,在 awk 中是存在 数组(Array) 这个概念的,不过这里的数组更加类似于 c++的 map、python 的 dict.通过索引取值,但是这个索引并不局限于数字,也可以是字符串.也就是说,与其说是 Array,不如说是 Key-Value .
对 ip 的统计主要就是使用了这个功能.我对日志进行遍历,然后做成 哈希表 ,最后使用 linux 自带的 sort 进行统计.
awk 自身也有排序函数 asort 和 asorti,不过在实际使用的过程中,asort 会破坏原有的 indices,这在 StackOverflow 上吗也有人遇到了同样的问题,Sort associative array with AWK.通过原生的 awk 方法解决太麻烦了,所以这里偷个懒直接使用 linux 的 sort 函数.
ps. sort 必要的参数说明
-r, –reverse reverse the result of comparisons
-n, –numeric-sort compare according to string numerical value
-k, –key=KEYDEF sort via a key; KEYDEF gives location and type
time gawk '{seen[$1]++} END {for(foo in seen) {print foo,seen[foo]}}' access.log | sort -nrk 2|head
# output
# 132.145.91.50 4305
# 58.210.143.102 316
# 146.56.217.168 162
# 80.82.70.187 155
# 42.51.60.61 155
# 183.136.225.56 105
# 121.235.166.248 100
# 183.136.225.35 78
# 163.177.13.2 78
# 101.251.242.238 78
# gawk '{seen[$1]++} END {for(foo in seen) {print foo,seen[foo]}}' access.log 0.02s user 0.00s system 71% cpu 0.032 total
# sort -nrk 2 0.07s user 0.00s system 74% cpu 0.090 total
# head 0.00s user 0.00s system 1% cpu 0.079 total
如果不使用 awk,也可以通过 linux 的组合命令实现统计的效果.
time awk '{print $1}' access.log | sort -n | uniq -c |sort -nr -k 1|head
# output
# 4305 132.145.91.50
# ... 结果同上
# awk '{print $1}' access.log 0.01s user 0.00s system 41% cpu 0.031 total
# sort -n 0.10s user 0.00s system 68% cpu 0.150 total
# uniq -c 0.03s user 0.00s system 22% cpu 0.155 total
# sort -nr -k 1 0.09s user 0.00s system 39% cpu 0.240 total
# head 0.00s user 0.00s system 0% cpu 0.232 total
可以看到,awk 在遍历的同时完成了统计的功能,而命令组则需要反复进行遍历,这就使得命令组的时间消耗大大高于 awk.
URL 访问量
清洗
根据统计访问量的目标不同,有着不同的清洗思路.
比如想要知道哪些文章点击量更高,又或者是哪个静态文件的访问量高 or 存在异常点击,前者很明显要排除所有 jpg\gif\txt 之类的静态文件,后者则差不多要是前者的补集.
这里以统计 url 接口为例.
awk '$7 !~ /\/static/ && $7 !~ /\.jpg|\.png|\.jpeg|\.gif|\.css|\.js|\.woff/ {print}' access.log > clean_access.log
清洗前后的结果对比.
wc access.log && wc clean_access.log
# output
# 23587 436203 4063334 access.log
# 22336 407926 3771874 clean_access.log
统计
awk '{seen[$7]++} END {for(foo in seen){print foo,seen[foo]}}' clean_access.log |sort -nr -k 2 |head
清洗与统计合并:
awk '$7 !~ /\/static/ && $7 !~ /\.jpg|\.png|\.jpeg|\.gif|\.css|\.js|\.woff/ {seen[$7]++} END {for(foo in seen){print foo,seen[foo]}}' access.log |sort -nr -k 2 |head
每日访问量
这里的每日访问量是指包括失败请求、静态资源请求在内的总的访问量. 样例数据
awk 'BEGIN {FPAT = "([^ ]+)|\\[([^\\]]+)\\]|(\"[^\"]+\")"} {split($4,a,":");gsub("\\[","",a[1]);seen[a[1]]++} END {for(foo in seen){print foo,seen[foo]}}' access.log|head
# output
# 17/Sep/2020 474
# 14/Sep/2020 81
# 11/Sep/2020 97
# 08/Oct/2020 454
# 31/Oct/2020 996
# 11/Aug/2020 113
# 29/Sep/2020 434
# 05/Oct/2020 436
# 08/Nov/2020 188
# 17/Oct/2020 127
分析
FPAT = “([^ ]+)|\[([^\]]+)\]|("[^"]+")”
指定了对文件的分割正则,分割之后的时间格式: [10/Aug/2020:09:05:18 +0000]
我们需要的是每日的访问量,所以要将该时间继续分割提取.
{split($4,a,”:”);gsub(“\[“,””,a[1]);}
做的就是这个工作,提取除精确到 day 的日期. 10/Aug/2020
再之后使用 map 进行统计计数.
补充(持续更新 ing)
将 awk 的结果作为 cmd 参数
最简单的使用就像这样.
echo $(awk)
这种用法,可以用来将 docker 中的无效 image 快速移除.
docker rmi $(docker images |awk '$1=="<none>" {print $3}')
批量在k8s服务器集群执行命令
以列出所有服务器时间为例:
kubectl get nodes | awk '(NR>2) {print $1}' | xargs -I {1} ssh {1} "date"
需要确保 host 文件中配置过服务器host, 否则需要修改 print $1
为ip值.
总结
将以上的脚本整合,然后设置一个定时执行,就可以每天整合一次网站访问记录.配合 echarts,可以将网站访问以漂亮的图表的形式输出.
至此,DIY 网站运行统计收工.