借助nginx统计前端页面PV信息

为了解决前端静态网站统计 PV 数据的问题,需要有个接口返回页面的访问数量。

我们不打算使用写接口来接受用户访问,然后数据库字段++ 的方案,因为需要维护 node 服务器、还有数据库,成本太高。

最后决定直接读取 nginx 的 log 日志信息,来获取用户的访问量。

要实现该功能,可以从以下几步来实现:

  1. 格式化 nginx 输出日志
  2. 统计日志中某个文件(例如 index.html)的日志数量
  3. 将数据输出到 json 文件中,供前端请求
  4. 定时不断执行 2、3 任务输出到文件中

下面介绍如何来实现以上功能

格式化 nginx 输出日志

借助 nginx 的 log_format 我们可以自定义一个日志输出格式。

1
2
3
4
5
http {
# ... 其他配置...
log_format analysis '$remote_addr - [$time_local] "$request" '
'"$http_referer" "$http_user_agent" "$http_x_forwarded_for" ';
}

修改我们的 server 配置

1
2
3
4
5
server {
listen 3000;
access_log /var/lib/nginx/log/website.log analysis;
# ... 其他配置...
}

注意 analysis 为我们自己的命名,只要保证相同即可。

这样拿到的请求数据日志就会如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
10.232.140.148 - [21/May/2021:16:14:48 +0800]  "GET /assets/js/runtime~main.1e8e793d.js HTTP/1.1" "http://10.210.4.154:3000/" "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" "-"
10.232.140.148 - [21/May/2021:16:14:48 +0800] "GET /img/logo.svg HTTP/1.1" "http://10.210.4.154:3000/" "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" "-"
10.232.140.148 - [21/May/2021:16:14:49 +0800] "GET /assets/css/styles.a72913c9.css HTTP/1.1" "http://10.210.4.154:3000/" "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" "-"
10.232.140.148 - [21/May/2021:16:14:49 +0800] "GET /assets/js/main.b489375c.js HTTP/1.1" "http://10.210.4.154:3000/" "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" "-"
10.232.140.148 - [21/May/2021:16:14:49 +0800] "GET /assets/js/486.ab98b521.js HTTP/1.1" "http://10.210.4.154:3000/" "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" "-"
10.232.140.148 - [21/May/2021:16:14:49 +0800] "GET /assets/js/611.58bc5d65.js HTTP/1.1" "http://10.210.4.154:3000/" "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" "-"
10.232.140.148 - [21/May/2021:16:14:49 +0800] "GET /assets/js/125.1dc4a115.js HTTP/1.1" "http://10.210.4.154:3000/" "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" "-"
10.232.140.148 - [21/May/2021:16:14:49 +0800] "GET /assets/js/c4f5d8e4.75ce3469.js HTTP/1.1" "http://10.210.4.154:3000/" "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" "-"
10.232.140.148 - [21/May/2021:16:14:49 +0800] "GET /assets/js/1be78505.085d4311.js HTTP/1.1" "http://10.210.4.154:3000/" "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" "-"
10.232.140.148 - [21/May/2021:16:14:49 +0800] "GET /assets/js/935f2afb.1a476284.js HTTP/1.1" "http://10.210.4.154:3000/" "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" "-"
10.232.140.148 - [21/May/2021:16:14:49 +0800] "GET /img/favicon.ico HTTP/1.1" "http://10.210.4.154:3000/" "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" "-"
10.232.140.148 - [21/May/2021:16:14:49 +0800] "GET /assets/js/c4f5d8e4.75ce3469.js HTTP/1.1" "http://10.210.4.154:3000/" "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" "-"
10.232.140.148 - [21/May/2021:16:14:50 +0800] "GET /assets/js/17896441.f8ee3a49.js HTTP/1.1" "http://10.210.4.154:3000/" "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" "-"
10.232.140.148 - [21/May/2021:16:14:50 +0800] "GET /assets/js/bbe1322f.7a2bf3d6.js HTTP/1.1" "http://10.210.4.154:3000/" "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" "-"

有了以上数据,我们便可以分析 UV/PV 等信息,当前需求是页面访问量,所以我们只需要取 index.html 的请求次数即可。

实践中,index.html 作为请求次数不是一个好的方案,因为 nginx 配置,请求/也会直接返回 index.html 文件,因此数据可能会少很多,因此我们采用统计runtime~main.js的请求次数作为请求次数的统计。

统计日志中某个文件的日志数量

我们使用grep命令从日志中过滤符合条件的文本,然后使用wc(word count)来统计出现的行数,这样就能拿到日志的行数,进而拿到了请求次数。

1
grep runtime /var/lib/nginx/log/website.log |wc -l

将数据输出到json文件中,供前端请求

我们只需要将输出的内容,写入到前端对应的目录下,前端使用ajax请求对应的文件即可,假设我们的前端项目部署在:/var/lib/nginx/html/目录下,则可以这样输出:

1
grep runtime /var/lib/nginx/log/website.log |wc -l > /var/lib/nginx/html/pv.json

这样,在代码中我们便可以直接请求该文件即可

1
2
3
fetch("/pv.json")
    .then(response => response.json())
    .then(count => console.log("PV数为", count))

定时不断执行2、3任务输出到文件中

只有一次的数据输出,是无法满足不断统计用户请求数量的,因此我们可以借助crontab来执行定时任务。
创建名为pvcron的任务文件,编辑内容

1
* * * * * grep runtime /var/lib/nginx/log/website.log |wc -l > /var/lib/nginx/html/pv.json

前面的* * * * *的意思是每分钟执行一次该写入任务,也可以根据需要定制cron任务,比如:

1
* 8-22 * 5 *

该内容的意思是在5月份的每天8点-22点之间,每分钟执行一次

接下来执行

1
crontab pvcron

现在该文件已经提交给cron进程,它将每分钟运行一次来将PV数写入json文件供前端请求。

备注

该方案有多个点会导致数据不够精确

  1. grep 过滤runtime内容时,并不准确,因此可以使用正则表达式来提高准确性
  2. 如果文件进行了缓存,可能会导致请求没有发到nginx中,也会导致计算不准确
  3. crontab最小精度为分钟,所以时间粒度不够精细

因此,在以上问题都可接受的情况下,才可以采用此方案。

附录