js錯(cuò)誤監(jiān)控總結(jié)

前言

做好錯(cuò)誤監(jiān)控面哼,將用戶使用時(shí)的錯(cuò)誤日志上報(bào)哼绑,可以幫助我們更快的解決一些問題宗苍。目前開源的比較好的前端監(jiān)控有

那前端監(jiān)控是怎么實(shí)現(xiàn)的呢?要想了解這個(gè)苔咪,需要知道前端錯(cuò)誤大概分為哪些以及如何捕獲處理搀突。

前端錯(cuò)誤分為JS運(yùn)行時(shí)錯(cuò)誤刀闷、資源加載錯(cuò)誤和接口錯(cuò)誤三種。

一、JS運(yùn)行時(shí)錯(cuò)誤

JS運(yùn)行時(shí)錯(cuò)誤一般使用window.onerror捕獲甸昏,但是有一種特殊情況就是promise被reject并且錯(cuò)誤信息沒有被處理的時(shí)候拋出的錯(cuò)誤

1.1 一般情況的JS運(yùn)行時(shí)錯(cuò)誤

使用window.onerror和window.addEventListener('error')捕獲顽分。

window.onerror = function (msg, url, lineNo, columnNo, error) 
    { 
       // 處理error信息
    } 

    window.addEventListener('error', event =>  
    {  
       console.log('addEventListener error:' + event.target); 
    }, true); 
    // true代表在捕獲階段調(diào)用,false代表在冒泡階段捕獲施蜜。使用true或false都可以

例子:https://jsbin.com/lujahin/edit?html,console,output 點(diǎn)擊button拋出錯(cuò)誤卒蘸,分別被window.onerror和window.addEventListener('error')捕獲

1.2 Uncaught (in promise)

當(dāng)promise被reject并且錯(cuò)誤信息沒有被處理的時(shí)候,會(huì)拋出一個(gè)unhandledrejection翻默,并且這個(gè)錯(cuò)誤不會(huì)被window.onerror以及window.addEventListener('error')捕獲缸沃,需要用專門的window.addEventListener('unhandledrejection')捕獲處理

window.addEventListener('unhandledrejection', event => 
    { 
       console.log('unhandledrejection:' + event.reason); // 捕獲后自定義處理
    });

https://developer.mozilla.org...
例子:https://jsbin.com/jofomob/edit?html,console,output 點(diǎn)擊button拋出unhandledrejection錯(cuò)誤,并且該錯(cuò)誤僅能被window.addEventListener('unhandledrejection')捕獲

1.3 console.error

一些特殊情況下修械,還需要捕獲處理console.error趾牧,捕獲方式就是重寫window.console.error

var consoleError = window.console.error; 
window.console.error = function () { 
    alert(JSON.stringify(arguments)); // 自定義處理
    consoleError && consoleError.apply(window, arguments); 
};

例子:https://jsbin.com/pemigew/edit?html,console,output

1.4 特別說明跨域日志

什么是跨域腳本error?

https://developer.mozilla.org...
當(dāng)加載自不同域的腳本中發(fā)生語法錯(cuò)誤時(shí)肯污,為避免信息泄露(參見bug 363897)翘单,語法錯(cuò)誤的細(xì)節(jié)將不會(huì)報(bào)告,而代之簡(jiǎn)單的"Script error."仇箱。在某些瀏覽器中县恕,通過在<script>使用crossorigin屬性并要求服務(wù)器發(fā)送適當(dāng)?shù)?CORS HTTP 響應(yīng)頭,該行為可被覆蓋剂桥。一個(gè)變通方案是單獨(dú)處理"Script error.",告知錯(cuò)誤詳情僅能通過瀏覽器控制臺(tái)查看属提,無法通過JavaScript訪問权逗。

window.onerror = function (msg, url, lineNo, columnNo, error) {
    var string = msg.toLowerCase();
    var substring = "script error";
    if (string.indexOf(substring) > -1){
        alert('Script Error: See Browser Console for Detail');
    } else {
        var message = [
            'Message: ' + msg,
            'URL: ' + url,
            'Line: ' + lineNo,
            'Column: ' + columnNo,
            'Error object: ' + JSON.stringify(error)
        ].join(' - ');

        alert(message);
    }

    return false;
};

例子: http://sandbox.runjs.cn/show/... 請(qǐng)打開頁(yè)面打開控制臺(tái)。該頁(yè)面分別加載了兩個(gè)不同域的js腳本冤议,配置了crossorigin的window.onerror可以報(bào)出詳細(xì)的錯(cuò)誤斟薇,沒有配置crossorigin只能報(bào)出'script error',并且沒有錯(cuò)誤信息

