聊聊前端監控——錯誤監控篇


當有人問起:你們的公司的這款應用用戶體驗怎么樣呀?訪問量怎么樣?此時,你該怎么回答呢?你會回答:UV、PV 巴拉巴拉,秒開率、FP、TTI 巴拉巴拉。

那么,這些數據是哪里來的呢?顯而易見,這些數據都來自前端監控系統。

前端監控的意義

當今時代,是一個快節奏的時代,應用的性能極大影響着用戶的留存率,沒有用戶會忍受一個卡到爆的應用。而監控應用性能的重擔,就由前端監控系統肩負着。

其次,對於線上應用來說,故障是不可避免的,對於高日活的應用來說,每次故障都意味着大量的損失。試想,如果是淘寶掛了一天,那么損失是多么慘痛。所以,對於開發人員來說,必須要盡早發現線上故障,而不是等到客戶打爆客服的電話才發現。線上錯誤監控,也是前端監控的任務之一。

最后,作為商業公司,需要根據用戶行為和數據進行分析,進一步制定各種策略,如果沒有各種數據,那么 BI 會熱情的找你談談人生。而這些數據,也是前端監控系統獲取的。

總而言之,前端監控肩負着:性能監控、錯誤監控以及數據上報等功能,無論對於大公司還是小公司,可以說是必不可缺的了。

今天,我們先來聊聊前端監控中的錯誤監控。

錯誤監控概述

一般來說,按照錯誤監控錯誤監控可以分為:腳本錯誤監控、請求錯誤監控以及資源錯誤監控。

腳本錯誤監控

腳本錯誤大體可以分為兩種:編譯時錯誤以及運行時錯誤。其中,編譯時錯誤一般在開發階段就會發現,配合 lint 工具比如 eslint、tslint 等以及 git 提交插件比如 husky 等,基本可以保證線上代碼不出現低級的編譯時錯誤。大廠一般都有發布前置檢測平台,能夠在發布前提前發現編譯時錯誤,當然,原理依然和之前所說的類似。

而發現並上報運行時錯誤就是前端檢測平台的本質工作啦,一般來說,腳本錯誤監控指的就是運行時錯誤監控。

說到腳本錯誤監控,你想到的第一個是什么?對,就是 try catch !

在編寫 JavaScript 時,我們為了防止出現錯誤阻塞程序,我們會通過 try catch 捕獲錯誤,對於錯誤捕獲,這是最簡單也是最通用的方案。

但是,try catch 捕獲錯誤是侵入式的,需要在開發代碼時即提前進行處理,而作為一個監控系統,無法做到在所有可能產生錯誤的代碼片段中都嵌入 try catch。所以,我們需要全局捕獲腳本錯誤。

常規腳本錯誤

當頁面出現腳本錯誤時,就會產生 onerror 事件,我們只需捕獲該事件即可。

