一文教会你使用Nginx访问日志统计PV与UV
前言
一个网站当用户量增大时候,不可避免有统计pv和uv的需求。
- UV(Unique Visitor):独立访客,以cookie为依据区分不同访客,UV计算一天之内(00:00-24:00),访问网站的访客数量。
- PV(Page View):页面访问量,同一个用户对页面多次访问累计。
本文介绍一种通过分析nginx日志统计pv、uv的方法。
一、方案设计
如何根据Nginx的访问日志统计pv和uv呢?
我们可以通过分析nginx的访问网站页面的日志来统计参数,比如一个单页应用的博客网站,用户访问/、/article_list、/article_detail都应该算作一次访问。
但是如果网站的路由不确定时候,就不好统计。当路由变化时候,需要更新统计脚本。而且,用户首次访问后才设置了cookie,所以首次页面请求是不带cookie的,这会导致漏报。另外,用cookie记录数据,由于是js写的cookie,所以需要保证同域访问,这就很不灵活。如果不是js写的cookie,那就说明依赖后端服务,也不够灵活。
所以我们采取的方法是前端上报页面访问事件。
首先前端生成一个uuid,向Nginx发起一个请求并携带uuid,Nginx会精确匹配这个请求,然后返回204,以减小数据传输量。
由于上报地址和页面是同域的,因此我们这里使用cookie保存uuid,如果不同域,还可以使用localStorage将uuid存在本地,然后在参数中将uuid带上。
Nginx收到上报后,根据我们指定的固定格式生成日志。我们还要设置定时任务,定期切割日志,以便分析日志时候以月和天为维度统计指标。
整体流程示意图如下:
二、上报访问事件
前端使用uuid这个库生成uuid,使用js-cookie对cookie进行读写,cookie有效期设置为30天,如果已经存在则不设置。
这里上报地址是“/report.gif”。为了避免上报请求被缓存,请求参数加一个时间戳。
// index.js import Cookies from 'js-cookie'; import {v4 as uuidv4} from 'uuid'; try { if (!Cookies.get('uuid')) { Cookies.set('uuid', uuidv4().replace(/-/g, ''), {expires: 30}); } // 上报访问 axios.get(`https://www.example.com/report.gif?t=${Date.now()}`); } catch (e) {}
Nginx需要配置响应
location =/report.gif { return 204; }
三、Nginx配置日志格式
我们可以指定Nginx访问日志的格式,分析日志时候更方便。
注意,log_format指令只能用在http模块中,不能用在server模块中。
这里在http模块中通过log_format定义了一个格式,命名为main,然后在server模块中使用access_log定义访问日志的存放目录,并且引用main指定日志格式。server模块中还匹配了请求里面的cookie,取出uuid赋值给$uuid变量以便写日志时候能够正常读取uuid。
http { log_format main '$remote_addr - [$time_local] "$request" ' ' - $status "uuid:$uuid" '; server { access_log /path/to/log/access443.log main; if ( $http_cookie ~* "uuid=([A-Z0-9]*)"){ set $uuid $1; } } }
我们会得到这样的日志
101.241.91.99 - [04/May/2022:09:36:34 +0800] "GET /assets/vendor.337922eb.js HTTP/1.1" - 304 "uuid:a27050e998864af89de0fbc7605d1548"
101.241.91.99 - [04/May/2022:09:36:34 +0800] "GET /assets/style.81f77c22.css HTTP/1.1" - 200 "uuid:a27050e998864af89de0fbc7605d1548"
101.241.91.99 - [04/May/2022:09:36:34 +0800] "GET /assets/index.9c0fae7c.js HTTP/1.1" - 304 "uuid:a27050e998864af89de0fbc7605d1548"
101.241.91.99 - [04/May/2022:09:36:34 +0800] "GET /assets/quiz.5e3bb724.js HTTP/1.1" - 304 "uuid:a27050e998864af89de0fbc7605d1548"
101.241.91.99 - [04/May/2022:09:36:34 +0800] "GET /report.gif?id=0&t=1651628194189 HTTP/1.1" - 204 "uuid:a27050e998864af89de0fbc7605d1548"
101.241.91.99 - [04/May/2022:09:36:34 +0800] "GET /assets/logo.c5f2dde3.jpeg HTTP/1.1" - 200 "uuid:a27050e998864af89de0fbc7605d1548"
101.241.91.99 - [04/May/2022:09:36:34 +0800] "GET /favicon.ico HTTP/1.1" - 200 "uuid:a27050e998864af89de0fbc7605d1548"
四、日志切割
为了方便统计我们希望把日志文件按时间分割,分割成这样的结构:
├── 2022
│ └── 05
│ └── 03.log
按照年、月、日分层,每天生成一个日志。
实现思路是,先建立一个日志存放目录,每天的凌晨0点1分,将前一天的日志按照日期移动到日志目录中。然后再重新创建一个日志文件供Nginx写入。
先写一个脚本实现这个功能
log_split.sh
#!/bin/bash # 定位到脚本所在目录(注意我这里也是Nginx写访问日志的目录,当然这不是必须的) log_base=$(cd `dirname $0`; pwd) # 根据前一天的时间生成日志所在目录名 log_path=${log_base}/$(date -d yesterday +%Y)/$(date -d yesterday +%m) # 创建日志目录 mkdir -p $log_path # 将当前Nginx的日志移动到指定存放目录 mv $log_base/access443.log $log_path/$(date -d yesterday +%d).log # 重新创建日志文件,给Nginx写日志用 touch $log_base/access443.log # 给Nginx发送信号,注意你的Nginx目录可能不同 kill -USR1 `cat /www/server/nginx/logs/nginx.pid`
值得注意的是,虽然移动完日志,并且重新创建,但是Nginx的文件引用还是移走的那个,所以最后要给Nginx发送信号,让它写到新的日志文件中。
脚本写完,我们还要定时(每天0点1分执行切割任务),这用到了Linux的crontab工具。
首先在控制台输入crontab -e
打开编辑界面。然后输入1 0 * * * sh /path/to/log/log_split.sh
。这个定时任务的意思是每天0点1分执行日志分割脚本,编辑完成后保存关闭,定时任务就生效了。
我们还可以通过crontab -l
查看当前的定时任务;通过crontab -r
移除当前的定时任务。
五、Nodejs脚本分析日志,统计PV、UV
有了日志,就很容易分析PV、UV。我们可以使用Linux命令分析,但我这次选择用Nodejs脚本来统计,原因是对JS更熟悉,另外相对Linux也更灵活。
分析的大概思路是根据每天的访问日志,过滤出report.gif这个上报请求,上报次数就是PV,然后根据uuid去重,得到UV。
统计脚本如下:
// stats.js const fs = require('fs'); const path = require('path'); const args = process.argv.slice(2); const [year] = args; // 打印统计结果 function echo() { yearDir = year || '2022'; const stats = statsYearLog(yearDir); Object.entries(stats) .sort(([a], [b]) => a - b) .forEach(([month, dateStats]) => { console.log(`${month}月`); Object.entries(dateStats) .sort(([a], [b]) => a - b) .forEach(([date, {pv, uv}]) => { console.log(' ', `${date}日`, `pv: ${pv}`, `uv: ${uv}`); }); console.log('\n'); }); } // 统计某一年的数据 function statsYearLog(year) { // 读取目录下的文件夹名字 const dir = path.resolve(__dirname, year); const monthDirList = fs.readdirSync(dir); const logMap = monthDirList.reduce((result, monthDir) => { const monthStats = statsMonthLog(year, monthDir); result[monthDir] = monthStats; return result; }, {}); return logMap; } // 统计每个月的数据 function statsMonthLog(year, month) { const dir = path.resolve(__dirname, year, month); const dateLogList = fs.readdirSync(dir); const monthLogMap = dateLogList.reduce((result, dateLogFileName) => { const dateStats = statsDateLog(year, month, dateLogFileName); result[dateLogFileName.replace('.log', '')] = dateStats; return result; }, {}); return monthLogMap; } // 统计某天的数据 function statsDateLog(year, month, dateFile) { const logPath = path.resolve(__dirname, year, month, dateFile); const logText = fs.readFileSync(logPath, 'utf-8'); const logList = logText.split('\n'); const pvLogList = logList.filter((line) => { return /report.gif/.test(line) }); const uvLogMap = pvLogList.reduce((result, line) => { const match = line.match(/uuid:(\S+)"/); if (match && match[1]) { result[match[1]] = 1; } return result; }, {}); return {pv: pvLogList.length, uv: Object.keys(uvLogMap).length}; } // 执行打印统计结果 echo();
执行统计脚本node stats.js 2022
打印结果
05月
03日 pv: 1 uv: 1
六、展望
后续可以考虑扩展现有能力,让Node实现日志切割的功能,并提供api和界面,可以可视化统计PV、UV。
到此这篇关于Nginx访问日志统计PV与UV的文章就介绍到这了,更多相关Nginx统计PV与UV内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!
原文出处:https://juejin.cn/post/7093699777572896804
相关文章
详解nginx同一端口监听多个域名和同时监听http与https
这篇文章主要介绍了详解nginx同一端口监听多个域名和同时监听http与https的相关资料,需要的朋友可以参考下...2017-07-06- 这篇文章主要介绍了Nginx根据不同浏览器语言配置页面跳转的方法,包括一个简体繁体的基本判断方法及实际根据中英文跳转的例子,需要的朋友可以参考下...2016-05-22
- 周一今天给大家分享shell脚本多实例部署nginx的详细教程,文章通过实例代码脚本给大家详细介绍,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧...2021-10-26
- 这篇文章主要介绍了Nginx中配置过滤爬虫的User-Agent的简单方法,文中罗列了一些常用搜索引擎的爬虫名称以免造成不必要的过滤,需要的朋友可以参考下...2016-01-27
- 这篇文章主要介绍了nginx配置引发的403问题解决办法的相关资料,需要的朋友可以参考下...2017-07-06
- 这篇文章主要介绍了Linux环境下nginx搭建简易图片服务器,需要的朋友可以参考下...2016-01-27
- 这篇文章主要介绍了Nginx访问日志及错误日志参数说明,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-11-14
- 这篇文章主要介绍了使用nginx方式实现http转换为https的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-09-06
Nginx反向代理proxy_cache_path directive is not allowed错误解决方法
这篇文章主要介绍了Nginx反向代理proxy_cache_path directive is not allowed错误解决方法,需要的朋友可以参考下...2016-01-27nginx+apache+mysql+php+memcached+squid搭建集群web环境
当前,LAMP开发模式是WEB开发的首选,如何搭建一个高效、可靠、稳定的WEB服务器一直是个热门主题,本文就是这个主题的一次尝试。...2016-01-27- Nginx日志主要分为两种:访问日志和错误日志。访问日志主要记录客户端访问Nginx的每一个请求,格式可以自定义。下面这篇文章主要给大家介绍了Nginx自定义访问日志的配置方式,需要的朋友可以参考学习,下面来一起看看吧。...2017-07-06
- 这篇文章主要介绍了nginx使用IPV6的相关配置项介绍,首先查看编译参数是否编译了IPV6模块,然后介绍了监听IPV6的配置语法,需要的朋友可以参考下...2016-01-27
解决使用了nginx获取IP地址都是127.0.0.1 的问题
这篇文章主要介绍了解决使用了nginx获取IP地址都是127.0.0.1 的问题,获取i工具的完整代码文中给大家提到,具体实例代码跟随小编一起看看吧...2021-09-18- 本篇文章主要介绍了nginx修改上传文件大小限制的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧。 ...2017-01-22
- 这篇文章主要介绍了Debian7编译安装nginx简明教程,本文直接给出操作命令和步骤,需要的朋友可以参考下...2016-01-27
- 这篇文章主要介绍了隐藏Nginx或Apache以及PHP的版本号的方法,主要用来防止针对性的漏洞攻击,需要的朋友可以参考下...2016-01-05
- 这篇文章主要介绍了Nginx DNS resolver配置实例,本文讲解在proxy_pass 和 upstream server 通信的时候需要手动指定 resolver,本文就给出了配置实例,需要的朋友可以参考下...2016-01-27
- 这篇文章主要介绍了关于Nginx中if语句的判断条件与多条件判断的相关资料,文中给出了详细的示例代码,对大家具有一定的参考价值,需要的朋友们下面来一起看看吧。...2017-07-06
详解Nginx服务器中配置Sysguard模块预防高负载的方案
这篇文章主要介绍了详解Nginx服务器中配置Sysguard模块预防高负载的方案,该模块由阿里巴巴的团队开发,能够设置负载阀值,比较强大,需要的朋友可以参考下...2016-02-02- 本篇文章主要介绍了nginx实现ssl反向代理实战,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧 ...2017-01-22