2023年11月28日发(作者:)

前端代码异常⽇志收集与监控

收集⽇志的⽅法

平时收集⽇志的⼿段,可以归类为两个⽅⾯,⼀个是逻辑中的错误判断,为主动判断;⼀个是利⽤语⾔给我们提供的捷径,暴⼒式获取错误

信息,如 atchr

1. 主动判断

我们在⼀些运算之后,得到⼀个期望的结果,然⽽结果不是我们想要的

//

function calc(){

//

return val;

}

if(calc() !== "someVal"){

({

position: "::calc"

msg: "calc error"

});

/**

* @param {String} errorMessage 错误信息

* @param {String} scriptURI 出错的⽂件

* @param {Long} lineNumber 出错代码的⾏号

* @param {Long} columnNumber 出错代码的列号

* @param {Object} errorObj 错误的详细信息,Anything

*/

r = function(errorMessage, scriptURI, lineNumber,columnNumber,errorObj) {

// code..

}

r 算是⼀种特别暴⼒的容错⼿段,atch 也是如此,他们底层的实现就是利⽤ C/C++ 中的 goto 语句实现,⼀旦发现错

误,不管⽬前的堆栈有多深,不管代码运⾏到了何处,直接跑到顶层或者 atch 捕获的那⼀层,这种⼀脚踢开错误的处理⽅式并不是很

好。

收集⽇志存在的问题

收集⽇志的⽬的是为了及时发现问题,最好⽇志能够告诉我们,错误在哪⾥,更优秀的做法是,不仅告诉错误在哪⾥,还告诉我们,如何处

理这个错误。终极⽬标是,发现错误,⾃动容错,这⼀步是最难的。

1. ⽆具体报错信息,Script error.

先看下⾯的例⼦,

⽽拿到的结果却是:

翻开 Chromium 的 WebCore ,可以看到:

跨域情况下,返回的结果是 Script error.

// /browser/branches/chromium/1453/Source/WebCore/dom/#L333

String message = errorMessage;

int line = lineNumber;

String sourceName = sourceURL;

// 已经拿到了所有的错误信息,但如果发现是⾮同源情况,`sanitizeScriptError` 中复写错误信息

sanitizeScriptError(message, line, sourceName, cachedScript);

的 WebCore 中只判断了 securityOrigin()->canRequest(targetURL),新版中还多了⼀个 cachedScript 的判断,可以看出浏览器对这⽅⾯

的限制越来越严格。

在本地测试了下:

可见在 file:// 协议下,securityOrigin()->canRequest(targetURL) 也是 false

为何Script error.?

简单报错: Script error,⽬的是避免数据泄露到不安全的域中,⼀个简单的例⼦:

上⾯我们并没有引⼊⼀个 js ⽂件,⽽是⼀个 html,这个 html 是银⾏的登录页⾯,如果你已经登录了 ,那 login 页⾯就会⾃动

跳转到 ,如果未登录则跳转到 ,那么 JS 报错也会是 is not defined is not

defined,通过这些信息可以判断⼀个⽤户是否登录他的银⾏帐号,给 hacker 提供了⼗分便利的判断渠道,这是相当不安全的。

crossOrigin参数跳过跨域限制

image 和 script 标签都有 crossorigin 参数,它的作⽤就是告诉浏览器,我要加载⼀个外域的资源,并且我信任这个资源。

然⽽,却报错了:

这是意料之中的错误,跨域资源共享策略要求,服务器也设置 Access-Control-Allow-Origin 的响应头:

header('Access-Control-Allow-Origin: *');

回头看看我们 CDN 的资源,

Javascript/CSS/Image/Font/SWF 等这些静态资源其实都已经早早地加上了 CORS 响应头。

2. 压缩代码⽆法定位到错误的具体位置

线上的代码⼏乎都是经过打包压缩的,⼏⼗上百的⽂件压缩后打包成⼀个,⽽且只有⼀⾏。当我们收到 a is not defined 的时候,如果只在

特定场景下才报错,我们根本⽆法定位到这个被压缩的 a 是个什么东西,那么此时的错误⽇志就是⽆效的。

第⼀个想到的办法是利⽤ sourceMap,利⽤它可以定位到压缩代码某⼀点在未压缩代码的具体位置。下⾯是 sourceMap 引⼊的格式,在

代码的最后⼀⾏加⼊:

(function(){})(); // file 1

// 1000 个空⾏

(function(){})(); // file 2

// 1000 个空⾏

(function(){})(); // file 3

// 1000 个空⾏

(function(){})(); // file 4

var _fileConfig = ['file 1', 'file 2', 'file 3', 'file 4']

如果报错在第 3001 ⾏,

r = function(msg, url, line, col, error){

// line = 3001

var lineNum = line;

("错误位置:" + _fileConfig[parseInt(lineNum / 1000) - 1]);

// -> "错误位置:file 3"

function needReport (sampling){

// sampling: 0 - 1

return () <= sampling;

}

= function(errInfo, sampling) {

if(needReport(sampling || 1)){

Reporter._send(errInfo);

}

};

这个采样率可以按需求来处理,可以同上,使⽤⼀个随机数,也可以使⽤ cookie 中的某个字段(如 nickname)的最后⼀个字母/数字来

判定,也可以将⽤户的 nickname 进⾏ hash 计算,再通过最后⼀位的字母/数字来判断,总之,⽅法是很多的。

收集⽇志布点位置

为了更加精准的拿到错误信息,有效地统计错误⽇志,我们应该更多地采⽤主动式埋点,⽐如在⼀个接⼝的请求中:

// Module A Get Shops Data

$.ajax({

url: URL,

dataType: "jsonp",

success: function(ret) {

if( === "failed") {

类似这样的错误都是不太可控的。可以在使⽤到 atch 的地⽅思考是否可以使⽤其他⽅式做兼容。感谢 EtherDream 的。

关于 r 的使⽤

可以尝试如下代码:

//

throw new Error("SHOW ME");

r = function(){

(arguments);

// 阻⽌在控制台中打印错误信息

return true;

};

上⾯的代码直接报错了,没有继续往下执⾏。页⾯中可能有好⼏个 script 标签,但是

r

这个错误监听⼀定要放到最前头!

错误的警报与提⽰

什么时候该警报?不能有错就报。上⾯也说了,因为⽹络环境和浏览器环境因素,复杂页⾯我们允许千分之⼀的错误率。⽇志处理后的数据

图: