>

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

- 编辑:至尊游戏网站 -

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

前面一脾质量与充足申报

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>

图片 1

在对脚本错误进行举报以前,大家需求对那些举办拍卖,程序须要先感知到脚本错误的发生,然后再谈至极申报。

脚本错误通常分为三种:语法错误,运转时不当。

上面就探讨三种极其监控的管理情势:

老大捕获方法

try-catch 至极管理

try-catch 在大家的代码中经常见到,通过给代码块实行 try-catch 举办打包后,今世码块产生出错开上下班时间 catch 将能捕捉到错误的音讯,页面也将得以继续施行。

不过 try-catch 管理非常的力量轻巧,只好捕获捉到运维时非异步错误,对于语法错误和异步错误就显得力无法支,捕捉不到。

全局捕获

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

JavaScript

window.onerror = function(errorMessage, scriptU翼虎I, lineNo, columnNo, error) { console.log('errorMessage: ' + errorMessage); // 至极音讯console.log('scriptU汉兰达I: ' + scriptUSportageI); // 相当文件路径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('这是一个错误');

图片 2

通过window.onerror事件,能够获取具体的特别消息、极度文件的URAV4L、相当的行号与列号及那多少个的旅舍新闻,再捕获万分后,统蒸蒸日上申报至大家的日记服务器。

亦或是,通过window.addEventListener主意来开展丰裕申报,道理同理:

JavaScript

window.add伊夫ntListener('error', function() { console.log(error); // ... // 相当上报 }); throw new Error('那是叁个谬误');

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

图片 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);
}

图片 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);
}

图片 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);
}

图片 6

唯有你在 setTimeout 函数中再套上大器晚成层 try-catch,不然就不只怕感知到其错误,但那样代码写起来相比较啰嗦。

跨域脚本不可能正确捕获非常

一般来说状态下,大家会把静态能源,如JavaScript本子放到专门的静态财富服务器,亦也许CDN,看以下例子:

<!DOCTYPE html> <html> <head> <title></title> </head> <body> <script type="text/javascript"> // 在index.html window.onerror = function(errorMessage, scriptUEvoqueI, lineNo, columnNo, error) { console.log('errorMessage: ' + errorMessage); // 卓殊信息console.log('scriptUPRADOI: ' + scriptU保时捷911I); // 十分文件路径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('这是一个错误');

图片 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

图片 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;
});

图片 9

但是 window.onerror 对于语法错误依旧爱莫能助,所以我们在写代码的时候要尽量制止语法错误的,然而貌似那样的荒诞会使得整个页面崩溃,依然相比易于能够察觉到的。

在骨子里的利用进度中,onerror 首假如来捕获预料之外的荒诞,而 try-catch 则是用来在可预激情形下监察和控制特定的大错特错,两个结合使用进一步高效。

亟待小心的是,window.onerror 函数独有在回到 true 的时候,分外才不会提高抛出,不然固然是知道特别的产生调整台依然会显得 Uncaught Error: xxxxx。

图片 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">

图片 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="">

图片 12

这一点知识或然必要知道,要不然客商访谈网址,图片 CDN 无法服务,图片加载不出去而开辟职员未有意识就难堪了。

sourceMap

万般在生养意况下的代码是由此webpack打包后回降混淆的代码,所以大家恐怕会高出这么的标题,如图所示:

图片 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,那样浏览器就可以知道牢固到实际错误的岗位:

图片 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;

日常来讲图所示,我们已经得以看来,在服务端已经打响分析出了具体错误的行号、列号,我们能够透过日记的章程开展记录,达到了前面二个卓殊监察和控制的目标。

图片 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'
});

图片 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'
});

图片 17

自然,即便你的利用尚未做 Promise 全局十三分管理的话,那很大概就好像某乎首页那样:

图片 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.addEventListener('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。

图片 19

可通过 npm run nocors 查看效果。

Script error 是浏览器在同源战略限制下发出的,浏览器处于对安全性上的设想,当页面引用非同域名外部脚本文件时中抛出特别的话,此时本页面是不曾职责知道那些报错新闻的,代替他的是出口 Script error 那样的音讯。

图片 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文档

图片 21

少了一些具有浏览器都帮助window.performance接口,上边来会见在调整台打字与印刷window.performance能够拿走些什么:

图片 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>

跨源能源分享机制( CO智跑S )

第黄金年代为页面上的 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')
});

图片 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);

因为重回的音讯会充当脚本文件来实践,风流倜傥旦回到的剧本内容出错了,也是爱莫能助捕捉到错误的新闻。

图片 24

解决办法也简单,跟从前同样,在抬高动态增进脚本的时候增加crossOrigin,并且在后端配上相应的 CO奥迪Q7S 字段就可以.

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 查看效果

图片 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 来查看效果:

图片 26

如此重写 createElement 理论上没什么难点,然而侵略了本来的代码,不保障一定不会出错,在工程上可能供给多品尝下看看再使用,恐怕存在包容性上难点,假诺你感觉会产出什么样难题的话也接待留言斟酌下。

有关 Script error 的题目就写到这里,要是你领会了上边的内容,基本上绝大多数的 Script error 都能缓慢解决。

估测计算网址质量

