前端代码异常监控实战,前端性能与异常上报

时间:2019-10-10 10:40来源:关于计算机
后边叁特性能与丰裕申报 2018/08/22 · 基本功技巧 ·性能 原稿出处: counterxing    初稿出处: happylindz    概述 对此后台开荒来讲,记录日志是一种十三分常见的费用习于旧贯,平常我

后边叁特性能与丰裕申报

2018/08/22 · 基本功技巧 · 性能

原稿出处: counterxing   

初稿出处: happylindz   

概述

对此后台开荒来讲,记录日志是一种十三分常见的费用习于旧贯,平常我们会使用try...catch代码块来主动抓获错误、对于每一趟接口调用,也会记录下每回接口调用的时间消耗,以便大家监察和控制服务器接口质量,进行难点各个核实。

刚进公司时,在开展Node.js的接口开垦时,笔者不太习贯每一遍每一个考察难题都要因此跳板机登上服务器看日志,后来稳步习于旧贯了这种艺术。

举例:

JavaScript

/** * 获取列表数据 * @parma req, res */ exports.getList = async function (req, res) { //获取央求参数 const openId = req.session.userinfo.openId; logger.info(`handler getList, user openId is ${openId}`); try { // 获得列表数据 const startTime = new Date().getTime(); let res = await ListService.getListFromDB(openId); logger.info(`handler getList, ListService.getListFromDB cost time ${new Date().getTime() - startDate}`); // 对数据管理,重临给前端 // ... } catch(error) { logger.error(`handler getList is error, ${JSON.stringify(error)}`); } };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 获取列表数据
* @parma req, res
*/
exports.getList = async function (req, res) {
    //获取请求参数
    const openId = req.session.userinfo.openId;
    logger.info(`handler getList, user openId is ${openId}`);
 
    try {
        // 拿到列表数据
        const startTime = new Date().getTime();
        let res = await ListService.getListFromDB(openId);
        logger.info(`handler getList, ListService.getListFromDB cost time ${new Date().getTime() - startDate}`);
        // 对数据处理,返回给前端
        // ...
    } catch(error) {
        logger.error(`handler getList is error, ${JSON.stringify(error)}`);
    }
};

以下代码常常会出现在用Node.js的接口中,在接口中会总计查询DB所耗费时间间、亦或许总计RPC劳动调用所耗时间,以便监测品质瓶颈,对品质做优化;又也许对至极使用try ... catch义不容辞抓获,以便随即对标题实行回看、还原难题的风貌,进行bug的修复。

而对在此以前端来讲吧?可以看以下的现象。

前不久在进展贰个急需开拓时,临时开掘webgl渲染印象退步的情事,也许说影象会并发剖析失败的事态,大家大概根本不精通哪张印象会解析或渲染失利;又或如最近成本的别的八个急需,我们会做贰个有关webgl渲染时间的优化和形象预加载的必要,借使缺少质量监察和控制,该怎么计算所做的渲染优化和形象预加载优化的优化比例,怎么样验证自个儿所做的政工具备价值吗?恐怕是经过测量检验同学的黑盒测验,对优化前后的岁月举办录屏,深入分析从进来页面到印象渲染实现到底经过了有一些帧图像。那样的数额,大概既不标准、又相比较片面,设想测量试验同学实际不是真正的客户,也不能复苏真实的客商他们所处的网络情况。回过头来开采,大家的品种,就算在服务端层面做好了日记和属性计算,但在前端对丰裕的监察和总体性的计算。对于前端的性能与足够申报的主旋律查究是有须要的。

前言

前边在对合营社的前端代码脚本错误实行逐个审查核对,试图收缩 JS Error 的错误量,结合自个儿后边的经验对那上头内容展开了实践并总结,上面就此谈谈自身对前面一个代码十分监察和控制的部分见解。

正文大约围绕上边几点展开商讨:

  1. JS 处理特别的主意
  2. 申报格局
  3. 十分监察和控制上报常见难点

十一分捕获

对于前端来讲,大家需求的十三分捕获无非为以下二种:

  • 接口调用境况;
  • 页面逻辑是或不是错误,比如,客商步向页面后页面展现白屏;

对此接口调用景况,在前端平日需求申报顾客端相关参数,比如:客商OS与浏览器版本、央求参数(如页面ID);而对于页面逻辑是或不是错误难点,平时除了客商OS与浏览器版本外,须要的是报错的库房新闻及具体报错地方。

JS 分外管理

对于 Javascript 来说,我们面临的仅仅只是相当,非凡的面世不会直接导致 JS 引擎崩溃,最七只会使当前推行的天职终止。

  1. 近来代码块将用作一个任务压入任务队列中,JS 线程会不断地从职分队列中领取职分施行。
  2. 当职务执行进度中出现格外,且十一分未有捕获处理,则会直接本着调用栈一层层向外抛出,最后休息当前任务的实施。
  3. JS 线程会继续从任务队列中提取下一个职分继续实施。
JavaScript

<script> error console.log('永远不会执行'); </script>
<script> console.log('我继续执行') </script>

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5a707ba987416418324373-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5a707ba987416418324373-2">
2
</div>
<div class="crayon-num" data-line="crayon-5a707ba987416418324373-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5a707ba987416418324373-4">
4
</div>
<div class="crayon-num" data-line="crayon-5a707ba987416418324373-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5a707ba987416418324373-6">
6
</div>
<div class="crayon-num" data-line="crayon-5a707ba987416418324373-7">
7
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5a707ba987416418324373-1" class="crayon-line">
&lt;script&gt;
</div>
<div id="crayon-5a707ba987416418324373-2" class="crayon-line crayon-striped-line">
  error
</div>
<div id="crayon-5a707ba987416418324373-3" class="crayon-line">
  console.log('永远不会执行');
</div>
<div id="crayon-5a707ba987416418324373-4" class="crayon-line crayon-striped-line">
&lt;/script&gt;
</div>
<div id="crayon-5a707ba987416418324373-5" class="crayon-line">
&lt;script&gt;
</div>
<div id="crayon-5a707ba987416418324373-6" class="crayon-line crayon-striped-line">
  console.log('我继续执行')
</div>
<div id="crayon-5a707ba987416418324373-7" class="crayon-line">
&lt;/script&gt;
</div>
</div></td>
</tr>
</tbody>
</table>

永利皇宫463手机版 1

在对脚本错误举行反馈在此之前,我们必要对极其进行拍卖,程序需求先感知到脚本错误的发出,然后再谈至极申报。

脚本错误平时分为三种:语法错误,运行时不当。

上面就谈谈两种拾分监察和控制的管理格局:

丰富捕获方法

try-catch 卓殊管理

try-catch 在大家的代码中时常看看,通过给代码块实行 try-catch 举办李包裹装后,今世码块发生出错开上下班时间 catch 将能捕捉到错误的新闻,页面也将得以继续推行。

然而 try-catch 管理非常的工夫有限,只可以捕获捉到运转时非异步错误,对于语法错误和异步错误就显得爱莫能助,捕捉不到。

全局捕获

可以通过全局监听极度来捕获,通过window.onerror或者addEventListener,看之下例子:

JavaScript

window.onerror = function(errorMessage, scriptULacrosseI, lineNo, columnNo, error) { console.log('errorMessage: ' + errorMessage); // 十分新闻console.log('scriptUEvoqueI: ' + scriptU逍客I); // 极度文件路径console.log('lineNo: ' + lineNo); // 卓殊行号 console.log('columnNo: ' + columnNo); // 万分列号 console.log('error: ' + error); // 格外仓库新闻// ... // 分外上报 }; throw new Error('那是叁个谬误');

1
2
3
4
5
6
7
8
9
10
window.onerror = function(errorMessage, scriptURI, lineNo, columnNo, error) {
  console.log('errorMessage: ' + errorMessage); // 异常信息
  console.log('scriptURI: ' + scriptURI); // 异常文件路径
  console.log('lineNo: ' + lineNo); // 异常行号
  console.log('columnNo: ' + columnNo); // 异常列号
  console.log('error: ' + error); // 异常堆栈信息
  // ...
  // 异常上报
};
throw new Error('这是一个错误');

永利皇宫463手机版 2

通过window.onerror事件,能够获得具体的十一分消息、卓殊文件的ULX570L、十分的行号与列号及特别的库房消息,再捕获极度后,统一举报至大家的日志服务器。

亦或是,通过window.addEventListener主意来进展特别申报,道理同理:

JavaScript

window.addEventListener('error', function() { console.log(error); // ... // 相当上报 }); throw new Error('那是三个荒唐');

1
2
3
4
5
6
window.addEventListener('error', function() {
  console.log(error);
  // ...
  // 异常上报
});
throw new Error('这是一个错误');

永利皇宫463手机版 3

身体力行:运转时不当

JavaScript

try { error // 未定义变量 } catch(e) { console.log('小编精晓不当了'); console.log(e); }

1
2
3
4
5
6
try {
  error    // 未定义变量
} catch(e) {
  console.log('我知道错误了');
  console.log(e);
}

永利皇宫463手机版 4

然则对于语法错误和异步错误就捕捉不到了。

try… catch

使用try... catch虽说能够较好地开展非常捕获,不至于使得页面由于一处错误挂掉,但try ... catch抓获格局突显过分臃肿,许多代码应用try ... catch包装,影响代码可读性。

示范:语法错误

JavaScript

try { var error = 'error'; // 大写分号 } catch(e) { console.log('我感知不到错误'); console.log(e); }

1
2
3
4
5
6
try {
  var error = 'error';   // 大写分号
} catch(e) {
  console.log('我感知不到错误');
  console.log(e);
}

永利皇宫463手机版 5

诚如语法错误在编辑器就能够反映出来,常展现的错误消息为: Uncaught SyntaxError: Invalid or unexpected token xxx 那样。但是这种张冠李戴会向来抛出非常,常使程序崩溃,平常在编码时候便于观察得到。

广泛难题

演示:异步错误

JavaScript

try { setTimeout(() => { error // 异步错误 }) } catch(e) { console.log('我感知不到错误'); console.log(e); }

1
2
3
4
5
6
7
8
try {
  setTimeout(() => {
    error        // 异步错误
  })
} catch(e) {
  console.log('我感知不到错误');
  console.log(e);
}

永利皇宫463手机版 6

只有你在 setTimeout 函数中再套上一层 try-catch,不然就不只怕感知到其错误,但如此代码写起来比较啰嗦。

跨域脚本不恐怕正确捕获至极

平凡景况下,大家会把静态能源,如JavaScript本子放到特地的静态财富服务器,亦只怕CDN,看以下例子:

<!DOCTYPE html> <html> <head> <title></title> </head> <body> <script type="text/javascript"> // 在index.html window.onerror = function(errorMessage, scriptU昂CoraI, lineNo, columnNo, error) { console.log('errorMessage: ' + errorMessage); // 相当信息console.log('scriptU君越I: ' + scriptU宝马7系I); // 万分文件路线console.log('lineNo: ' + lineNo); // 至极行号 console.log('columnNo: ' + columnNo); // 非凡列号 console.log('error: ' + error); // 分外仓库新闻// ... // 相当上报 }; </script> <script src="./error.js"></script> </body> </html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <script type="text/javascript">
    // 在index.html
    window.onerror = function(errorMessage, scriptURI, lineNo, columnNo, error) {
      console.log('errorMessage: ' + errorMessage); // 异常信息
      console.log('scriptURI: ' + scriptURI); // 异常文件路径
      console.log('lineNo: ' + lineNo); // 异常行号
      console.log('columnNo: ' + columnNo); // 异常列号
      console.log('error: ' + error); // 异常堆栈信息
      // ...
      // 异常上报
    };
 
  </script>
  <script src="./error.js"></script>
</body>
</html>

JavaScript

// error.js throw new Error('那是二个荒唐');

1
2
// error.js
throw new Error('这是一个错误');

永利皇宫463手机版 7

结果展现,跨域之后window.onerror平昔捕获不到科学的不胜信息,而是统一重临贰个Script error

技术方案:对script标签增添五个crossorigin=”anonymous”,並且服务器加多Access-Control-Allow-Origin

<script src="" crossorigin="anonymous"></script>

1
<script src="http://cdn.xxx.com/index.js" crossorigin="anonymous"></script>

window.onerror 分外管理

window.onerror 捕获非凡工夫比 try-catch 稍微强点,无论是异步依旧非异步错误,onerror 都能捕获到运营时不当。

亲自去做:运维时一同错误

JavaScript

/** * @param {String} msg 错误新闻 * @param {String} url 出错文件 * @param {Number} row 行号 * @param {Number} col 列号 * @param {Object} error 错误详细音信 */ window.onerror = function (msg, url, row, col, error) { console.log('作者精晓不当了'); console.log({ msg, url, row, col, error }) return true; }; error

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @param {String}  msg    错误信息
* @param {String}  url    出错文件
* @param {Number}  row    行号
* @param {Number}  col    列号
* @param {Object}  error  错误详细信息
*/
window.onerror = function (msg, url, row, col, error) {
  console.log('我知道错误了');
  console.log({
    msg,  url,  row, col, error
  })
  return true;
};
error

永利皇宫463手机版 8

演示:异步错误

JavaScript

window.onerror = function (msg, url, row, col, error) { console.log('作者理解异步错误了'); console.log({ msg, url, row, col, error }) return true; }; setTimeout(() => { error; });

1
2
3
4
5
6
7
8
9
10
window.onerror = function (msg, url, row, col, error) {
  console.log('我知道异步错误了');
  console.log({
    msg,  url,  row, col, error
  })
  return true;
};
setTimeout(() => {
  error;
});

永利皇宫463手机版 9

而是 window.onerror 对于语法错误依然力无法及,所以大家在写代码的时候要尽量幸免语法错误的,不过貌似那样的失实会使得整个页面崩溃,照旧相比易于能够察觉到的。

在骨子里的使用过程中,onerror 首假设来捕获预料之外的不当,而 try-catch 则是用来在可预感景况下监察和控制特定的荒唐,两个结合使用越发急迅。

内需注意的是,window.onerror 函数唯有在回来 true 的时候,极度才不会进步抛出,不然纵然是明白特别的发出调控台依然展览会示 Uncaught Error: xxxxx。

永利皇宫463手机版 10

关于 window.onerror 还会有两点须要值得注意

  1. 对于 onerror 这种全局捕获,最好写在颇负 JS 脚本的眼前,因为您不可能保障你写的代码是或不是出错,假设写在后头,一旦爆发错误的话是不会被 onerror 捕获到的。
  2. 除此以外 onerror 是力不能支捕获到网络极度的荒谬。

当大家相见 <img src="./404.png">报 404 互联网伏乞十分的时候,onerror 是力不可能支支持大家捕获到丰硕的。

JavaScript

<script> window.onerror = function (msg, url, row, col, error) { console.log('笔者清楚异步错误了'); console.log({ msg, url, row, col, error }) return true; }; </script> <img src="./404.png">

1
2
3
4
5
6
7
8
9
10
<script>
  window.onerror = function (msg, url, row, col, error) {
    console.log('我知道异步错误了');
    console.log({
      msg,  url,  row, col, error
    })
    return true;
  };
</script>
<img src="./404.png">

永利皇宫463手机版 11

是因为互连网央求十分不会事件冒泡,因而必得在抓获阶段将其捕捉到才行,可是这种办法固然能够捕捉到互连网必要的那多少个,但是力不能及看清 HTTP 的动静是 404 照旧另外诸如 500 等等,所以还须求匹配服务端日志才举办排查分析才足以。

JavaScript

<script> window.addEventListener('error', (msg, url, row, col, error) => { console.log('我知道 404 错误了'); console.log( msg, url, row, col, error ); return true; }, true); </script> <img src="./404.png" alt="">

1
2
3
4
5
6
7
8
9
10
<script>
window.addEventListener('error', (msg, url, row, col, error) => {
  console.log('我知道 404 错误了');
  console.log(
    msg, url, row, col, error
  );
  return true;
}, true);
</script>
<img src="./404.png" alt="">

永利皇宫463手机版 12

那一点知识恐怕要求知道,要不然顾客采访网址,图片 CDN 不能够服务,图片加载不出去而开采人士未有发觉就窘迫了。

sourceMap

日常在生养意况下的代码是经过webpack卷入后回退混淆的代码,所以我们兴许会蒙受这么的题目,如图所示:

永利皇宫463手机版 13

我们开采装有的报错的代码行数都在第一行了,为何吗?那是因为在生产条件下,咱们的代码被压缩成了一行:

JavaScript

!function(e){var n={};function r(o){if(n[o])return n[o].exports;var t=n[o]={i:o,l:!1,exports:{}};return e[o].call(t.exports,t,t.exports,r),t.l=!0,t.exports}r.m=e,r.c=n,r.d=function(e,n,o){r.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:o})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,n){if(1&n&&(e=r(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(r.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var t in e)r.d(o,t,function(n){return e[n]}.bind(null,t));return o},r.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(n,"a",n),n},r.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},r.p="",r(r.s=0)}([function(e,n){throw window.onerror=function(e,n,r,o,t){console.log("errorMessage: "+e),console.log("scriptURI: "+n),console.log("lineNo: "+r),console.log("columnNo: "+o),console.log("error: "+t);var l={errorMessage:e||null,scriptURI:n||null,lineNo:r||null,columnNo:o||null,stack:t&&t.stack?t.stack:null};if(XMLHttpRequest){var u=new XMLHttpRequest;u.open("post","/middleware/errorMsg",!0),u.setRequestHeader("Content-Type","application/json"),u.send(JSON.stringify(l))}},new Error("那是多个不当")}]);

1
!function(e){var n={};function r(o){if(n[o])return n[o].exports;var t=n[o]={i:o,l:!1,exports:{}};return e[o].call(t.exports,t,t.exports,r),t.l=!0,t.exports}r.m=e,r.c=n,r.d=function(e,n,o){r.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:o})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,n){if(1&n&&(e=r(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(r.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var t in e)r.d(o,t,function(n){return e[n]}.bind(null,t));return o},r.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(n,"a",n),n},r.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},r.p="",r(r.s=0)}([function(e,n){throw window.onerror=function(e,n,r,o,t){console.log("errorMessage: "+e),console.log("scriptURI: "+n),console.log("lineNo: "+r),console.log("columnNo: "+o),console.log("error: "+t);var l={errorMessage:e||null,scriptURI:n||null,lineNo:r||null,columnNo:o||null,stack:t&&t.stack?t.stack:null};if(XMLHttpRequest){var u=new XMLHttpRequest;u.open("post","/middleware/errorMsg",!0),u.setRequestHeader("Content-Type","application/json"),u.send(JSON.stringify(l))}},new Error("这是一个错误")}]);

在本人的开支进程中也遇到过这些难题,作者在支付贰个功效组件库的时候,使用npm link了自己的零件库,但是由于组件库被npm link后是包装后的生产条件下的代码,全部的报错都稳固到了第一行。

消除办法是展开webpacksource-map,大家使用webpack包装后的变型的一份.map的本子文件就足以让浏览器对不当地方实行追踪了。此处能够参见webpack document。

实在便是webpack.config.js中增加一行devtool: 'source-map',如下所示,为示范的webpack.config.js

JavaScript

var path = require('path'); module.exports = { devtool: 'source-map', mode: 'development', entry: './client/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'client') } }

1
2
3
4
5
6
7
8
9
10
var path = require('path');
module.exports = {
    devtool: 'source-map',
    mode: 'development',
    entry: './client/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'client')
    }
}

webpack卷入后变卦对应的source-map,那样浏览器就能够稳固到实际错误的地方:

永利皇宫463手机版 14

开启source-map的劣势是宽容性,这两天独有Chrome浏览器和Firefox浏览器才对source-map支持。但是大家对这一类情形也可以有化解办法。能够动用引进npm库来援救source-map,可以参照他事他说加以考察mozilla/source-map。这个npm库既可以够运行在客商端也足以运作在服务端,可是更为推荐的是在服务端使用Node.js对吸收接纳到的日记消息时行使source-map解析,防止止源代码的泄漏导致风险,如下代码所示:

JavaScript

const express = require('express'); const fs = require('fs'); const router = express.Router(); const sourceMap = require('source-map'); const path = require('path'); const resolve = file => path.resolve(__dirname, file); // 定义post接口 router.get('/error/', async function(req, res) { // 获取前端传过来的报错对象 let error = JSON.parse(req.query.error); let url = error.scriptUEscortI; // 压缩文件路线if (url) { let fileUrl = url.slice(url.indexOf('client/')) + '.map'; // map文件路线 // 分析sourceMap let consumer = await new sourceMap.SourceMapConsumer(fs.readFileSync(resolve('../' + fileUrl), 'utf8')); // 再次回到一个promise对象 // 剖判原始报错数据 let result = consumer.originalPositionFor({ line: error.lineNo, // 压缩后的行号 column: error.columnNo // 压缩后的列号 }); console.log(result); } }); module.exports = router;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const express = require('express');
const fs = require('fs');
const router = express.Router();
const sourceMap = require('source-map');
const path = require('path');
const resolve = file => path.resolve(__dirname, file);
// 定义post接口
router.get('/error/', async function(req, res) {
    // 获取前端传过来的报错对象
    let error = JSON.parse(req.query.error);
    let url = error.scriptURI; // 压缩文件路径
    if (url) {
        let fileUrl = url.slice(url.indexOf('client/')) + '.map'; // map文件路径
        // 解析sourceMap
        let consumer = await new sourceMap.SourceMapConsumer(fs.readFileSync(resolve('../' + fileUrl), 'utf8')); // 返回一个promise对象
        // 解析原始报错数据
        let result = consumer.originalPositionFor({
            line: error.lineNo, // 压缩后的行号
            column: error.columnNo // 压缩后的列号
        });
        console.log(result);
    }
});
module.exports = router;

通常来讲图所示,大家早就足以看出,在服务端已经打响深入分析出了实际错误的行号、列号,我们得以经过日记的不二诀要开展记录,达到了前面贰个格外监察和控制的指标。

永利皇宫463手机版 15

Promise 错误

透过 Promise 能够支持大家缓慢解决异步回调鬼世界的难题,不过一旦 Promise 实例抛出万分而你没有用 catch 去捕获的话,onerror 或 try-catch 也不能够,不能捕捉到错误。

JavaScript

window.addEventListener('error', (msg, url, row, col, error) => { console.log('小编感知不到 promise 错误'); console.log( msg, url, row, col, error ); }, true); Promise.reject('promise error'); new Promise((resolve, reject) => { reject('promise error'); }); new Promise((resolve) => { resolve(); }).then(() => { throw 'promise error' });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
window.addEventListener('error', (msg, url, row, col, error) => {
  console.log('我感知不到 promise 错误');
  console.log(
    msg, url, row, col, error
  );
}, true);
Promise.reject('promise error');
new Promise((resolve, reject) => {
  reject('promise error');
});
new Promise((resolve) => {
  resolve();
}).then(() => {
  throw 'promise error'
});

永利皇宫463手机版 16

即使在写 Promise 实例的时候养成最终写上 catch 函数是个好习贯,不过代码写多了就轻松糊涂,忘记写 catch。

就此只要你的使用用到非常多的 Promise 实例的话,特别是您在有个别基于 promise 的异步库例如 axios 等自然要小心,因为您不亮堂怎么着时候这几个异步央求会抛出极其而你并不曾拍卖它,所以您最佳增添三个Promise 全局十二分捕获事件 unhandledrejection。

JavaScript

window.add伊夫ntListener("unhandledrejection", function(e){ e.preventDefault() console.log('作者精晓 promise 的谬误了'); console.log(e.reason); return true; }); Promise.reject('promise error'); new Promise((resolve, reject) => { reject('promise error'); }); new Promise((resolve) => { resolve(); }).then(() => { throw 'promise error' });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
window.addEventListener("unhandledrejection", function(e){
  e.preventDefault()
  console.log('我知道 promise 的错误了');
  console.log(e.reason);
  return true;
});
Promise.reject('promise error');
new Promise((resolve, reject) => {
  reject('promise error');
});
new Promise((resolve) => {
  resolve();
}).then(() => {
  throw 'promise error'
});

永利皇宫463手机版 17

本来,假诺您的接纳尚未做 Promise 全局十二分管理的话,那很或许似乎某乎首页那样:

永利皇宫463手机版 18

Vue捕获极度

在小编的品类中就遭遇那样的主题素材,使用了js-tracker如此那般的插件来统一开展全局的要命捕获和日志上报,结果开采我们历来捕获不到Vue组件的特别,查阅资料获知,在Vue中,非常恐怕被Vue自身给try ... catch了,不会传出window.onerror事件触发,那么大家什么把Vue组件中的至极香港作家联谊会见捕获呢?

使用Vue.config.errorHandler这样的Vue大局配置,能够在Vue点名组件的渲染和观赛期间未捕获错误的管理函数。那些管理函数被调用时,可取得错误消息和Vue 实例。

JavaScript

Vue.config.errorHandler = function (err, vm, info) { // handle error // `info` 是 Vue 特定的错误音信,比方错误所在的生命周期钩子 // 只在 2.2.0+ 可用 }

1
2
3
4
5
Vue.config.errorHandler = function (err, vm, info) {
  // handle error
  // `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
  // 只在 2.2.0+ 可用
}

React中,能够行使ErrorBoundary组件包含业务组件的不二法门开展非常捕获,合作React 16.0+新出的componentDidCatch API,能够兑现统一的卓殊捕获和日志上报。

JavaScript

class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } componentDidCatch(error, info) { // Display fallback UI this.setState({ hasError: true }); // You can also log the error to an error reporting service logErrorToMyService(error, info); } render() { if (this.state.hasError) { // You can render any custom fallback UI return <h1>Something went wrong.</h1>; } return this.props.children; } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
 
  componentDidCatch(error, info) {
    // Display fallback UI
    this.setState({ hasError: true });
    // You can also log the error to an error reporting service
    logErrorToMyService(error, info);
  }
 
  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}

行使办法如下:

<ErrorBoundary> <MyWidget /> </ErrorBoundary>

1
2
3
<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

那三个申报情势

督察得到报错音讯之后,接下去就须求将捕捉到的错误音信发送到新闻搜聚平台上,常用的发送格局首要有二种:

  1. 因而 Ajax 发送数据
  2. 动态创建 img 标签的款式
JavaScript

function report(error) { var reportUrl = 'http://xxxx/report'; new
Image().src = reportUrl + 'error=' + error; }

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5a707ba98744f433416112-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5a707ba98744f433416112-2">
2
</div>
<div class="crayon-num" data-line="crayon-5a707ba98744f433416112-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5a707ba98744f433416112-4">
4
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5a707ba98744f433416112-1" class="crayon-line">
function report(error) {
</div>
<div id="crayon-5a707ba98744f433416112-2" class="crayon-line crayon-striped-line">
  var reportUrl = 'http://xxxx/report';
</div>
<div id="crayon-5a707ba98744f433416112-3" class="crayon-line">
  new Image().src = reportUrl + 'error=' + error;
</div>
<div id="crayon-5a707ba98744f433416112-4" class="crayon-line crayon-striped-line">
}
</div>
</div></td>
</tr>
</tbody>
</table>

实例 – 动态制造 img 标签实行反映

性能监察和控制

督察上报常见难题

下述例子作者总体放在自家的 github 上,读者能够自行查阅,前面不再赘言。

JavaScript

git clone cd blog/code/jserror/ npm install

1
2
3
git clone https://github.com/happylindz/blog.git
cd blog/code/jserror/
npm install

最简便易行的习性监察和控制

最分布的质量监察和控制供给则是必要我们总计顾客从初始伏乞页面到持有DOM要素渲染实现的光阴,约等于俗称的首屏加载时间,DOM提供了这一接口,监听documentDOMContentLoaded事件与windowload事件可计算页面首屏加载时间即具备DOM渲染时间:

<!DOCTYPE html> <html> <head> <title></title> <script type="text/javascript"> // 记录页面加载开头时间 var timerStart = Date.now(); </script> <!-- 加载静态财富,如样式能源 --> </head> <body> <!-- 加载静态JS财富 --> <script type="text/javascript"> document.addEventListener('DOMContentLoaded', function() { console.log("DOM 挂载时间: ", Date.now() - timerStart); // 质量日志上报 }); window.add伊芙ntListener('load', function() { console.log("全数能源加载成功时间: ", Date.now()-timerStart); // 质量日志上报 }); </script> </body> </html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html>
<head>
  <title></title>
  <script type="text/javascript">
    // 记录页面加载开始时间
    var timerStart = Date.now();
  </script>
  <!-- 加载静态资源,如样式资源 -->
</head>
<body>
  <!-- 加载静态JS资源 -->
  <script type="text/javascript">
    document.addEventListener('DOMContentLoaded', function() {
      console.log("DOM 挂载时间: ", Date.now() - timerStart);
      // 性能日志上报
    });
    window.addEventListener('load', function() {
      console.log("所有资源加载完成时间: ", Date.now()-timerStart);
      // 性能日志上报
    });
  </script>
</body>
</html>

对此利用框架,如Vue或者说React,组件是异步渲染然后挂载到DOM的,在页面早先化时并没有太多的DOM节点,能够仿照效法下文有关首屏时间访谈自动化的缓慢解决方案来对渲染时间张开贿赂。

Script error 脚本错误是怎么

因为大家在线上的版本,常常做静态财富 CDN 化,那就能够形成大家常访谈的页面跟脚本文件来自分化的域名,那时候若无进展额外的安顿,就能够轻便发生Script error。

永利皇宫463手机版 19

可通过 npm run nocors 查看效果。

Script error 是浏览器在同源战略限制下产生的,浏览器处于对安全性上的虚构,当页面援用非同域名外界脚本文件时中抛出特别的话,此时本页面是从未有过任务知道这么些报错消息的,替代它的是出口 Script error 那样的消息。

永利皇宫463手机版 20

如此那般做的目标是防止数据败露到不安全的域中,举个大致的例证,

JavaScript

<script src="xxxx.com/login.html"></script>

1
<script src="xxxx.com/login.html"></script>

地点咱们并未引进二个 js 文件,而是二个 html,那么些 html 是银行的登入页面,假若您曾经报到了,那 login 页面就能自动跳转到 Welcome xxx...,借使未登陆则跳转到 Please Login...,那么报错也会是 Welcome xxx... is not defined,Please Login... is not defined,通过那些新闻方可肯定多少个顾客是或不是登入他的帐号,给侵袭者提供了丰富方便人民群众的论断渠道,那是一对一不安全的。

介绍完背景后,那么我们理应去解决那几个标题?

第一能够想到的方案料定是同源化战略,将 JS 文件内联到 html 或许放置同域下,就算能简单可行地缓慢解决 script error 难点,可是如此不可能运用好文件缓存和 CDN 的优势,不引进应用。正确的办法应该是从根本上缓慢解决 script error 的一无是处。

performance

但是上述时间的督察过于简短,比如大家想计算文书档案的网络加载耗费时间、分析DOM的耗费时间与渲染DOM的耗费时间,就不太好办到了,所幸的是浏览器提供了window.performance接口,具体可知MDN文档

永利皇宫463手机版 21

少了一些全体浏览器都帮忙window.performance接口,上面来探视在调节台打字与印刷window.performance可以获得些什么:

永利皇宫463手机版 22

能够观看,window,performance主要不外乎有memorynavigationtiming以及timeOriginonresourcetimingbufferfull方法。

  • navigation指标提供了在钦点的岁月段里发生的操作相关音讯,包蕴页面是加载依然刷新、爆发了有一些次重定向等等。
  • timing目的饱含延迟相关的习性消息。那是大家页面加载性能优化须求中至关心注重要反映的连带消息。
  • memoryChrome加上的三个非规范扩大,这几个本性提供了三个可以赢得到宗旨内部存款和储蓄器使用意况的目的。在任何浏览器应该思考到这几个API的合营管理。
  • timeOrigin则赶回质量度量起来时的大运的高精度时间戳。如图所示,正确到了小数点后三人。
  • onresourcetimingbufferfull格局,它是一个在resourcetimingbufferfull事件触发时会被调用的event handler。这些事件当浏览器的能源时间品质缓冲区已满时会触发。可以经过监听这一风波触发来预估页面crash,总结页面crash几率,以便中期的习性优化,如下示例所示:
JavaScript

function buffer_full(event) { console.log("WARNING: Resource Timing
Buffer is FULL!"); performance.setResourceTimingBufferSize(200); }
function init() { // Set a callback if the resource buffer becomes
filled performance.onresourcetimingbufferfull = buffer_full; }
&lt;body onload="init()"&gt;

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f00bfee161383152889-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f00bfee161383152889-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f00bfee161383152889-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f00bfee161383152889-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f00bfee161383152889-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f00bfee161383152889-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f00bfee161383152889-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f00bfee161383152889-8">
8
</div>
<div class="crayon-num" data-line="crayon-5b8f00bfee161383152889-9">
9
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f00bfee161383152889-1" class="crayon-line">
function buffer_full(event) {
</div>
<div id="crayon-5b8f00bfee161383152889-2" class="crayon-line crayon-striped-line">
  console.log(&quot;WARNING: Resource Timing Buffer is FULL!&quot;);
</div>
<div id="crayon-5b8f00bfee161383152889-3" class="crayon-line">
  performance.setResourceTimingBufferSize(200);
</div>
<div id="crayon-5b8f00bfee161383152889-4" class="crayon-line crayon-striped-line">
}
</div>
<div id="crayon-5b8f00bfee161383152889-5" class="crayon-line">
function init() {
</div>
<div id="crayon-5b8f00bfee161383152889-6" class="crayon-line crayon-striped-line">
  // Set a callback if the resource buffer becomes filled
</div>
<div id="crayon-5b8f00bfee161383152889-7" class="crayon-line">
  performance.onresourcetimingbufferfull = buffer_full;
</div>
<div id="crayon-5b8f00bfee161383152889-8" class="crayon-line crayon-striped-line">
}
</div>
<div id="crayon-5b8f00bfee161383152889-9" class="crayon-line">
&lt;body onload=&quot;init()&quot;&gt;
</div>
</div></td>
</tr>
</tbody>
</table>

跨源财富分享机制( COTiggoS )

首先为页面上的 script 标签加多 crossOrigin 属性

JavaScript

// <script> window.onerror = function (msg, url, row, col, error) { console.log('小编清楚不当了,也精通不当音信'); console.log({ msg, url, row, col, error }) return true; }; </script> <script src="" crossorigin></script> // setTimeout(() => { console.log(error); })

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// http://localhost:8080/index.html
<script>
  window.onerror = function (msg, url, row, col, error) {
    console.log('我知道错误了,也知道错误信息');
    console.log({
      msg,  url,  row, col, error
    })
    return true;
  };
</script>
<script src="http://localhost:8081/test.js" crossorigin></script>
 
// http://localhost:8081/test.js
setTimeout(() => {
  console.log(error);
})

当您改改完前端代码后,你还供给相当给后端在响应头里加上 Access-Control-Allow-Origin: localhost:8080,这里本人以 Koa 为例。

JavaScript

const Koa = require('koa'); const path = require('path'); const cors = require('koa-cors'); const app = new Koa(); app.use(cors()); app.use(require('koa-static')(path.resolve(__dirname, './public'))); app.listen(8081, () => { console.log('koa app listening at 8081') });

1
2
3
4
5
6
7
8
9
10
11
const Koa = require('koa');
const path = require('path');
const cors = require('koa-cors');
const app = new Koa();
 
app.use(cors());
app.use(require('koa-static')(path.resolve(__dirname, './public')));
 
app.listen(8081, () => {
  console.log('koa app listening at 8081')
});

永利皇宫463手机版 23

读者可通过 npm run cors 详细的跨域知识作者就不开展了,有意思味能够看看自家事先写的篇章:跨域,你须求理解的全在此间

你感觉那样就完了啊?并从未,上边就说一些 Script error 你不时遇见的点:

咱俩都掌握 JSONP 是用来跨域获取数据的,何况包容性卓绝,在一些利用中还是会动用到,所以你的类型中可能会用这样的代码:

JavaScript

// window.onerror = function (msg, url, row, col, error) { console.log('小编领悟不当了,但不知情不当音讯'); console.log({ msg, url, row, col, error }) return true; }; function jsonpCallback(data) { console.log(data); } const url = ''; const script = document.createElement('script'); script.src = url; document.body.appendChild(script);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// http://localhost:8080/index.html
window.onerror = function (msg, url, row, col, error) {
  console.log('我知道错误了,但不知道错误信息');
  console.log({
    msg,  url,  row, col, error
  })
  return true;
};
function jsonpCallback(data) {
  console.log(data);
}
const url = 'http://localhost:8081/data?callback=jsonpCallback';
const script = document.createElement('script');
script.src = url;
document.body.appendChild(script);

因为重临的音讯会当作脚本文件来施行,一旦回到的台本内容出错了,也是不能捕捉到错误的信息。

永利皇宫463手机版 24

消除办法也轻便,跟此前同一,在加上动态增加脚本的时候增多crossOrigin,何况在后端配上相应的 COMuranoS 字段就可以.

JavaScript

const script = document.createElement('script'); script.crossOrigin = 'anonymous'; script.src = url; document.body.appendChild(script);

1
2
3
4
const script = document.createElement('script');
script.crossOrigin = 'anonymous';
script.src = url;
document.body.appendChild(script);

读者能够因而 npm run jsonp 查看效果

永利皇宫463手机版 25

精晓原理之后您可能会认为不要紧,不正是给各样动态变化的本子添加crossOrigin 字段嘛,不过在实质上工程中,你恐怕是面向广大库来编制程序,比如动用 jQuery,Seajs 或然 webpack 来异步加载脚本,非常多库封装了异步加载脚本的力量,以 jQeury 为例你恐怕是那样来触发异步脚本。

JavaScript

$.ajax({ url: '', dataType: 'jsonp', success: (data) => { console.log(data); } })

1
2
3
4
5
6
7
$.ajax({
  url: 'http://localhost:8081/data',
  dataType: 'jsonp',
  success: (data) => {
    console.log(data);
  }
})

假诺那一个库中从未提供 crossOrigin 的手艺的话(jQuery jsonp 大概有,假装你不清楚),那你只好去修改人家写的源代码了,所以作者那边提供一个思路,便是去威胁document.createElement,一贯自上去为各种动态变化的本子增加 crossOrigin 字段。

JavaScript

document.createElement = (function() { const fn = document.createElement.bind(document); return function(type) { const result = fn(type); if(type === 'script') { result.crossOrigin = 'anonymous'; } return result; } })(); window.onerror = function (msg, url, row, col, error) { console.log('小编知道不当了,也明白不当音讯'); console.log({ msg, url, row, col, error }) return true; }; $.ajax({ url: '', dataType: 'jsonp', success: (data) => { console.log(data); } })

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
document.createElement = (function() {
  const fn = document.createElement.bind(document);
  return function(type) {
    const result = fn(type);
    if(type === 'script') {
      result.crossOrigin = 'anonymous';
    }
    return result;
  }
})();
window.onerror = function (msg, url, row, col, error) {
  console.log('我知道错误了,也知道错误信息');
  console.log({
    msg,  url,  row, col, error
  })
  return true;
};
$.ajax({
  url: 'http://localhost:8081/data',
  dataType: 'jsonp',
  success: (data) => {
    console.log(data);
  }
})

效用也是一样的,读者能够经过 npm run jsonpjq 来查看效果:

永利皇宫463手机版 26

这么重写 createElement 理论上没什么难题,不过侵犯了原本的代码,不保障一定不会出错,在工程上恐怕必要多尝试下看看再采用,恐怕存在包容性上难点,假诺你认为会冒出哪些难题的话也款待留言研究下。

至于 Script error 的主题材料就写到这里,要是你领会了地点的剧情,基本上绝超过八分之四的 Script error 都能化解。

测算网址质量

使用performancetiming属性,能够得到页面性能相关的数目,这里在数不清稿子都有关联有关使用window.performance.timing笔录页面质量的稿子,比方alloyteam团组织写的初探 performance – 监察和控制网页与程序质量,对于timing的各种品质含义,能够依赖摘自此文的下图驾驭,以下代码摘自此文作为计量网址质量的工具函数参照他事他说加以考察:

永利皇宫463手机版 27

JavaScript

// 获取 performance 数据 var performance = { // memory 是非规范属性,只在 Chrome 有 // 财富难点:笔者有多少内部存款和储蓄器 memory: { usedJSHeapSize: 16一千00, // JS 对象(包罗V8引擎内部对象)占用的内部存款和储蓄器,一定小于 totalJSHeapSize totalJSHeapSize: 35一千00, // 可使用的内部存款和储蓄器 jsHeapSizeLimit: 792000000 // 内部存款和储蓄器大小限制 }, // 医学难点:小编从何地来? navigation: { redirectCount: 0, // 假使有重定向的话,页面通过几遍重定向跳转而来 type: 0 // 0 即 TYPE_NAVIGATENEXT 正常走入的页面(非刷新、非重定向等) // 1 即 TYPE_RELOAD 通过 window.location.reload() 刷新的页面 // 2 即 TYPE_BACK_FOTucsonWA奇骏D 通过浏览器的上进后退开关步向的页面(历史记录) // 255 即 TYPE_UNDEFINED 非上述办法步入的页面 }, timing: { // 在同多个浏览器上下文中,前贰个网页(与如今页面不自然同域)unload 的时刻戳,如果无前贰个网页 unload ,则与 fetchStart 值相等 navigationStart: 144111269一九三二, // 前两个网页(与近年来页面同域)unload 的光阴戳,如若无前贰个网页 unload 只怕前三个网页与当前页面分裂域,则值为 0 unload伊夫ntStart: 0, // 和 unloadEventStart 相对应,重返前贰个网页 unload 事件绑定的回调函数试行达成的年月戳 unloadEventEnd: 0, // 第二个HTTP 重定向产生时的小时。有跳转且是同域名内的重定向才算,不然值为 0 redirectStart: 0, // 最终一个 HTTP 重定向实现时的时刻。有跳转且是同域名内部的重定向才算,否则值为 0 redirectEnd: 0, // 浏览器策动好使用 HTTP 央求抓取文书档案的光阴,那爆发在检查本地缓存以前 fetchStart: 1441112692155, // DNS 域名询问初步的年华,要是采取了地点缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等 domainLookupStart: 1441112692155, // DNS 域名询问达成的岁月,倘若使用了本土缓存(即无 DNS 查询)或长久连接,则与 fetchStart 值相等 domainLookupEnd: 1441112692155, // HTTP(TCP) 最初另起炉灶连接的时刻,要是是细水长流连接,则与 fetchStart 值相等 // 注意要是在传输层发生了不当且重新成立连接,则这里展现的是新营造的连日起来的时光 connectStart: 1441112692155, // HTTP(TCP) 实现创立连接的年华(达成握手),倘使是长久连接,则与 fetchStart 值相等 // 注意假如在传输层爆发了错误且重新确立连接,则这里展示的是新构建的延续成功的时辰// 注意这里握手甘休,包蕴平安连接建构达成、SOCKS 授权通过 connectEnd: 1441112692155, // HTTPS 连接起来的时日,如若不是平安连接,则值为 0 secureConnectionStart: 0, // HTTP 诉求读取真实文书档案最初的光阴(达成创建连接),包罗从地方读取缓存 // 连接错误重连时,这里显得的也是新确立连接的大运 requestStart: 1441112692158, // HTTP 初始收受响应的岁月(获取到第三个字节),包蕴从地点读取缓存 responseStart: 1441112692686, // HTTP 响应全体接收完结的时刻(获取到最后叁个字节),包蕴从本土读取缓存 responseEnd: 1441112692687, // 初叶解析渲染 DOM 树的日子,此时 Document.readyState 变为 loading,并将抛出 readystatechange 相关事件 domLoading: 1441112692690, // 完毕分析 DOM 树的时光,Document.readyState 变为 interactive,并将抛出 readystatechange 相关事件 // 注意只是 DOM 树剖析落成,这时候并不曾从头加载网页内的财富 domInteractive: 1441112693093, // DOM 分析达成后,网页国内资本源加载先导的小时 // 在 DOMContentLoaded 事件抛出前发生 domContentLoadedEventStart: 1441112693093, // DOM 分析达成后,网页国内资本源加载成功的时刻(如 JS 脚本加载施行完成) domContentLoaded伊芙ntEnd: 1441112693101, // DOM 树分析完结,且资源也准备妥帖的日子,Document.readyState 变为 complete,并将抛出 readystatechange 相关事件 domComplete: 1441112693214, // load 事件发送给文书档案,也即 load 回调函数开端试行的年华 // 注意若无绑定 load 事件,值为 0 load伊夫ntStart: 1441112693214, // load 事件的回调函数施行达成的岁月 loadEventEnd: 1441112693215 // 字母顺序 // connectEnd: 1441112692155, // connectStart: 1441112692155, // domComplete: 1441112693214, // domContentLoaded伊芙ntEnd: 1441112693101, // domContentLoadedEventStart: 1441112693093, // domInteractive: 1441112693093, // domLoading: 1441112692690, // domainLookupEnd: 1441112692155, // domainLookupStart: 1441112692155, // fetchStart: 1441112692155, // load伊夫ntEnd: 1441112693215, // loadEventStart: 1441112693214, // navigationStart: 144111269一九三一, // redirectEnd: 0, // redirectStart: 0, // requestStart: 1441112692158, // responseEnd: 1441112692687, // responseStart: 1441112692686, // secureConnectionStart: 0, // unloadEventEnd: 0, // unload伊芙ntStart: 0 } };

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// 获取 performance 数据
var performance = {  
    // memory 是非标准属性,只在 Chrome 有
    // 财富问题:我有多少内存
    memory: {
        usedJSHeapSize:  16100000, // JS 对象(包括V8引擎内部对象)占用的内存,一定小于 totalJSHeapSize
        totalJSHeapSize: 35100000, // 可使用的内存
        jsHeapSizeLimit: 793000000 // 内存大小限制
    },
    //  哲学问题:我从哪里来?
    navigation: {
        redirectCount: 0, // 如果有重定向的话,页面通过几次重定向跳转而来
        type: 0           // 0   即 TYPE_NAVIGATENEXT 正常进入的页面(非刷新、非重定向等)
                          // 1   即 TYPE_RELOAD       通过 window.location.reload() 刷新的页面
                          // 2   即 TYPE_BACK_FORWARD 通过浏览器的前进后退按钮进入的页面(历史记录)
                          // 255 即 TYPE_UNDEFINED    非以上方式进入的页面
    },
    timing: {
        // 在同一个浏览器上下文中,前一个网页(与当前页面不一定同域)unload 的时间戳,如果无前一个网页 unload ,则与 fetchStart 值相等
        navigationStart: 1441112691935,
        // 前一个网页(与当前页面同域)unload 的时间戳,如果无前一个网页 unload 或者前一个网页与当前页面不同域,则值为 0
        unloadEventStart: 0,
        // 和 unloadEventStart 相对应,返回前一个网页 unload 事件绑定的回调函数执行完毕的时间戳
        unloadEventEnd: 0,
        // 第一个 HTTP 重定向发生时的时间。有跳转且是同域名内的重定向才算,否则值为 0
        redirectStart: 0,
        // 最后一个 HTTP 重定向完成时的时间。有跳转且是同域名内部的重定向才算,否则值为 0
        redirectEnd: 0,
        // 浏览器准备好使用 HTTP 请求抓取文档的时间,这发生在检查本地缓存之前
        fetchStart: 1441112692155,
        // DNS 域名查询开始的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
        domainLookupStart: 1441112692155,
        // DNS 域名查询完成的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
        domainLookupEnd: 1441112692155,
        // HTTP(TCP) 开始建立连接的时间,如果是持久连接,则与 fetchStart 值相等
        // 注意如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接开始的时间
        connectStart: 1441112692155,
        // HTTP(TCP) 完成建立连接的时间(完成握手),如果是持久连接,则与 fetchStart 值相等
        // 注意如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接完成的时间
        // 注意这里握手结束,包括安全连接建立完成、SOCKS 授权通过
        connectEnd: 1441112692155,
        // HTTPS 连接开始的时间,如果不是安全连接,则值为 0
        secureConnectionStart: 0,
        // HTTP 请求读取真实文档开始的时间(完成建立连接),包括从本地读取缓存
        // 连接错误重连时,这里显示的也是新建立连接的时间
        requestStart: 1441112692158,
        // HTTP 开始接收响应的时间(获取到第一个字节),包括从本地读取缓存
        responseStart: 1441112692686,
        // HTTP 响应全部接收完成的时间(获取到最后一个字节),包括从本地读取缓存
        responseEnd: 1441112692687,
        // 开始解析渲染 DOM 树的时间,此时 Document.readyState 变为 loading,并将抛出 readystatechange 相关事件
        domLoading: 1441112692690,
        // 完成解析 DOM 树的时间,Document.readyState 变为 interactive,并将抛出 readystatechange 相关事件
        // 注意只是 DOM 树解析完成,这时候并没有开始加载网页内的资源
        domInteractive: 1441112693093,
        // DOM 解析完成后,网页内资源加载开始的时间
        // 在 DOMContentLoaded 事件抛出前发生
        domContentLoadedEventStart: 1441112693093,
        // DOM 解析完成后,网页内资源加载完成的时间(如 JS 脚本加载执行完毕)
        domContentLoadedEventEnd: 1441112693101,
        // DOM 树解析完成,且资源也准备就绪的时间,Document.readyState 变为 complete,并将抛出 readystatechange 相关事件
        domComplete: 1441112693214,
        // load 事件发送给文档,也即 load 回调函数开始执行的时间
        // 注意如果没有绑定 load 事件,值为 0
        loadEventStart: 1441112693214,
        // load 事件的回调函数执行完毕的时间
        loadEventEnd: 1441112693215
        // 字母顺序
        // connectEnd: 1441112692155,
        // connectStart: 1441112692155,
        // domComplete: 1441112693214,
        // domContentLoadedEventEnd: 1441112693101,
        // domContentLoadedEventStart: 1441112693093,
        // domInteractive: 1441112693093,
        // domLoading: 1441112692690,
        // domainLookupEnd: 1441112692155,
        // domainLookupStart: 1441112692155,
        // fetchStart: 1441112692155,
        // loadEventEnd: 1441112693215,
        // loadEventStart: 1441112693214,
        // navigationStart: 1441112691935,
        // redirectEnd: 0,
        // redirectStart: 0,
        // requestStart: 1441112692158,
        // responseEnd: 1441112692687,
        // responseStart: 1441112692686,
        // secureConnectionStart: 0,
        // unloadEventEnd: 0,
        // unloadEventStart: 0
    }
};

 

JavaScript

// 计算加载时间 function getPerformanceTiming() { var performance = window.performance; if (!performance) { // 当前浏览器不扶助console.log('你的浏览器不援助 performance 接口'); return; } var t = performance.timing; var times = {}; //【重要】页面加载成功的时日 //【原因】那差十分的少代表了客商等待页面可用的光阴 times.loadPage = t.loadEventEnd - t.navigationStart; //【主要】分析 DOM 树结构的时光 //【原因】反省下您的 DOM 树嵌套是还是不是太多了! times.domReady = t.domComplete - t.responseEnd; //【主要】重定向的年月 //【原因】拒绝重定向!譬如, 就不应该写成 times.redirect = t.redirectEnd - t.redirectStart; //【首要】DNS 查询时间 //【原因】DNS 预加载做了么?页面内是否运用了太多分裂的域名导致域名查询的岁月太长? // 可应用 HTML5 Prefetch 预查询 DNS ,见:[HTML5 prefetch]() times.lookupDomain = t.domainLookupEnd - t.domainLookupStart; //【首要】读取页面第叁个字节的时光 //【原因】那可以明白为顾客得到您的资源占用的流年,加异地机房了么,加CDN 管理了么?加带宽了么?加 CPU 运算速度了么? // TTFB 即 Time To First Byte 的情致 // 维基百科: times.ttfb = t.responseStart - t.navigationStart; //【首要】内容加载成功的年月 //【原因】页面内容通过 gzip 压缩了么,静态能源 css/js 等压缩了么? times.request = t.responseEnd - t.requestStart; //【主要】实行 onload 回调函数的时间 //【原因】是不是太多不须要的操作都放到 onload 回调函数里实行了,思量过延迟加载、按需加载的计谋么? times.load伊夫nt = t.loadEventEnd - t.loadEventStart; // DNS 缓存时间 times.appcache = t.domainLookupStart - t.fetchStart; // 卸载页面包车型客车光阴 times.unloadEvent = t.unloadEventEnd - t.unloadEventStart; // TCP 创建连接成功握手的年华 times.connect = t.connectEnd - t.connectStart; return times; }

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
31
32
33
34
35
36
37
38
39
40
41
42
// 计算加载时间
function getPerformanceTiming() {
    var performance = window.performance;
    if (!performance) {
        // 当前浏览器不支持
        console.log('你的浏览器不支持 performance 接口');
        return;
    }
    var t = performance.timing;
    var times = {};
    //【重要】页面加载完成的时间
    //【原因】这几乎代表了用户等待页面可用的时间
    times.loadPage = t.loadEventEnd - t.navigationStart;
    //【重要】解析 DOM 树结构的时间
    //【原因】反省下你的 DOM 树嵌套是不是太多了!
    times.domReady = t.domComplete - t.responseEnd;
    //【重要】重定向的时间
    //【原因】拒绝重定向!比如,http://example.com/ 就不该写成 http://example.com
    times.redirect = t.redirectEnd - t.redirectStart;
    //【重要】DNS 查询时间
    //【原因】DNS 预加载做了么?页面内是不是使用了太多不同的域名导致域名查询的时间太长?
    // 可使用 HTML5 Prefetch 预查询 DNS ,见:[HTML5 prefetch](http://segmentfault.com/a/1190000000633364)            
    times.lookupDomain = t.domainLookupEnd - t.domainLookupStart;
    //【重要】读取页面第一个字节的时间
    //【原因】这可以理解为用户拿到你的资源占用的时间,加异地机房了么,加CDN 处理了么?加带宽了么?加 CPU 运算速度了么?
    // TTFB 即 Time To First Byte 的意思
    // 维基百科:https://en.wikipedia.org/wiki/Time_To_First_Byte
    times.ttfb = t.responseStart - t.navigationStart;
    //【重要】内容加载完成的时间
    //【原因】页面内容经过 gzip 压缩了么,静态资源 css/js 等压缩了么?
    times.request = t.responseEnd - t.requestStart;
    //【重要】执行 onload 回调函数的时间
    //【原因】是否太多不必要的操作都放到 onload 回调函数里执行了,考虑过延迟加载、按需加载的策略么?
    times.loadEvent = t.loadEventEnd - t.loadEventStart;
    // DNS 缓存时间
    times.appcache = t.domainLookupStart - t.fetchStart;
    // 卸载页面的时间
    times.unloadEvent = t.unloadEventEnd - t.unloadEventStart;
    // TCP 建立连接完成握手的时间
    times.connect = t.connectEnd - t.connectStart;
    return times;
}

window.onerror 能还是不可能捕获 iframe 的一无可取

当您的页面有使用 iframe 的时候,你供给对您引进的 iframe 做老大监察和控制的拍卖,不然如若你引进的 iframe 页面出现了难题,你的主站呈现不出来,而你却浑然不知。

首先供给重申,父窗口直接利用 window.onerror 是无力回天直接破获,假令你想要捕获 iframe 的百般的话,有分好二种情状。

只要你的 iframe 页面和您的主站是同域名的话,直接给 iframe 增多 onerror 事件就能够。

JavaScript

<iframe src="./iframe.html" frameborder="0"></iframe> <script> window.frames[0].onerror = function (msg, url, row, col, error) { console.log('我精晓 iframe 的荒唐了,也知道不当消息'); console.log({ msg, url, row, col, error }) return true; }; </script>

1
2
3
4
5
6
7
8
9
10
<iframe src="./iframe.html" frameborder="0"></iframe>
<script>
  window.frames[0].onerror = function (msg, url, row, col, error) {
    console.log('我知道 iframe 的错误了,也知道错误信息');
    console.log({
      msg,  url,  row, col, error
    })
    return true;
  };
</script>

读者能够透过 npm run iframe 查看效果:

永利皇宫463手机版 28

假若您置于的 iframe 页面和您的主站不是同个域名的,可是 iframe 内容不属于第三方,是您可以决定的,那么能够通过与 iframe 通讯的措施将不胜新闻抛给主站接收。与 iframe 通信的不二等秘书技有比较多,常用的如:postMessage,hash 或然 name 字段跨域等等,这里就不开展了,感兴趣的话能够看:跨域,你须要领会的全在此处

只假如非同域且网址不受本人说了算以来,除了通过调节台见到详细的错误音讯外,不能捕获,那是出于安全性的思量,你引进了多个百度首页,人家页面报出的错误凭啥让你去监督呢,那会引出比较多安全性的难题。

日志上报

减弱代码怎么样定位到脚本极度地方

线上的代码大致都经过了削减管理,几10个文本打包成了多少个并丑化代码,当咱们接受 a is not defined 的时候,大家历来不掌握那个变量 a 究竟是哪些意思,此时报错的一无所长日志鲜明是无效的。

首先想到的主意是接纳 sourcemap 定位到错误代码的具体地点,详细内容能够参照:Sourcemap 定位脚本错误

除此以外也足以经过在卷入的时候,在各类合併的文本之间增多几行空格,并相应增加一些批注,那样在定位难点的时候很轻松能够明白是哪个文件报的失实,然后再通过有个别重要词的研究,能够便捷地稳住到难点的所在地方。

独立的日志域名

对于日记上报利用单独的日记域名的目标是制止对业务形成影响。其一,对于服务器来讲,大家自然不愿意占用专门的职业服务器的揣摸能源,也不希望过多的日记在作业服务器堆集,变成专门的学业服务器的仓库储存空间远远不足的情形。其二,大家清楚在页面初阶化的历程中,会对页面加载时间、PV、UV等数据开展报告,这几个报告央浼会和加载业务数据差不离是同期刻产生,而浏览器日常会对同三个域名的伏乞量有并发数的限定,如Chrome会有对并发数为6个的界定。由此必要对日记系统独立设定域名,最小化对页面加载品质形成的震慑。

募集格外音讯量太多,怎么做

假若您的网址采访量十分大,如若网页的 PV 有 1kw,那么一个决然的一无所能发送的音信就有 1kw 条,大家能够给网址设置二个收集率:

JavaScript

Reporter.send = function(data) { // 只搜罗 三分之一 if(Math.random() < 0.3) { send(data) // 上报错误音信 } }

1
2
3
4
5
6
Reporter.send = function(data) {
  // 只采集 30%
  if(Math.random() < 0.3) {
    send(data)      // 上报错误信息
  }
}

本条搜聚率能够通过实际实在的动静来设定,方法多种化,能够行使二个随意数,也足以切切实实依据客户的一些特征来打开推断。

上边大致是本人对后面一个代码监控的一部分明亮,聊起来轻巧,不过假使在工程化应用,难免须要思考到宽容性等种种难点,读者能够通过本人的具体情形举行调度,前端代码非凡监控对于大家的网址的牢固性起着主要的效应。假使文中颇负不对的地点,还望指正。

跨域的标题

对于单身的日志域名,鲜明会提到到跨域的难题,选拔的减轻方案日常有以下二种:

  • 一种是组织空的Image指标的艺术,其缘由是伸手图片并不涉及到跨域的主题材料;
JavaScript

var url = 'xxx'; new Image().src = url;

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f00bfee170123843269-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f00bfee170123843269-2">
2
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f00bfee170123843269-1" class="crayon-line">
var url = 'xxx';
</div>
<div id="crayon-5b8f00bfee170123843269-2" class="crayon-line crayon-striped-line">
new Image().src = url;
</div>
</div></td>
</tr>
</tbody>
</table>
  • 利用Ajax汇报日志,必需对日记服务器接口开启跨域央浼底部Access-Control-Allow-Origin:*,这里Ajax就并不强制行使GET呼吁了,就可以战胜URL长度限制的题目。
JavaScript

if (XMLHttpRequest) { var xhr = new XMLHttpRequest();
xhr.open('post', 'https://log.xxx.com', true); //
上报给node中间层处理 xhr.setRequestHeader('Content-Type',
'application/json'); // 设置请求头
xhr.send(JSON.stringify(errorObj)); // 发送参数 }

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f00bfee174544186263-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f00bfee174544186263-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f00bfee174544186263-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f00bfee174544186263-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f00bfee174544186263-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f00bfee174544186263-6">
6
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f00bfee174544186263-1" class="crayon-line">
if (XMLHttpRequest) {
</div>
<div id="crayon-5b8f00bfee174544186263-2" class="crayon-line crayon-striped-line">
  var xhr = new XMLHttpRequest();
</div>
<div id="crayon-5b8f00bfee174544186263-3" class="crayon-line">
  xhr.open('post', 'https://log.xxx.com', true); // 上报给node中间层处理
</div>
<div id="crayon-5b8f00bfee174544186263-4" class="crayon-line crayon-striped-line">
  xhr.setRequestHeader('Content-Type', 'application/json'); // 设置请求头
</div>
<div id="crayon-5b8f00bfee174544186263-5" class="crayon-line">
  xhr.send(JSON.stringify(errorObj)); // 发送参数
</div>
<div id="crayon-5b8f00bfee174544186263-6" class="crayon-line crayon-striped-line">
}
</div>
</div></td>
</tr>
</tbody>
</table>

在自家的种类中选用的是第一种的法子,也正是结构空的Image永利皇宫463手机版,指标,可是大家领略对于GET呼吁会有长度的限制,必要有限支撑的是央求的长度不会超越阈值。

参考小说

  • 脚本错误量极致优化-监察和控制上报与Script error
  • 前端代码分外日志收罗与监察和控制
  • 前者法力堂——格外不只有是try/catch

    1 赞 2 收藏 评论

节约响应宗旨

对于大家陈诉日志,其实对于客商端的话,并没有须求思虑上报的结果,以至对于举报失利,大家也无需在前端做其他交互,所以报告来说,其实选用HEAD呼吁就够了,接口再次来到空的结果,最大地收缩上报日志变成的能源浪费。

集合反映

好像于Coca Cola图的研商,若是大家的选拔须求申报的日记数量过多,那么有须要合併日志实行合併的反映。

减轻方案能够是尝试在顾客距离页面大概零部件销毁时发送一个异步的POST央浼来开展反映,不过尝试在卸载(unload)文书档案在此之前向web服务器发送数据。保险在文书档案卸载时期发送数据一贯是叁个不便。因为顾客代理经常会忽视在卸载事件管理器中生出的异步XMLHttpRequest,因为那时候早就能够跳转到下二个页面。所以这里是必需安装为共同的XMLHttpRequest请求吗?

JavaScript

window.addEventListener('unload', logData, false); function logData() { var client = new XMLHttpRequest(); client.open("POST", "/log", false); // 第八个参数表明是一同的 xhr client.setRequestHeader("Content-Type", "text/plain;charset=UTF-8"); client.send(analyticsData); }

1
2
3
4
5
6
7
8
window.addEventListener('unload', logData, false);
 
function logData() {
    var client = new XMLHttpRequest();
    client.open("POST", "/log", false); // 第三个参数表明是同步的 xhr
    client.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
    client.send(analyticsData);
}

使用同步的艺术势必会对客户体验产生影响,以至会让客户感受到浏览器卡死感觉,对于产品来说,体验特别不佳,通过查阅MDN文档,能够动用sendBeacon()方法,将会使顾客代理在有空申时异步地向服务器发送数据,同临时间不会推迟页面包车型客车卸载或影响下一导航的载入质量。那就缓慢解决了付出分析数据时的兼具的题材:使它可信赖,异步何况不会潜移默化下一页面包车型客车加载。别的,代码实际上还要比任何技能轻松!

下边包车型大巴事例体现了三个答辩上的计算代码情势——通过选择sendBeacon()措施向服务器发送数据。

JavaScript

window.addEventListener('unload', logData, false); function logData() { navigator.sendBeacon("/log", analyticsData); }

1
2
3
4
5
window.addEventListener('unload', logData, false);
 
function logData() {
    navigator.sendBeacon("/log", analyticsData);
}

小结

用作前端开拓者来讲,要对成品保持敬畏之心,时刻保持对质量追求极致,对那七个不可忍受的千姿百态。前端的属性监察和控制与那多少个申报显得尤其关键。

代码难免有标题,对于极其能够行使window.onerror或者addEventListener的点子丰盛全局的可怜捕获侦听函数,但或然应用这种措施不可能精确捕获到错误:对于跨域的本子,须求对script标签扩大一个crossorigin=”anonymous”;对于生产景况打包的代码,不可能正确定位到特别爆发的行数,能够利用source-map来消除;而对此利用框架的气象,需求在框架统一的可怜捕获处埋点。

而对此品质的监察和控制,所幸的是浏览器提供了window.performance API,通过那几个API,相当的轻巧地获取到当前页面质量相关的数量。

而这几个特别和质量数据怎么着反馈呢?平时说来,为了制止对职业发生的震慑,会单独创造日志服务器和日志域名,但对于差别的域名,又会发出跨域的标题。大家得以通过结构空的Image指标来缓慢解决,亦恐怕通过设定跨域央求尾部Access-Control-Allow-Origin:*来缓和。另外,假如反映的性质和日志数据高频触发,则能够在页面unload时统一申报,而unload时的异步诉求又或然会被浏览器所忽略,且不能够改为联合须要。此时navigator.sendBeacon API可算帮了大家大忙,它可用以通过HTTP将少许多少异步传输到Web服务器。而忽略页面unload时的震慑。

1 赞 1 收藏 评论

永利皇宫463手机版 29

编辑:关于计算机 本文来源:前端代码异常监控实战,前端性能与异常上报

关键词: