前端錯(cuò)誤捕獲

常見錯(cuò)誤的分類

對(duì)于用戶在訪問頁面時(shí)發(fā)生的錯(cuò)誤锭亏,主要包括以下幾個(gè)類型:

1槐臀、js運(yùn)行時(shí)錯(cuò)誤

JavaScript代碼在用戶瀏覽器中執(zhí)行時(shí)爷速,由于一些邊界情況、本地環(huán)境的不可控等因素层释,可能會(huì)存在js運(yùn)行時(shí)錯(cuò)誤诊县。

而依賴客戶端的某些方法座舍,由于兼容性或者網(wǎng)絡(luò)等問題港令,也有概率會(huì)出現(xiàn)運(yùn)行時(shí)錯(cuò)誤。

e.g: 下圖是當(dāng)使用了未定義的變量"foo"秕磷,導(dǎo)致產(chǎn)生js運(yùn)行時(shí)錯(cuò)誤時(shí)的上報(bào)數(shù)據(jù):


2诵闭、資源加載錯(cuò)誤

這里的靜態(tài)資源包括js、css以及image等。現(xiàn)在的web項(xiàng)目疏尿,往往依賴了大量的靜態(tài)資源瘟芝,而且一般也會(huì)有cdn存在。

如果某個(gè)節(jié)點(diǎn)出現(xiàn)問題導(dǎo)致某個(gè)靜態(tài)資源無法訪問润歉,就需要能夠捕獲這種異常并進(jìn)行上報(bào)模狭,方便第一時(shí)間解決問題颈抚。

e.g: 下圖是圖片資源不存在時(shí)的上報(bào)數(shù)據(jù):

3踩衩、未處理的promise錯(cuò)誤

未使用catch捕獲的promise錯(cuò)誤,往往都會(huì)存在比較大的風(fēng)險(xiǎn)贩汉。而編碼時(shí)有可能覆蓋的不夠全面驱富,因此有必要監(jiān)控未處理的promise錯(cuò)誤并進(jìn)行上報(bào)。

e.g: 下圖是promise請求接口發(fā)生錯(cuò)誤后匹舞,未進(jìn)行catch時(shí)的上報(bào)數(shù)據(jù):

4褐鸥、異步請求錯(cuò)誤(fetch與xhr)

異步錯(cuò)誤的捕獲分為兩個(gè)部分:一個(gè)是傳統(tǒng)的XMLHttpRequest,另一個(gè)是使用fetch api赐稽。

像axios和jQuery等庫就是在xhr上的封裝叫榕,而有些情況也可能會(huì)使用原生的fetch,因此對(duì)這兩種情況都要進(jìn)行捕獲姊舵。

e.g: 下圖是xhr請求接口返回400時(shí)捕獲后的上報(bào)數(shù)據(jù):


各個(gè)類型錯(cuò)誤的捕獲方式

1晰绎、window.onerror與window.addEventListener('error')捕獲js運(yùn)行時(shí)錯(cuò)誤

使用window.onerror和window.addEventListener('error')都能捕獲,但是window.onerror含有詳細(xì)的error堆棧信息括丁,存在error.stack中荞下,所以我們選擇使用onerror的方式對(duì)js運(yùn)行時(shí)錯(cuò)誤進(jìn)行捕獲。