為了跨域捕獲JavaScript異常恕酸,可執(zhí)行以下兩個(gè)解法:

解法一堪滨、1.添加crossorigin="anonymous"屬性。

<script src="http://another-domain.com/app.js" crossorigin="anonymous"></script>

此步驟的作用是告知瀏覽器以匿名方式獲取目標(biāo)腳本蕊温。這意味著請(qǐng)求腳本時(shí)不會(huì)向服務(wù)端發(fā)送潛在的用戶身份信息(例如Cookies袱箱、HTTP證書等)。

2.添加跨域HTTP響應(yīng)頭义矛。

Access-Control-Allow-Origin: * 或者 Access-Control-Allow-Origin: http://test.com

解法二 发笔、try catch

<!doctype html>
<html>
<head>
  <title>Test page in http://test.com</title>
</head>
<body>
  <script src="http://another-domain.com/app.js"></script>
 <script>
  window.onerror = function (message, url, line, column, error) {
    console.log(message, url, line, column, error);
  }
  try {
    foo(); // 調(diào)用app.js中定義的foo方法
  } catch (e) {
    console.log(e);
    throw e;
  }
  </script>
</body>
</html>

再次運(yùn)行,輸出結(jié)果如下:

=> ReferenceError: bar is not defined
     at foo (http://another-domain.com/app.js:2:3)
     at http://test.com/:15:3
=> "Script error.", "", 0, 0, undefined

可見try catch中的Console語句輸出了完整的信息凉翻,但window.onerror中只能捕獲“Script error”了讨。根據(jù)這個(gè)特點(diǎn),可以在catch語句中手動(dòng)上報(bào)捕獲的異常,詳情請(qǐng)參見API 使用指南前计。

1.5 特別說明sourceMap

在線上由于JS一般都是被壓縮或者打包(webpack)過胞谭,打包后的文件只有一行,因此報(bào)錯(cuò)會(huì)出現(xiàn)第一行第5000列出現(xiàn)JS錯(cuò)誤男杈,給排查帶來困難韭赘。sourceMap存儲(chǔ)打包前的JS文件和打包后的JS文件之間一個(gè)映射關(guān)系,可以根據(jù)打包后的位置快速解析出對(duì)應(yīng)源文件的位置势就。

但是出于安全性考慮泉瞻,線上設(shè)置sourceMap會(huì)存在不安全的問題,因?yàn)榫W(wǎng)站使用者可以輕易的看到網(wǎng)站源碼苞冯,此時(shí)可以設(shè)置.map文件只能通過公司內(nèi)網(wǎng)訪問降低隱患

sourceMap配置devtool: 'inline-source-map'
如果使用了uglifyjs-webpack-plugin 必須把 sourceMap設(shè)置為true
https://doc.webpack-china.org...

1.6 其它

1.6.1 sentry把所有的回調(diào)函數(shù)使用try catch封裝一層
https://github.com/getsentry/raven-js/blob/master/src/raven.js

1.6.2 vue errorHandler
https://vuejs.org/v2/api/#errorHandler
其原理也是使用try catch封裝了nextTick,$emit, watch,data等
https://github.com/vuejs/vue/blob/dev/dist/vue.runtime.js

二袖牙、資源加載錯(cuò)誤

使用window.addEventListener('error')捕獲,window.onerror捕獲不到資源加載錯(cuò)誤

https://jsbin.com/rigasek/edit?html,console 圖片資源加載錯(cuò)誤舅锄。此時(shí)只有window.addEventListener('error')可以捕獲到

window.onerror和window.addEventListener('error')的異同:相同點(diǎn)是都可以捕獲到window上的js運(yùn)行時(shí)錯(cuò)誤鞭达。區(qū)別是1.捕獲到的錯(cuò)誤參數(shù)不同 2.window.addEventListener('error')可以捕獲資源加載錯(cuò)誤,但是window.onerror不能捕獲到資源加載錯(cuò)誤

window.addEventListener('error')捕獲到的錯(cuò)誤皇忿,可以通過target?.src || target?.href區(qū)分是資源加載錯(cuò)誤還是js運(yùn)行時(shí)錯(cuò)誤

三畴蹭、接口錯(cuò)誤

所有http請(qǐng)求都是基于xmlHttpRequest或者fetch封裝的。所以要捕獲全局的接口錯(cuò)誤鳍烁,方法就是封裝xmlHttpRequest或者fetch