/** * @description window.onerror 全局捕獲錯誤 * @param event 錯誤信息,如果是 * @param source 錯誤源文件URL * @param lineno 行號 * @param colno 列號 * @param error Error對象 */ window.onerror = function (event, source, lineno, colno, error) { // 上報錯誤 // 如果不想在控制台拋出錯誤,只需返回 true 即可 }; 

可以發現,各種錯誤監控所需的信息,如錯誤信息、錯誤源文件的 URL、錯誤行號、錯誤列號都被回調函數所傳入。

但是,window.onerror 有兩個缺點:

  1. 只能綁定一個回調函數,如果想在不同文件中想綁定不同的回調函數,window.onerror 顯然無法完成;同時,不同回調函數直接容易造成互相覆蓋。
  2. 回調函數的參數過於離散,使用不方便

所以,一般情況下,我們使用 addEventListener 來代替。

/** * @param event 事件名 * @param function 回調函數 * @param useCapture 回調函數是否在捕獲階段執行,默認是false,在冒泡階段執行 */ window.addEventListener('error', (event) => { // addEventListener 回調函數的離散參數全部聚合在 error 對象中 // 上報錯誤 }, true) 

tips:在一些特殊情況下,我們依然需要使用 window.onerror。比如,不期望在控制台拋出錯誤時,因為只有 window.onerror 才能阻止拋出錯誤到控制台

Promise 錯誤

使用了這兩種方法,是不是可以捕獲所有腳本錯誤了呢?這個問題再幾年前其實是正確的,但是隨着前端技術的發展,出現了 Promise 這項技術,而使用這兩種常規方法無法捕獲 Promise 錯誤。

和常規腳本錯誤的捕獲一樣,我們只需捕獲 Promise 對應的錯誤事件即可。而 Promise 錯誤事件有兩種,unhandledrejection 以及 rejectionhandled

當 Promise 被 reject 且沒有 reject 處理器的時候,會觸發 unhandledrejection 事件。

當 Promise 被 reject 且有 reject 處理器的時候,會觸發 rejectionhandled 事件。

// unhandledrejection 推薦處理方案 window.addEventListener('unhandledrejection', (event) => { console.log(event) }, true); // unhandledrejection 備選處理方案 window.onunhandledrejection = function (error) { console.log(error) } // rejectionhandled 推薦處理方案 window.addEventListener('rejectionhandled', (event) => { console.log(event) }, true); // rejectionhandled 備選處理方案 window.onrejectionhandled = function (error) { console.log(error) } 

框架錯誤

由於我 React 使用的不多,所以在此只討論下 Vue 的框架錯誤處理,如果有大佬了解 React 的框架錯誤處理,歡迎補充~

在 Vue 中,框架提供了 errorHandler 這個 API 來捕獲並處理錯誤。

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

值得一提的是,框架錯誤指的不是框架層面的錯誤,而是指框架提供了 API 來捕獲全局錯誤。

請求錯誤監控

一般來說,前端請求有兩種方案,使用 ajax 或者 fetch ,所以只需重寫兩種方法,進行代理,即可實現請求錯誤監控。

代理的核心在於使用 apply 重新執行原有方法,並且在執行原有方法之前進行監聽操作。在請求錯誤監控中,我們關心三種錯誤事件:abort,error 以及 timeout,所以,只需在代理中對這三種事件進行統一處理即可。

tips:如果能夠統一使用一種請求工具,如 axios 等,那么不需要重寫 ajax 或者 fetch 只需在請求攔截器以及響應攔截器進行處理上報即可

資源錯誤監控

資源錯誤監控本質上和常規腳本錯誤監控一樣,都是監控錯誤事件實現錯誤捕獲。

那么如果區分腳本錯誤還是資源錯誤呢?我們可以通過 instanceof 區分,腳本錯誤參數對象 instanceof ErrorEvent,而資源錯誤的參數對象 instanceof Event

值得一提的是,由於 ErrorEvent 繼承於 Event ,所以不管是腳本錯誤還是資源錯誤的參數對象,它們都 instanceof Event,所以,需要先判斷腳本錯誤。

此外,兩個參數對象之間有一些細微的不同,比如,腳本錯誤的參數對象中包含 message ,而資源錯誤沒有,這些都可以作為判斷資源錯誤或者腳本錯誤的依據。

/** * @param event 事件名 * @param function 回調函數 * @param useCapture 回調函數是否在捕獲階段執行,默認是false,在冒泡階段執行 */ window.addEventListener('error', (event) => { if (event instanceof ErrorEvent) { console.log('腳本錯誤') } else if (event instanceof Event) { console.log('資源錯誤') } }, true); 

tips:使用 addEventListener 捕獲資源錯誤時,一定要將 useCapture 即第三個選項設為 true,因為資源錯誤沒有冒泡,所以只能在捕獲階段捕獲。同理,由於 window.onerror 是通過在冒泡階段捕獲錯誤,所以無法捕獲資源錯誤。

補充:跨域腳本錯誤捕獲

為了性能方面的考慮,我們一般會將腳本文件放到 CDN ,這種方法會大大加快首屏時間。但是,如果腳本報錯,此時,瀏覽器出於於安全方面的考慮,對於不同源的腳本報錯,無法捕獲到詳細錯誤信息,只會顯示 Script Error。那么,有解決該問題的方案嗎?

  1. 方案一:所有的腳本全部放到同一源下,但是,該方案會放棄 CDN ,降低性能。
  2. 方案二:在 script 標簽中,添加 crossorigin 屬性(推薦使用 webpack 插件自動添加);同時,配置 CDN 服務器,為跨域腳本配上 CORS

可以發現,方案二基本可以完美解決跨域腳本錯誤捕獲的問題。但是,其實該方案有一個隱藏的坑,即兼容性問題,crossorigin 屬性對於 IE 以及 Safari 支持程度不高。

所以,該如何真正完美的解決跨域腳本錯誤捕獲問題?

終極解決方案:對所有原生方法進行代理~

但是,一方面,很難覆蓋所有的原生方法,另一方面,對原生方法進行代理容易出現無法預知的問題。

綜合所有方案,看起來還是方案二最靠譜,至於低級瀏覽器,就讓它們隨風消逝吧~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM