現(xiàn)行有一些已經(jīng)開源的前端異常監(jiān)控庫,如騰訊的badJs,全棧js監(jiān)控fundebug,國外的sentry等。
錯誤分類
-
javascript異常
- 語法錯誤
- 運(yùn)行時(shí)錯誤
- script文件內(nèi)錯誤(跨域和未跨域)
JS文件、CSS文件、img圖片等(資源)的404錯誤(其實(shí)是有onerror事件的dom)
promise的異常捕獲
ajax請求錯誤
錯誤上報(bào)
1、采用Ajax通信的方式上報(bào)(不常用)
2、利用Image對象上報(bào)(常用)
錯誤捕獲類型
- 主動捕獲(try catch / promise catch)
- 全局捕獲(onerror / addEventListener)
錯誤捕獲原理
error事件的事件處理程序。針對各種目標(biāo)的不同類型的錯誤觸發(fā)了 Error 事件:
當(dāng)JavaScript運(yùn)行時(shí)錯誤(包括語法錯誤)發(fā)生時(shí),window會觸發(fā)一個ErrorEvent接口的error事件,并執(zhí)行window.onerror()。
當(dāng)一項(xiàng)資源(如<img>或<script>)加載失敗,加載資源的元素會觸發(fā)一個Event接口的error事件,并執(zhí)行該元素上的onerror()處理函數(shù)。這些error事件不會向上冒泡到window,不過(至少在Firefox中)能被單一的window.addEventListener捕獲。
加載一個全局的error事件處理函數(shù)可用于自動收集錯誤報(bào)告。
由于歷史原因,window.onerror和element.onerror接受不同的參數(shù)。
window.onerror = function(message, source, lineno, colno, error) { ... }
通過為頁面上的 script 標(biāo)簽添加 crossOrigin 屬性完成跨域上報(bào),別忘了服務(wù)器也設(shè)置 Access-Control-Allow-Origin 的響應(yīng)頭。(解決跨域的js腳本錯誤上報(bào))
window.addEventListener監(jiān)聽error事件
由于網(wǎng)絡(luò)請求異常不會事件冒泡,因此必須在捕獲階段將其捕捉到才行,但是這種方式雖然可以捕捉到網(wǎng)絡(luò)請求的異常,但是無法判斷 HTTP 的狀態(tài)是 404 還是其他比如 500 等等,所以還需要配合服務(wù)端日志才進(jìn)行排查分析才可以。
window.addEventListener('error', function(event) { ... }) //兼容ie9
element.onerror = function(event) { ... } //dom0,兼容低版ie
注意事項(xiàng):
當(dāng)加載自不同域的腳本中發(fā)生語法錯誤時(shí),為避免信息泄露(參見bug 363897),語法錯誤的細(xì)節(jié)將不會報(bào)告,而代之簡單的"Script error."。在某些瀏覽器中,通過在<script>使用crossorigin屬性并要求服務(wù)器發(fā)送適當(dāng)?shù)?CORS HTTP 響應(yīng)頭,該行為可被覆蓋。一個變通方案是單獨(dú)處理"Script error.",告知錯誤詳情僅能通過瀏覽器控制臺查看,無法通過JavaScript訪問。
別忘了服務(wù)器也設(shè)置 Access-Control-Allow-Origin 的響應(yīng)頭。(解決跨域的js腳本錯誤上報(bào))
try,catch 和window.onerror 比較
try,catch的方案有如下特點(diǎn):
無法捕捉到語法錯誤,只能捕捉運(yùn)行時(shí)錯誤;
可以拿到出錯的信息,堆棧,name,message,stack,有些瀏覽器不能獲取出錯的文件、行號、列號;
需要借助工具把所有的function塊以及文件塊加入try,catch,可以在這個階段打入更多的靜態(tài)信息。
不能捕獲異步錯誤(promise,setTimeout),但可以捕獲async await,Generator 可以直接使用co 函數(shù)庫來使用try...catch
window.onerror的方案有如下特點(diǎn):
可以捕捉語法錯誤,也可以捕捉運(yùn)行時(shí)錯誤;
可以拿到出錯的信息,堆棧,出錯的文件、行號、列號;
只要在當(dāng)前頁面執(zhí)行的js腳本出錯都會捕捉到,例如:瀏覽器插件的javascript、或者flash拋出的異常等。
跨域的資源需要特殊頭部支持。
promise.catch主動捕獲
unhandledrejection監(jiān)聽全局沒有catch的promise執(zhí)行.但是這個的兼容性不是很好
在一個JavaScript Promise 被 reject(拒絕) 但是沒有 reject 處理函數(shù)來處理時(shí)觸發(fā)。
window.onunhandledrejection = function(e) {
console.log(e.reason);
}
目前chrome49支持,在 Firefox 里,有實(shí)現(xiàn)這個接口但是默認(rèn)是禁用的。要打開它的話,去到about:config 將 dom.promise_rejection_events.enabled 啟用為真。
附:try catch Error對象屬性:
1.Firefox中的Error對象擁有如下屬性:
message —— 錯誤提示信息
fileName —— 表示出錯代碼所在文件
lineNumber —— 出錯代碼所在行數(shù)
stack —— 出錯堆棧信息
name —— 異常對象名/類型
2.在IE下,Error對象只有如下屬性:
name —— 異常對象名/類型,和Firefox中顯示的名稱可能不同
message —— 錯誤提示信息
description —— 和message屬性相同
number —— ErrorCode,錯誤代碼,對于普通開發(fā)人員來說基本沒意義
3.在Safari中的Error對象擁有如下屬性:
message —— 錯誤提示信息
line —— 出錯代碼所在行數(shù)
sourceId —— 一個數(shù)字,不明白什么意思
sourceURL —— 表示出錯代碼所在文件
name —— 異常對象名/類型
4.Opera下的Error對象擁有如下屬性:
message —— 錯誤提示信息
opera#sourceloc —— 出錯代碼所在行數(shù)
stacktrace —— 出錯堆棧信息
參考:http://www.itdecent.cn/p/85d9a2778d80
https://developer.mozilla.org/zh-CN/docs/Web/API/GlobalEventHandlers/onerror
代碼示例
(function(global, factory) {
'use strict';
if (typeof module === 'object' && typeof module.exports === 'object') {
module.exports = global.document ? factory(global, true) : function(w) {
if (!w.document) {
throw new Error('track.js requires a window with a document');
}
return factory(w);
};
} else {
factory(global);
}
})(typeof window !== 'undefined' ? window : this, function(window, noGlobal) {
window.onerror = function(message, url, line, column, error) {
var stackInfo;
//不一定所有瀏覽器都支持col參數(shù)
column = column || (window.event && window.event.errorCharacter);
if (error && error.stack) {
stackInfo = error.stack.toString();
} else {
var stack = [];
var i = 3;
var f = arguments.callee.caller;
while (f && i > 0) {
var funcName = f.name || f.toString().match(/^function\s*([^\s(]+)/)[1]; //IE不支持function.name
stack.push('function ' + funcName + ' () ');
if (f === f.caller) {
break;//如果有環(huán)
}
f = f.caller;
i--;
}
stackInfo = stack.join('\n at ');
}
var data = {
msg: message,
url: url,
page: location.href,
line: line,
col: column,
stack: stackInfo
};
track({
'type': 'error',
'info': data
});
};
window.addEventListener('unhandledrejection', function(error) {
track({
'type': 'error',
'info': error.reason
});
});
var commonParams = function() {
var o = {},
screen;
if (screen = window && window.screen) {
var sc = screen.width,
sh = screen.height;
o.screen = sc + ' x ' + sh;
o.colorDepth = screen.colorDepth && (screen.colorDepth + '-bit');
}
if (navigator) {
o.language = (navigator.language || navigator.browserLanguage).toLowerCase();
o.javaEnabled = navigator.javaEnabled();
}
return o;
};
var track = function(extendParams) {
var o = {};
for(var temp in extendParams){
o[temp]=extendParams[temp];
}
o.commonParams = track.commonParams || (track.commonParams=commonParams(track));
var arr = [];
for (var key in o) {
if (o[key] !== undefined) {
if (o[key] instanceof Object) {
for (var okey in o[key]) {
arr.push(encodeURIComponent(key + '[' + okey + ']') + '=' + encodeURIComponent(o[key][okey]));
}
} else {
arr.push(key + '=' + encodeURIComponent(o[key]));
}
}
}
console.log(arr,11)
var img = new Image();
img.src = 'http://172.31.11.245:3000/_h5track?' + arr.join('&') + '×tamp=' + new Date().getTime();
// img.onload = img.onerror = function() {
// img=null;
// };
};
if (!noGlobal) {
window.track = window.trackLog = track;
}
});
// 監(jiān)控資源加載錯誤(img,script,css,以及jsonp)
window.addEventListener('error',function(e){
defaults.t =new Date().getTime();
defaults.msg =e.target.localName+' is load error';
defaults.data = JSON.stringify({
target: e.target.localName,
type: e.type,
resourceUrl:e.target.currentSrc,
pageUrl:location.href,
category:'resource'
});
if(e.target!=window){//拋去js語法錯誤
// 合并上報(bào)的數(shù)據(jù),包括默認(rèn)上報(bào)的數(shù)據(jù)和自定義上報(bào)的數(shù)據(jù)
var reportData=Object.assign({},params.data || {},defaults);
// 把錯誤信息發(fā)送給后臺
alert(JSON.stringify(reportData))
}
},true);
window.addEventListener('unhandledrejection', function(err) {
console.log(err);
});
angularjs報(bào)錯實(shí)現(xiàn):
var app = angular.module('myApp', []);
app.config(function($provide) {
$provide.decorator('$exceptionHandler', [
'$httpParamSerializerJQLike', '$delegate', function($httpParamSerializerJQLike, $delegate) {
return function(exception, cause) {
$delegate(exception, cause);
var data = {
msg: exception.toString(),
cause: cause,
url:exception.fileName||exception.sourceURL,
page: location.href,
line: exception.lineNumber||exception.line ||exception['opera#sourceloc'],
stack: exception.stack||exception.stacktrace
};
track(data);
};
}]);
});
app.controller('myCtrl', function($scope) {
$scope.firstName = 'John';
$scope.lastName = 'Doe';
$scope.c=undefined.a;
// t;
});