關于前端異常監(jiān)控办素,我們需要做到捕獲JS異常和代碼中未捕獲的promise異常,然后向服務器上報
捕獲JS異常
在了解捕獲JS異常前先了解下window.onerror語法
window.onerror = function(message, source, lineno, colno, error) { ... }
函數(shù)參數(shù):
-
message
:錯誤信息(字符串)古徒。可用于HTMLonerror=""
處理程序中的event
读恃。 -
source
:發(fā)生錯誤的腳本URL(字符串) -
lineno
:發(fā)生錯誤的行號(數(shù)字) -
colno
:發(fā)生錯誤的列號(數(shù)字) -
error
:Error對象(對象)
若該函數(shù)返回true
隧膘,則阻止執(zhí)行默認事件處理函數(shù)。
window.addEventListener('error')節(jié)
window.addEventListener('error', function(event) { ... })
ErrorEvent
類型的event
包含有關事件和錯誤的所有信息寺惫。
element.onerror節(jié)
element.onerror = function(event) { ... }
element.onerror
使用單一Event
參數(shù)的函數(shù)作為其處理函數(shù)疹吃。
我們可以看到函數(shù)正常是可以收集到錯誤字符串信息、發(fā)生錯誤的js文件西雀,錯誤所在的行數(shù)萨驶、列數(shù)、和Error對象(里面會有調(diào)用堆棧信息等)艇肴,把這些信息回傳到server端即可篡撵,再配合sourcemap的話我們就可以知道是源碼中的哪一行出錯了判莉,從而實現(xiàn)完美的錯誤實時監(jiān)控系統(tǒng)了。然而要完美還是需要做很多工作的育谬。
首先,我們的js文件一般都是和網(wǎng)站不同域的帮哈,這是為了提高頁面的渲染速度以及架構的可維護性(單獨CDN域名膛檀,充分利用瀏覽器http并發(fā)數(shù))。這樣的js文件中發(fā)生錯誤我們直接監(jiān)控你會發(fā)現(xiàn)你啥信息都收集不到娘侍。
實驗一:我們的站點是a.com
咖刃,頁面中引用了兩個js文件,一個是a.com
域名下的a.js憾筏,一個是b.com
域名下的b.js嚎杨,我們在a.js文件中添加window.onerror監(jiān)控,在b.js文件中主動拋出錯誤
<!-- index.html -->
<script type="text/javascript" src="http://a.com/a.js" ></script>
<script type="text/javascript" src="http://b.com/b.js" ></script>
// a.js
window.onerror = function (message, url, line, column, error) {
console.log('log---onerror::::',message, url, line, column, error);
}
// b.js
throw new Error('this is the error happened in b.js');
我們可以看到下圖的結果氧腰,onerror函數(shù)拿到的信息是Script error, a 0 null
枫浙,啥卵用都沒有,你完全不知道發(fā)生了什么錯誤古拴,哪個文件發(fā)生的錯誤箩帚。
這是瀏覽器的同源策略,當加載自不同域(協(xié)議黄痪、域名紧帕、端口三者任一不同)的腳本中發(fā)生語法(?)錯誤時,為避免信息泄露桅打,語法錯誤的細節(jié)將不會報告是嗜,而代之簡單的"Script error."。
但是我們確實是需要知道發(fā)生錯誤的具體信息啊挺尾,不然監(jiān)控就沒有意義了鹅搪。既然又是類同源限制的問題,那肯定是可以通過CORS來解決了潦嘶。
實驗二:我們給b.js
加上Access-Control-Allow-Origin:*
的response header涩嚣,后面我們會發(fā)現(xiàn)還是沒啥變化。
實驗三:我們繼續(xù)給b.js
加上crossorigin
屬性掂僵,發(fā)現(xiàn)可以了航厚,想要的信息都收集到了,nice
<!-- index.html -->
<script type="text/javascript" src="http://a.com/a.js" ></script>
<script type="text/javascript" src="http://b.com/b.js" crossorigin></script>
結論:如果想通過onerror函數(shù)收集不同域的js錯誤锰蓬,我們需要做兩件事:
- 相關的js文件上加上
Access-Control-Allow-Origin:*
的response header - 引用相關的js文件時加上crossorigin屬性
注意: 以上兩步缺一不可幔睬。實驗二告訴我們,如果只是加上Access-Control-Allow-Origin:*
的話芹扭,錯誤還是無法捕獲麻顶。如果只加上crossorigin屬性赦抖,瀏覽器會報無法加載的錯誤,如下圖
可是辅肾。队萤。。
如果你使用sentry的raven.js的話矫钓,你會發(fā)現(xiàn)你什么都不用做要尔,他依然可以幫你捕獲到一些錯誤的非常具體信息,確實是有點神奇啊新娜,具體怎么做的赵辕?關鍵就是raven源碼中的install方法中調(diào)用的_instrumentTryCatch函數(shù)起了作用,他通過tryCatch的方式wrap了一些關鍵函數(shù)概龄,使得這些函數(shù)里的報錯能夠捕獲还惠,_instrumentTryCatch的具體實現(xiàn)原理我們后面再說
install: function() {
var self = this;
if (self.isSetup() && !self._isRavenInstalled) {
TraceKit.report.subscribe(function () {
self._handleOnErrorStackInfo.apply(self, arguments);
});
if (self._globalOptions.instrument && self._globalOptions.instrument.tryCatch) {
self._instrumentTryCatch();// 通過tryCatch來wrap關鍵函數(shù),從而獲得error的具體信息
}
if (self._globalOptions.autoBreadcrumbs)
self._instrumentBreadcrumbs();
// Install all of the plugins
self._drainPlugins();
self._isRavenInstalled = true;
}
Error.stackTraceLimit = self._globalOptions.stackTraceLimit;
return this;
},
其實如果你真的什么都不做私杜,raven也只是能捕獲一些異步錯誤蚕键,同步錯誤還是無法捕獲,所以你即使使用了sentry等第三方的錯誤收集庫歪今,你還是需要加上Access-Control-Allow-Origin:*
和crossorigin屬性
捕獲未處理的promise異常
為了保證可讀性嚎幸,本文采用意譯而非直譯,并且對源代碼進行了大量修改寄猩。另外嫉晶,本文版權歸原作者所有,翻譯僅用于學習田篇。
使用Promise編寫異步代碼時替废,使用reject來處理錯誤。有時泊柬,開發(fā)者通常會忽略這一點椎镣,導致一些錯誤沒有得到處理。例如:
function main() {
asyncFunc()
.then(···)
.then(() => console.log('Done!'));
}
這篇博客將分別介紹在瀏覽器與Node.js中兽赁,如何捕獲那些未處理的Promise錯誤状答。由于沒有使用catch方法捕獲錯誤,當asyncFunc()函數(shù)reject時刀崖,拋出的錯誤則沒有被處理惊科。
瀏覽器中未處理的Promise錯誤
一些瀏覽器(例如Chrome)能夠捕獲未處理的Promise錯誤。
unhandledrejection
監(jiān)聽unhandledrejection事件亮钦,即可捕獲到未處理的Promise錯誤:
window.addEventListener('unhandledrejection', event => ···);
promise
: reject的Promise這個事件是PromiseRejectionEvent實例馆截,它有2個最重要的屬性:
-
reason
: Promise的reject值
示例代碼:
window.addEventListener('unhandledrejection', event => {
console.log(event.reason);
});
function foo() {
Promise.reject('Hello, Fundebug!');
}
foo();
當一個Promise錯誤最初未被處理,但是稍后又得到了處理,則會觸發(fā)rejectionhandled事件:
window.addEventListener('rejectionhandled', event => ···);
示例代碼:這個事件是PromiseRejectionEvent實例蜡娶。
window.addEventListener('unhandledrejection', event =>
{
console.log(event.reason); // 打印"Unhandle Promise Error!"
});
window.addEventListener('rejectionhandled', event => {
console.log('rejection handled'); // 1秒后打印"rejection handled"
});
function foo() {
return Promise.reject('Unhandle Promise Error!');
}
var r = foo();
setTimeout(() => {
r.catch(e =>{});
}, 1000);
參考文獻:
GlobalEventHandlers.onerror
What the heck is "Script error"?