window.onerror =function(msg, url, lineNo, columnNo, error){// 處理錯(cuò)誤信息}// demomsg: UncaughtTypeError: UncaughtReferenceError: a is not definederror.statck:TypeError:ReferenceError: a is not defined at http://xxxx.js:1:13window.addEventListener('error', event => (){// 處理錯(cuò)誤信息},false);// true代表在捕獲階段調(diào)用史飞,false代表在冒泡階段捕獲尖昏。使用true或false都可以,默認(rèn)為false

2构资、資源加載錯(cuò)誤使用addEventListener去監(jiān)聽error事件捕獲

實(shí)現(xiàn)原理:當(dāng)一項(xiàng)資源(如<img>或<script>)加載失敗抽诉,加載資源的元素會(huì)觸發(fā)一個(gè)Event接口的error事件,并執(zhí)行該元素上的onerror()處理函數(shù)吐绵。

這些error事件不會(huì)向上冒泡到window迹淌,不過能被window.addEventListener在捕獲階段捕獲。

但這里需要注意拦赠,由于上面提到了addEventListener也能夠捕獲js錯(cuò)誤巍沙,因此需要過濾避免重復(fù)上報(bào),判斷為資源錯(cuò)誤的時(shí)候才進(jìn)行上報(bào)荷鼠。

window.addEventListener('error', event => (){// 過濾js errorlettarget = event.target || event.srcElement;letisElementTarget = targetinstanceofHTMLScriptElement || targetinstanceofHTMLLinkElement || targetinstanceofHTMLImageElement;if(!isElementTarget)returnfalse;// 上報(bào)資源地址leturl = target.src || target.href;console.log(url);},true);

3句携、未處理的promise錯(cuò)誤處理方式

實(shí)現(xiàn)原理:當(dāng)promise被reject并且錯(cuò)誤信息沒有被處理的時(shí)候,會(huì)拋出一個(gè)unhandledrejection允乐。

這個(gè)錯(cuò)誤不會(huì)被window.onerror以及window.addEventListener('error')捕獲矮嫉,但是有專門的window.addEventListener('unhandledrejection')方法進(jìn)行捕獲處理削咆。

window.addEventListener('rejectionhandled', event => {// 錯(cuò)誤的詳細(xì)信息在reason字段// demo:settimeout errorconsole.log(event.reason);});

4、fetch與xhr錯(cuò)誤的捕獲

對(duì)于fetch和xhr蠢笋,我們需要通過改寫它們的原生方法拨齐,在觸發(fā)錯(cuò)誤時(shí)進(jìn)行自動(dòng)化的捕獲和上報(bào)。

改寫fetch方法:

// fetch的處理function_errorFetchInit(){if(!window.fetch)return;let_oldFetch =window.fetch;window.fetch =function(){return_oldFetch.apply(this,arguments)? ? ? ? .then(res=>{if(!res.ok) {// 當(dāng)status不為2XX的時(shí)候昨寞,上報(bào)錯(cuò)誤}returnres;? ? ? ? })// 當(dāng)fetch方法錯(cuò)誤時(shí)上報(bào).catch(error=>{// error.message,// error.stack// 拋出錯(cuò)誤并且上報(bào)throwerror;? ? ? ? })? ? }}

對(duì)于XMLHttpRequest的重寫:

xhr改寫

// xhr的處理function_errorAjaxInit(){letprotocol =window.location.protocol;if(protocol ==='file:')return;// 處理XMLHttpRequestif(!window.XMLHttpRequest) {return;? ? ? }letxmlhttp =window.XMLHttpRequest;// 保存原生send方法let_oldSend = xmlhttp.prototype.send;let_handleEvent =function(event){try{if(event && event.currentTarget && event.currentTarget.status !==200) {// event.currentTarget 即為構(gòu)建的xhr實(shí)例// event.currentTarget.response// event.currentTarget.responseURL || event.currentTarget.ajaxUrl// event.currentTarget.status// event.currentTarget.statusText});? ? ? ? ? ? }? ? ? ? }catch(e) {vaconsole.log('Tool\'s error: '+ e);? ? ? ? }? ? }? ? xmlhttp.prototype.send =function(){this.addEventListener('error', _handleEvent);// 失敗this.addEventListener('load', _handleEvent);// 完成this.addEventListener('abort', _handleEvent);// 取消return_oldSend.apply(this,arguments);? ? }}

關(guān)于responseURL 的說明

需要特別注意的是瞻惋,當(dāng)請求完全無法執(zhí)行的時(shí)候,XMLHttpRequest會(huì)收到status=0 和 statusText=null的返回援岩,此時(shí)responseURL也為空string歼狼。

另外在安卓4.4及以下版本的webview中,xhr對(duì)象也不存在responseURL屬性享怀。

因此我們需要額外的改寫xhr的open方法羽峰,將傳入的url記錄下來,方便上報(bào)時(shí)帶上添瓷。

var_oldOpen = xmlhttp.prototype.open;// 重寫open方法,記錄請求的urlxmlhttp.prototype.open =function(method, url){? ? _oldOpen.apply(this,arguments);this.ajaxUrl = url;};

其他問題

1梅屉、其他框架,例如vue項(xiàng)目的錯(cuò)誤捕獲

vue內(nèi)部發(fā)生的錯(cuò)誤會(huì)被Vue攔截鳞贷,因此vue提供方法給我們處理vue組件內(nèi)部發(fā)生的錯(cuò)誤坯汤。

Vue.config.errorHandler?=function(err,?vm,?info){//?handle?error//?`info`?是?Vue?特定的錯(cuò)誤信息,比如錯(cuò)誤所在的生命周期鉤子//?只在?2.2.0+?可用}

2悄晃、script error的解決方式

"script error.”有時(shí)也被稱為跨域錯(cuò)誤玫霎。當(dāng)網(wǎng)站請求并執(zhí)行一個(gè)托管在第三方域名下的腳本時(shí),就可能遇到該錯(cuò)誤妈橄。最常見的情形是使用 CDN 托管 JS 資源庶近。

其實(shí)這并不是一個(gè) JavaScript Bug。出于安全考慮眷蚓,瀏覽器會(huì)刻意隱藏其他域的 JS 文件拋出的具體錯(cuò)誤信息鼻种,這樣做可以有效避免敏感信息無意中被不受控制的第三方腳本捕獲。

因此沙热,瀏覽器只允許同域下的腳本捕獲具體錯(cuò)誤信息叉钥,而其他腳本只知道發(fā)生了一個(gè)錯(cuò)誤,但無法獲知錯(cuò)誤的具體內(nèi)容篙贸。

解決方案1:(推薦)

添加 crossorigin="anonymous" 屬性投队。

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

添加跨域 HTTP 響應(yīng)頭:

Access-Control-Allow-Origin: *

或者

Access-Control-Allow-Origin: http://test.com

注意:大部分主流 CDN 默認(rèn)添加了 Access-Control-Allow-Origin 屬性。

完成上述兩步之后,即可通過 window.onerror 捕獲跨域腳本的報(bào)錯(cuò)信息扒披。

解決方案2

難以在 HTTP 請求響應(yīng)頭中添加跨域?qū)傩詴r(shí)值依,還可以考慮 try catch 這個(gè)備選方案。

在如下示例 HTML 頁面中加入 try catch:

<!doctype html>Test page in http://test.com// app.js里面有一個(gè)foo方法碟案,調(diào)用了不存在的bar方法window.onerror =function(message, url, line, column, error){console.log(message, url, line, column, error);? ? }try{? ? ? ? foo();? ? }catch(e) {console.log(e);throwe;? ? }// 運(yùn)行輸出結(jié)果如下:=> ReferenceError: bar is not definedat 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)捕獲的異常辆亏。

總結(jié)

上述的錯(cuò)誤捕獲基本覆蓋了前端監(jiān)控所需的錯(cuò)誤場景,但是第三部分指出的兩個(gè)其他問題熔任,目前解決的方式都不太完美褒链。

對(duì)于有使用框架的項(xiàng)目:一是需要有額外的處理流程,比如示例中就需要單獨(dú)為vue項(xiàng)目進(jìn)行初始化疑苔;二是對(duì)于其他框架,都需要單獨(dú)處理甸鸟,例如react項(xiàng)目的話惦费,則需要使用官方提供的componentDidCatch方法來做錯(cuò)誤捕獲。

而對(duì)于跨域js捕獲的問題:我們并不能保證所有的跨域靜態(tài)資源都添加跨域 HTTP 響應(yīng)頭抢韭;而通過第二種包裹try-catch的方式進(jìn)行上報(bào)薪贫,則需要考慮的場景繁多并且無法保證沒有遺漏。

雖然存在這兩點(diǎn)不足刻恭,但前端錯(cuò)誤捕獲這部分還是和項(xiàng)目的使用場景密切相關(guān)的瞧省。我們可以在了解這些方式以后,選擇最適合自己項(xiàng)目的方案鳍贾,為自己的監(jiān)控工具服務(wù)鞍匾。

—— —— 參考文檔 —— ——

1.Using XMLHttpRequest:?

https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest

2.script error 產(chǎn)生的原因和解決辦法:?

https://www.alibabacloud.com/help/zh/faq-detail/88579.htm

3.JavaScript執(zhí)行錯(cuò)誤:?

https://docs.fundebug.com/notifier/javascript/type/javascript.html

4.betterjs的script error:?

https://github.com/BetterJS/badjs-report/issues/3

5.Vuejs的errorHandler:?

https://cn.vuejs.org/v2/api/index.html#errorHandler

6.React的componentDidCatch:?

https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市骑科,隨后出現(xiàn)的幾起案子橡淑,更是在濱河造成了極大的恐慌,老刑警劉巖咆爽,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件梁棠,死亡現(xiàn)場離奇詭異,居然都是意外死亡斗埂,警方通過查閱死者的電腦和手機(jī)符糊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來呛凶,“玉大人男娄,你說我怎么就攤上這事。” “怎么了沪伙?”我有些...
    開封第一講書人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵瓮顽,是天一觀的道長。 經(jīng)常有香客問我围橡,道長暖混,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任翁授,我火速辦了婚禮拣播,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘收擦。我一直安慰自己贮配,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開白布塞赂。 她就那樣靜靜地躺著泪勒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪宴猾。 梳的紋絲不亂的頭發(fā)上圆存,一...
    開封第一講書人閱讀 49,985評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音仇哆,去河邊找鬼沦辙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛讹剔,可吹牛的內(nèi)容都是我干的油讯。 我是一名探鬼主播,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼延欠,長吁一口氣:“原來是場噩夢啊……” “哼陌兑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起衫冻,我...
    開封第一講書人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤诀紊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后隅俘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體邻奠,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年为居,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了碌宴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蒙畴,死狀恐怖贰镣,靈堂內(nèi)的尸體忽然破棺而出呜象,到底是詐尸還是另有隱情,我是刑警寧澤碑隆,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布恭陡,位于F島的核電站,受9級(jí)特大地震影響上煤,放射性物質(zhì)發(fā)生泄漏休玩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一劫狠、第九天 我趴在偏房一處隱蔽的房頂上張望拴疤。 院中可真熱鬧,春花似錦独泞、人聲如沸呐矾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蜒犯。三九已至,卻和暖如春孕惜,著一層夾襖步出監(jiān)牢的瞬間愧薛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來泰國打工衫画, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瓮栗。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓削罩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親费奸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子弥激,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • 前端一直是距離用戶最近的一層,隨著產(chǎn)品的日益完善辛孵,我們會(huì)更加注重用戶體驗(yàn)丛肮,而前端異常卻如鯁在喉,甚是煩人魄缚。 一宝与、為...
    易懂程序猿閱讀 810評(píng)論 0 0
  • 個(gè)人博客:https://yeaseonzhang.github.io 花了半個(gè)多月的時(shí)間焚廊,終于又把“JS紅寶書”...
    Yeaseon閱讀 1,745評(píng)論 2 23
  • 學(xué)會(huì)相信,學(xué)會(huì)感恩自己习劫,學(xué)會(huì)包容自己咆瘟,學(xué)會(huì)接受自己最真實(shí)的樣子。 每天使用1%的時(shí)間诽里,進(jìn)步1%袒餐,成為1%的人 第1...
    1d8e109700b7閱讀 294評(píng)論 0 0
  • 一提到奢侈品,你會(huì)想到什么须肆?Lorna在分享之初就把我們的興致提了起來匿乃。 我所想到的奢侈品一個(gè)是貴,二是品質(zhì)豌汇,三是...
    邊蓉Carol閱讀 307評(píng)論 0 0
  • 當(dāng)你考學(xué)時(shí)幢炸,被同學(xué)們質(zhì)疑;當(dāng)你考證時(shí)拒贱,被同事們質(zhì)疑宛徊; 當(dāng)你旅行時(shí),被朋友們質(zhì)疑逻澳; 當(dāng)你創(chuàng)業(yè)時(shí)闸天,被親人們質(zhì)疑; 當(dāng)你...
    周朝陽閱讀 335評(píng)論 1 0