使用performancetiming品质,能够获得页面品质相关的数额,这里在重重篇章都有提到关于选择window.performance.timing笔录页面质量的篇章,举例alloyteam集体写的初探 performance – 监察和控制网页与程序质量,对于timing的每一项品质含义,能够看重摘自此文的下图通晓,以下代码摘自此文作为计量网站品质的工具函数参谋:

图片 27

JavaScript

// 获取 performance 数据 var performance = { // memory 是非标准化准属性,只在 Chrome 有 // 能源难题:笔者有稍许内部存款和储蓄器 memory: { usedJSHeapSize: 16一千00, // JS 对象(包涵V8引擎内部对象)占用的内部存款和储蓄器,一定小于 totalJSHeapSize totalJSHeapSize: 35一千00, // 可选拔的内部存款和储蓄器 jsHeapSizeLimit: 79两千000 // 内部存款和储蓄器大小限制 }, // 艺术学难点:小编从哪个地方来? navigation: { redirectCount: 0, // 假设有重定向的话,页面通过一次重定向跳转而来 type: 0 // 0 即 TYPE_NAVIGATENEXT 通常踏向的页面(非刷新、非重定向等) // 1 即 TYPE_RELOAD 通过 window.location.reload() 刷新的页面 // 2 即 TYPE_BACK_FO奥迪Q5WA福特ExplorerD 通过浏览器的提升后退开关步入的页面(历史记录) // 255 即 TYPE_UNDEFINED 非以上办法踏向的页面 }, timing: { // 在同贰个浏览器上下文中,前贰个网页(与当前页面不必然同域)unload 的日子戳,借使无前多少个网页 unload ,则与 fetchStart 值相等 navigationStart: 144111269一九三三, // 前二个网页(与日前页面同域)unload 的年华戳,借使无前贰个网页 unload 或许前三个网页与当下页面分化域,则值为 0 unloadEventStart: 0, // 和 unloadEventStart 相对应,重临前多个网页 unload 事件绑定的回调函数试行实现的小时戳 unload伊芙ntEnd: 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, // domContentLoaded伊夫ntStart: 1441112693093, // domInteractive: 1441112693093, // domLoading: 1441112692690, // domainLookupEnd: 1441112692155, // domainLookupStart: 1441112692155, // fetchStart: 1441112692155, // loadEventEnd: 1441112693215, // load伊夫ntStart: 1441112693214, // navigationStart: 144111269一九三三, // redirectEnd: 0, // redirectStart: 0, // requestStart: 1441112692158, // responseEnd: 1441112692687, // responseStart: 1441112692686, // secureConnectionStart: 0, // unloadEventEnd: 0, // unloadEventStart: 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.loadEvent = t.load伊芙ntEnd - t.loadEventStart; // DNS 缓存时间 times.appcache = t.domainLookupStart - t.fetchStart; // 卸载页面包车型客车小运 times.unloadEvent = t.unload伊夫ntEnd - 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 查看效果:

图片 28

纵然你置于的 iframe 页面和你的主站不是同个域名的,但是 iframe 内容不属于第三方,是你能够调节的,那么能够经过与 iframe 通讯的格局将极度音信抛给主站接收。与 iframe 通讯的法子有广大,常用的如:postMessage,hash 大概 name 字段跨域等等,这里就不进行了,感兴趣的话能够看:跨域,你须要精通的全在这里间

假定是非同域且网址不受本身调整以来,除了通过调整台看见详细的错误音信外,不可能捕获,那是由于安全性的虚拟,你引进了八个百度首页,人家页面报出的荒谬凭啥令你去监督呢,那会引出非常多安全性的难点。

日志上报

削减代码如何定位到脚本非常地点

线上的代码大约都因而了压缩处理,几12个文件打包成了贰个并丑化代码,当我们收到 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对象,可是大家知道对于GET伸手会有长度的界定,必要保证的是呼吁的尺寸不会超过阈值。

参照小说

  • 脚本错误量极致优化-监察和控制上报与Script error
  • 前边三个代码格外日志搜罗与监督
  • 前端法力堂——卓殊不独有是try/catch

    1 赞 2 收藏 评论

节省响应大旨

对于大家申报日志,其实对于客商端的话,并无需思虑上报的结果,以至对于报告战败,大家也无需在前面多少个做任何交互,所以报告来讲,其实使用HEAD恳求就够了,接口再次来到空的结果,最大地减弱上报日志形成的能源浪费。

合併反映

看似于Pepsi-Cola图的观念,如若大家的施用供给上报的日志数量比相当多,那么有不可缺少合併日志进行联合的上报。

杀鸡取卵方案得以是尝尝在客户间距页面大概零部件销毁时发送多少个异步的POST恳请来进展上报,可是尝试在卸载(unload)文书档案此前向web服务器发送数据。保险在文书档案卸载时期发送数据一贯是一个辛劳。因为客商代理平常会忽略在卸载事件管理器中爆发的异步XMLHttpRequest,因为此时早就能跳转到下多少个页面。所以那边是必得安装为联合的XMLHttpRequest请求吗?

JavaScript

window.add伊夫ntListener('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 收藏 评论

图片 29

本文由技术教程发布,转载请注明来源:前端代码异常监控实战,前端性能与异常上报