We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
作者前段时间在做类似Google Analytics(以下简称GA)的第三方监控脚本。所以对GA的前端代码做过调研,对GA的压缩后代码做了一定程度上的人肉美化。这里美化的是analytics.js的j41版本,本文提到的小技巧也是基于这个版本的js。
GA的JS代码是运行在开发者网站中的,如果GA本身出现了bug那么将会影响到很多的网站。除了发布前做好检查之外,还需要对线上GA的JS代码做好出错监控,确保线上错误出现时能够及时修复问题。
onerror
在Web前端领域做JS错误监控的常用方法就是重写window.onerror方法然后获取出错的信息,但是这同时也会监控到页面上其他的JS代码的错误。当然我们可以根据第二个参数source来获取仅仅属于GA自己的错误信息。示例代码如下:
window.onerror
source
GA
window.onerror = function (msg, url, lineNo, columnNo, error) { if (~source.indexOf('analytics.js')) { // ... handle error ... } return false; }
不过这个方法也有两个致命确定。
script
crossorigin
GA的JS代码所在的域名一般都放在开发者的网页上,所以受到同源策略的限制。而且GA对监控脚本的兼容性要求较高希望支持更多的浏览器。所以重写window.onerror的方法并不是很合适。
try catch
它采用了更为稳妥的try catch方案。对所有的对外接口做了一层包装,当接口调用出错时发送错误日志给后端。示例代码如下:
/** * 对接口做一个包装并返回 * * wrap('functionName', function() {}); * var context = { * name: function() {} * }; * wrap('name', context); * * @param {string} methodName 函数在对象上的属性名 * @param {Object} context 函数所在的对象 * @param {Function} method 函数 * @param {number} idx 函数所在的对象 */ function wrapApi(methodName, context, method, idx) { context[methodName] = function() { try { idx && reg(idx); return method.apply(this, arguments) } catch (e) { // 发送错误日志 errorPing('exc', methodName, e && e.name); throw e; } } };
有意思的是GA并没有每次出错都发日志来记录问题,而是只取其中的1%来发送日志,也就是99%的概率会直接放过。这么设计的原因本人猜想有三点:
当GA的码农们接收到错误告警邮件之后如何去定位这个错误呢?首先他们会找到出错的网站和浏览器,然后打开对应的浏览器和网站地址去复现错误。
如果错误比较常见,那么很容易就能复现。如果是登陆后的网站地址呢?如果是在用户的机器下才能复现呢?如果用户通过修改了hosts文件造了个假域名来访问本地文件呢?如果……
比较简单的方法就是去联系用户QQ远程协助看看出问题的原因。前提是你开发的是普通的网站且能找到用户联系方式且用户的协助意愿比较强,那么可以这么做。GA的报错是发生在非自己可控的网站上,更拿不到大多数用户的联系信息。
GA采用另一种方法,通过“打点”来记录运行环境和执行位置。打点信息会被传给后端用于还原错误产生时候的状态,分析可能的原因并尝试解决。
GA通过一个数字ID来给每个重要的代码标示位标记,如果代码进入了这个标示位或者触发了这个标示位那么就将对应的数字ID传给服务器。如果没触发则不发。
服务器通过传过来的ID,便能知道哪些逻辑被执行过,依次来实现一定程度上的场景还原,帮助定位问题。上面的代码中reg(idx);这行便是用来记录代码标识位的操作,idx则是标识位。
reg(idx);
idx
一般来说标识位主要用在两种地方:
第一点相对容易理解,比如之前的代码就是在包装过的API方法执行之前做一次打点。然后服务器便可以知道API方法是否已经执行过。以此来大概判断出错位置。
第二个点则是用来判断一些特殊条件,例如:
if (noCookie) { reg(20); // 分支逻辑 } // 正常逻辑
例子中当浏览器没有开启cookie支持时,要执行特殊的分支逻辑。这些地方很可能是开发者在平时很少测试到的逻辑,比较容易出现线上bug。所以要增加打点,帮助定位当时浏览器的运行环境,确定哪些特殊分支逻辑被执行了。
依靠打点GA开发者基本上就可以确定哪些逻辑被执行过了,但并不能定位到执行的次数。对于GA的情况执行的次数可能并不是一个重要的信息,所以没有记录。
打点的数据本质上是一个数字数字,例如:
[1, 4, 10, 14, 15, 18, 23, 26, 28, 30, 32, 35, 38, 40, 41, 43, 45, 48]
数字并不一定是连续的。相对普通的传输方式是arr.join('.')之后变成1.4.10.14.15.18.23.26.28.30.32.35.38.40.41.43.45.48传给后端。当打点很多时传输的字符数据会太多。我们之前已经讨论过:传输数据的长度是有限制的。所以要尽量压缩字符长度。GA采取了一种有趣的压缩算法,大致代码如下:
arr.join('.')
1.4.10.14.15.18.23.26.28.30.32.35.38.40.41.43.45.48
function encode(data) { var arr = []; for (var i = 0; i < data.length; i++) { if (data[i]) { // `1 << x` === `Math.pow(2, x)` arr[Math.floor(i / 6)] ^= 1 << (i % 6); } } for (i = 0; i < arr.length; i++) { arr[i] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'.charAt(arr[i] || 0); } return arr.join('') + '~'; }
大致的逻辑是:
[true, true, false, true, false, ......]
true
0
[true, true, false, true, false, true]
101011
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_
~
这样就可以尽量压缩传输数据的字符长度了,只需要在服务器端解码即可。
GA先通过try catch来捕捉到JS错误,同时使用打点来记录JS的执行情况。然后将压缩后的打点数据和错误信息一起传输给后端记录,以便之后分析问题。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
GA的JS代码是运行在开发者网站中的,如果GA本身出现了bug那么将会影响到很多的网站。除了发布前做好检查之外,还需要对线上GA的JS代码做好出错监控,确保线上错误出现时能够及时修复问题。
onerror
事件在Web前端领域做JS错误监控的常用方法就是重写
window.onerror
方法然后获取出错的信息,但是这同时也会监控到页面上其他的JS代码的错误。当然我们可以根据第二个参数source
来获取仅仅属于GA
自己的错误信息。示例代码如下:不过这个方法也有两个致命确定。
onerror
事件,但是一些旧版浏览器并不支持它script
标签的crossorigin
属性才能够得到更加完整的错误信息。当然crossorigin
属性也是有兼容性问题的GA的JS代码所在的域名一般都放在开发者的网页上,所以受到同源策略的限制。而且GA对监控脚本的兼容性要求较高希望支持更多的浏览器。所以重写
window.onerror
的方法并不是很合适。try catch
方案它采用了更为稳妥的
try catch
方案。对所有的对外接口做了一层包装,当接口调用出错时发送错误日志给后端。示例代码如下:频率控制
有意思的是GA并没有每次出错都发日志来记录问题,而是只取其中的1%来发送日志,也就是99%的概率会直接放过。这么设计的原因本人猜想有三点:
错误的定位
当GA的码农们接收到错误告警邮件之后如何去定位这个错误呢?首先他们会找到出错的网站和浏览器,然后打开对应的浏览器和网站地址去复现错误。
如果错误比较常见,那么很容易就能复现。如果是登陆后的网站地址呢?如果是在用户的机器下才能复现呢?如果用户通过修改了hosts文件造了个假域名来访问本地文件呢?如果……
比较简单的方法就是去联系用户QQ远程协助看看出问题的原因。前提是你开发的是普通的网站且能找到用户联系方式且用户的协助意愿比较强,那么可以这么做。GA的报错是发生在非自己可控的网站上,更拿不到大多数用户的联系信息。
GA采用另一种方法,通过“打点”来记录运行环境和执行位置。打点信息会被传给后端用于还原错误产生时候的状态,分析可能的原因并尝试解决。
打点
GA通过一个数字ID来给每个重要的代码标示位标记,如果代码进入了这个标示位或者触发了这个标示位那么就将对应的数字ID传给服务器。如果没触发则不发。
服务器通过传过来的ID,便能知道哪些逻辑被执行过,依次来实现一定程度上的场景还原,帮助定位问题。上面的代码中
reg(idx);
这行便是用来记录代码标识位的操作,idx
则是标识位。一般来说标识位主要用在两种地方:
第一点相对容易理解,比如之前的代码就是在包装过的API方法执行之前做一次打点。然后服务器便可以知道API方法是否已经执行过。以此来大概判断出错位置。
第二个点则是用来判断一些特殊条件,例如:
例子中当浏览器没有开启cookie支持时,要执行特殊的分支逻辑。这些地方很可能是开发者在平时很少测试到的逻辑,比较容易出现线上bug。所以要增加打点,帮助定位当时浏览器的运行环境,确定哪些特殊分支逻辑被执行了。
依靠打点GA开发者基本上就可以确定哪些逻辑被执行过了,但并不能定位到执行的次数。对于GA的情况执行的次数可能并不是一个重要的信息,所以没有记录。
打点的传输
打点的数据本质上是一个数字数字,例如:
数字并不一定是连续的。相对普通的传输方式是
arr.join('.')
之后变成1.4.10.14.15.18.23.26.28.30.32.35.38.40.41.43.45.48
传给后端。当打点很多时传输的字符数据会太多。我们之前已经讨论过:传输数据的长度是有限制的。所以要尽量压缩字符长度。GA采取了一种有趣的压缩算法,大致代码如下:大致的逻辑是:
[true, true, false, true, false, ......]
。数组的每个值代表的对应标示位是否开启。例如第一个值为true
,那么标示位0
打开[true, true, false, true, false, true]
被转换成二进制数101011
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_
的索引值~
这样就可以尽量压缩传输数据的字符长度了,只需要在服务器端解码即可。
总结
GA先通过
try catch
来捕捉到JS错误,同时使用打点来记录JS的执行情况。然后将压缩后的打点数据和错误信息一起传输给后端记录,以便之后分析问题。The text was updated successfully, but these errors were encountered: