错误监控及定位

455 阅读4分钟

JS异常

方案描述
window.onerrordeveloper.mozilla.org/zh-CN/docs/…
window.addEventListener('error')developer.mozilla.org/en-US/docs/…

window.onerror

不能捕获静态资源报错,因为当一项资源(如<img>或<script>)加载失败,加载资源的元素会触发一个Event接口的error事件,并执行该元素上的onerror()处理函数。这些error事件不会向上冒泡到window。

错误类型描述
EvalError与eval()相关的错误。eval()本身没有正确执行
RangeError数值变量或参数超出其有效范围。例子:var a = new Array(-1);
ReferenceError语法错误。例子:var a = ;
TypeError变量或参数不属于有效范围。例子:[1,2].split('.')
URIError给 encodeURI或 decodeURl()传递的参数无效。例子:decodeURI('%2')
AggregateError当多个错误需要包装在一个错误中时,该对象表示一个错误;当需要由操作报告多个错误被抛出,例如通过Promise.any(),在传递给它的所有Promise拒绝。
InternalError创建一个代表Javascript引擎内部错误的异常抛出的实例。 如: "递归太多"。非ECMAScript标准。
DOMException接口代表调用方法或访问 Web API 属性时发生的异常事件(被称为异常,exception)

RangeError

window.onerror = function(message, source, lineno, colno, error) { 
  console.log('错误信息(字符串):',message);
  console.log('发生错误的脚本URL:',source);
  console.log('发生错误的行号:',lineno);
  console.log('发生错误的列号:',colno);
  console.log('Error对象:',error);
}
var a = new Array(-1);

image.png

DOMException

window.onerror = function(message, source, lineno, colno, error) { 
  console.log('错误信息(字符串):',message);
  console.log('发生错误的脚本URL:',source);
  console.log('发生错误的行号:',lineno);
  console.log('发生错误的列号:',colno);
  console.log('Error对象:',error);
}
var node = document.querySelector('#app');
var refnode = node.nextSibling;
var newnode = document.createElement('div');
node.insertBefore(newnode, refnode);

image.png

Promise异常

promise抛出异常,window.onerror捕获不到,需要用到window.addEventListener('unhandledrejection')

window.addEventListener('unhandledrejection',function(promiseRejectionEvent){
  console.log(promiseRejectionEvent.reason)
})
const p1 = new Promise(function(resolve,reject){
  resolve(a+b);
});
p1.then(function(value){
  console.log(value)
})

image.png

console.error

在某种场景下,也需要捕获业务系统的console.error异常

window.console.error = function(){
  console.log('捕获到了console.error异常,参数是:',arguments);
  consoleError && consoleError.apply(window,arguments);
}
console.error('这是一个console.error异常')

image.png

框架异常

Vue异常捕获

app.config.errorHandler = function(err, vm, info) {
  console.log(err, vm, info, "---a00-----");
};

image.png

静态资源异常

当一项资源(如<img>或<script>)加载失败,加载资源的元素会触发一个Event接口的error事件,并执行该元素上的onerror()处理函数。这些error事件不会向上冒泡到window,不过能被window.addEventListener在捕获阶段捕获。所以useCapture要传值为true,才能捕获到静态资源异常.

window.addEventListener('error',function(event){
  console.log(event,'event');
},true)

image.png

跨域脚本error

当加载自不同域的脚本中发生语法错误时,为避免信息泄露(参见bug 363897),语法错误的细节将不会报告,而代之简单的"Script error."。

在某些浏览器中,通过在<script>使用crossorigin属性并要求服务器发送适当的 CORS HTTP 响应头,该行为可被覆盖。一个变通方案是单独处理"Script error.",告知错误详情仅能通过浏览器控制台查看。

接口异常

接口异常后端可以进行接口拦截,后端进行处理,这里前端就不列举方案;

需要解决问题

现在的前端项目,如果一旦发布上线,代码一般都会进行混淆、压缩甚至加密,线上报错时,定位到的行号始终为1,如下面例子,这样,我们监控到的错误数据就没有意义,需要解决这个问题,理想的结果是能够精确定位到某个文件的多少行,多少列

image.png

image.png

解决办法

引入source-map包

var fs = require('fs'),
  path = require('path'),
  sourceMap = require('source-map');
 
// 要解析的map文件路径./test/vendor.8b1e40e47e1cc4a3533b.js.map
var GENERATED_FILE = path.join(
  '.',
  'dist/js',
  'app.5025c1aa.js.map'
)
let getSource = async()=>{
// 读取map文件,实际就是一个json文件
    var rawSourceMap = fs.readFileSync(GENERATED_FILE).toString();
    // 通过sourceMap库转换为sourceMapConsumer对象
    var consumer = await new sourceMap.SourceMapConsumer(rawSourceMap);
    // 传入要查找的行列数,查找到压缩前的源文件及行列数
    var sm = consumer.originalPositionFor({
        line: 1,  // 压缩后的行数
        column: 224607  // 压缩后的列数
      });
      console.log('源文件行列数:',sm)
    // 压缩前的所有源文件列表
    var sources = consumer.sources;
    // 根据查到的source,到源文件列表中查找索引位置
    var smIndex = sources.indexOf(sm.source);
    // 到源码列表中查到源代码
    var smContent = consumer.sourcesContent[smIndex];
    // 将源代码串按"行结束标记"拆分为数组形式
    const rawLines = smContent.split(/\r?\n/g);
    // 输出源码行,因为数组索引从0开始,故行数需要-1
    console.log(rawLines.join('\n'))
}
getSource();

image.png

根据source-map文件,可以定位到具体的某个文件的某一行,某一列报错。该问题已经被解决。