3.1 封裝xmlHttpRequest

if(!window.XMLHttpRequest) return;
var xmlhttp = window.XMLHttpRequest;
var _oldSend = xmlhttp.prototype.send;
var _handleEvent = function (event) {
    if (event && event.currentTarget && event.currentTarget.status !== 200) {
          // 自定義錯(cuò)誤上報(bào) }
}
xmlhttp.prototype.send = function () {
    if (this['addEventListener']) {
        this['addEventListener']('error', _handleEvent);
        this['addEventListener']('load', _handleEvent);
        this['addEventListener']('abort', _handleEvent);
    } else {
        var _oldStateChange = this['onreadystatechange'];
        this['onreadystatechange'] = function (event) {
            if (this.readyState === 4) {
                _handleEvent(event);
            }
            _oldStateChange && _oldStateChange.apply(this, arguments);
        };
    }
    return _oldSend.apply(this, arguments);
}

3.2 封裝fetch

if(!window.fetch) return;
    let _oldFetch = window.fetch;
    window.fetch = function () {
        return _oldFetch.apply(this, arguments)
        .then(res => {
            if (!res.ok) { // True if status is HTTP 2xx
                // 上報(bào)錯(cuò)誤
            }
            return res;
        })
        .catch(error => {
            // 上報(bào)錯(cuò)誤
            throw error;  
        })
}

結(jié)論

  1. 使用window.onerror捕獲JS運(yùn)行時(shí)錯(cuò)誤
  2. 使用window.addEventListener('unhandledrejection')捕獲未處理的promise reject錯(cuò)誤
  3. 重寫console.error捕獲console.error錯(cuò)誤
  4. 在跨域腳本上配置crossorigin="anonymous"捕獲跨域腳本錯(cuò)誤
  5. window.addEventListener('error')捕獲資源加載錯(cuò)誤叨襟。因?yàn)樗材懿东@js運(yùn)行時(shí)錯(cuò)誤,為避免重復(fù)上報(bào)js運(yùn)行時(shí)錯(cuò)誤幔荒,此時(shí)只有event.srcElement inatanceof HTMLScriptElement或HTMLLinkElement或HTMLImageElement時(shí)才上報(bào)
  6. 重寫window.XMLHttpRequest和window.fetch捕獲請(qǐng)求錯(cuò)誤

利用以上原理糊闽,簡(jiǎn)單寫了一個(gè)JS監(jiān)控,只處理了一些JS錯(cuò)誤爹梁,暫時(shí)沒有做和性能相關(guān)的監(jiān)控
https://github.com/Lie8466/better-js

如果發(fā)現(xiàn)文章有錯(cuò)誤右犹,歡迎指正。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末姚垃,一起剝皮案震驚了整個(gè)濱河市念链,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌积糯,老刑警劉巖掂墓,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異絮宁,居然都是意外死亡梆暮,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門绍昂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來啦粹,“玉大人偿荷,你說我怎么就攤上這事∵胪郑” “怎么了跳纳?”我有些...
    開封第一講書人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)贪嫂。 經(jīng)常有香客問我寺庄,道長(zhǎng),這世上最難降的妖魔是什么力崇? 我笑而不...
    開封第一講書人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任斗塘,我火速辦了婚禮,結(jié)果婚禮上亮靴,老公的妹妹穿的比我還像新娘馍盟。我一直安慰自己,他們只是感情好茧吊,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開白布贞岭。 她就那樣靜靜地躺著,像睡著了一般搓侄。 火紅的嫁衣襯著肌膚如雪瞄桨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,792評(píng)論 1 290
  • 那天讶踪,我揣著相機(jī)與錄音芯侥,去河邊找鬼。 笑死俊柔,一個(gè)胖子當(dāng)著我的面吹牛筹麸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播雏婶,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼白指!你這毒婦竟也來了留晚?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤告嘲,失蹤者是張志新(化名)和其女友劉穎错维,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體橄唬,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡赋焕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了仰楚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片隆判。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡犬庇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出侨嘀,到底是詐尸還是另有隱情臭挽,我是刑警寧澤,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布咬腕,位于F島的核電站欢峰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏涨共。R本人自食惡果不足惜纽帖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望举反。 院中可真熱鬧懊直,春花似錦、人聲如沸照筑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)凝危。三九已至波俄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蛾默,已是汗流浹背懦铺。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留支鸡,地道東北人冬念。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像牧挣,于是被迫代替她去往敵國(guó)和親急前。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348