JS实现网站统计信息上报

之前


在动物厂的笔试和面试环节,都问到了用JS来做全站统计信息的上报的思路
面试官当时举例问了三个涉及的方面

  • PV / UV
  • 页面加载性能信息
  • JS运行报错信息

以及如何去部署这些公用脚本


PV / UV


根据定义,先设置相关的用户识别码,然后使用js获取各种信息,发送到后端接口。

  • PV: 页面访问次数,页面每刷新一次就可以count加一次
  • UV: 独立访客数,通过在cookie中设置唯一码进行区分不同用户
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 1. 生成用户识别码 uid 
var uid = "xxxx-xxxx-xxxx-xxxx".replace(/x/g, function(char) {
var charCode = Math.random() * 16 | 0;
return charCode.toString(16).toUpperCase();
});
// "1325-2E6F-A3E1-E2AD"

// 2. 保存UID到 cookie,并设置7天过期
var expiresTime = new Date();
expiresTime.setTime(expiresTime.getTime() + 7 * 24 * 3600000);
document.cookie = "UUID=" + escape (uid) + ";expires=" + expiresTime.toGMTString();
// 为了统一和操作、最好使用其他包装好的 cookie 操作库

// 3. 使用 sessionStorage 存储一次会话中的浏览量
sessionStorage && sessionStorage.PVCOUNT ? sessionStorage.PVCOUNT = Number(sessionStorage.PVCOUNT) + 1 : sessionStorage.PVCOUNT = 1;

// 4. 增加其他一些姿势
// 当前网址
window.location.href
// "http://buybuybuy.me/"

// 跳转前网址,可以查看是否通过搜索引擎跳转来的
document.referre
// "https://www.google.co.jp/"

// 客户端信息,可以抽取系统版本、浏览器等
navigator.userAgent
// "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36"

// 5. 之后就可以把前面的信息 ajax 传输到后端统计接口

另外需要注意的一点是,前台js是无法直接获取客户端的ip地址。
若要使用ip地址:

  • 可以使用ajax向后台发送一个请求返回ip地址
  • 或者使用服务器预先渲染js代码到页面中

页面加载性能信息


了解前端各种资源在客户端的加载情况,便于己方对各类资源进行CDN缓存等来改进用户体验。
这里可以直接使用 window.performance 对象来评估。

window.performance.timing

包含了各种页面加载的时序信息

  • navigationStart:前一网页unload事件时间
  • redirectStart:发生http跳转时间
  • requestStart:发送http请求时间
  • domLoading:dom开始解析时间

timing

更多参考:Performance API - W3C Performance API - ruanyifeng

window.performance.getEntries

返回页面加载过程中,http文件请求的结果。
以数组形式保存每个文件请求的相关信息。
firefox和chrome都支持该api。

getEntries

更多参考:Evaluating network performance


JS运行报错信息


除去代码中try-catch和throw配对地进行处理可能的错误之外。
当js发生运行时错误和语法错误时,window 对象的 onerror 方法会被调用执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// onerror 函数提供的参数阐述了发生错误的各种信息
// main.js
window.onerror = function (message, url, lineNumber, columnNumber, error) {
// 1. message
// 错误信息
// 2. url
// 脚本资源url
// 3. lineNumber & 4. columnNumber
// 发生错误的行号和列号
// 5. 发生错误相关的error对象
console.log(message + '\n' + url + '\n' + lineNumber + '\n' + columnNumber + '\n' + error.stack);


// fun.js
var k = 1;
var f = Number(k + k2);

// console
> Uncaught ReferenceError: k2 is not defined
> http://localhost:8088/fun/fun.js
> 2
> 20
> ReferenceError: k2 is not defined
> at http://localhost:8088/fun/fun.js:2:20

但是页面上资源(img等)加载错误,只会在该element上触发事件,且该事件并不会向上冒泡。


利用 Nginx 部署全站脚本


因为这类信息统计脚本算是全站所有页面的公有脚本,可以考虑在服务器页面响应中插入此段代码。
这里可以使用 nginx 的 ngx_http_sub_module 模块完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 因为之前使用homebrew安装的nginx
// 查看后发现,默认安装没有开启这个模块
nginx -V

// 于是编辑 nginx-full.rb 文件
brew edit nginx-full

// 在编译参数数组中添加 --with-http_sub_module
args = %W[
...
--with-http_sub_module
]

// 保存好修改的文件,使用brew卸载掉nginx再重装

然后修改nginx配置文件,在对应的 server > location 上下文中,添加 sub_filter 指令。
这里使用了变量 $injected 保存了脚本地址,对response中有 body 标签的地方修改插入。

1
2
3
4
5
6
7
8
9
// nginx.conf
location / {
...
set $injected '<script src="http://localhost:8088/other/status.js"></script>';

sub_filter '<body>' '<body>${injected}';
sub_filter_types *;
sub_filter_once on;
}

html代码注入脚本后的效果:

nginx

这里还可以在 sub_filter 多添加一条规则,将客户端ip地址替换注入到页面中。

1
2
// 替换响应中的 UUIP_IP 字段为 ip 地址
sub_filter 'UUIP_IP' '${remote_addr}';