前端錯誤日誌收集方案
公司的專案上線出現問題後難以定位錯誤,研究過現存的錯誤監控方案,受限於特殊條件只能定製自己的錯誤收集方案。
基於以上背景我擼出來一個錯誤日誌收集方案 -ofollow,noindex">Ohbug 。
歡迎各位大佬 star ~
監控錯誤
說起錯誤的捕獲,首先想到的是try catch
,通過catch
捕獲到錯誤後進一步做出處理
try { undefined.map(v => v); } catch(e) { console.log(e); // TypeError: Cannot read property 'map' of undefined } 複製程式碼
然而try catch
對於非同步產生的錯誤毫無感知
try { setTimeout(() => { undefined.map(v => v); }, 1000) } catch(e) { console.log(e); // TypeError: Cannot read property 'map' of undefined } 複製程式碼
並且在實際工作中我也不可能給所有程式碼加上try catch
,所以能否捕獲全域性的錯誤呢?
react componentDidCatch
React 16
提供了一個內建函式componentDidCatch
,使用它可以非常簡單的獲取到 react 下的錯誤資訊
componentDidCatch(error, info) { console.log(error, info); } 複製程式碼
vue errorHandler
指定元件的渲染和觀察期間未捕獲錯誤的處理函式。這個處理函式被呼叫時,可獲取錯誤資訊和 Vue 例項。
Vue.config.errorHandler = function (err, vm, info) { // handle error // `info` 是 Vue 特定的錯誤資訊,比如錯誤所在的生命週期鉤子 // 只在 2.2.0+ 可用 } 複製程式碼
onerror
vsaddEventListener
對於沒有使用 react 或 vue 的專案可以通過onerror
或addEventListener
監控全域性錯誤(當然使用 react 或 vue 的專案同樣可以)
onerror
或addEventListener
都可以捕獲到一些未知的錯誤,然而這兩個有什麼區別呢?
window.onerror = (msg, url, row, col, error) => { console.log({msg, url, row, col, error}); }; setTimeout(() => { undefined.map(v => v); }, 1000); 複製程式碼
window.addEventListener('error', (e) => { console.log(e); }, true); 複製程式碼
除此之外,addEventListener
還可以捕獲資源載入錯誤、未 catch 的 promise 錯誤。
// 捕獲未 catch 的 promise 錯誤 window.addEventListener("unhandledrejection", e => { e.preventDefault(); console.log(e); }); Promise.reject('promiseError'); 複製程式碼
ajax/fetch 錯誤監控
想要監控請求失敗,上面的方法肯定是不可取的了。
使用axios
的小夥伴可以通過配置攔截器實現錯誤的監控。
// 新增請求攔截器 axios.interceptors.request.use(function (config) { // 在傳送請求之前做些什麼 return config; }, function (error) { // 對請求錯誤做些什麼 return Promise.reject(error); }); // 新增響應攔截器 axios.interceptors.response.use(function (response) { // 對響應資料做點什麼 return response; }, function (error) { // 對響應錯誤做點什麼 return Promise.reject(error); }); 複製程式碼
這裡我採用了重新封裝XMLHttpRequest
/fetch
物件的方法實現對網路請求的監控。
XMLHttpRequest
const AJAX = { // 記錄請求的 url reqUrl: '', // 記錄請求的方法 reqMethod: '', // 儲存原生的 open 方法 xhrOpen: window.XMLHttpRequest.prototype.open, // 儲存原生的 send 方法 xhrSend: window.XMLHttpRequest.prototype.send, init() { const that = this; window.XMLHttpRequest.prototype.open = function () { that.reqUrl = arguments[1]; that.reqMethod = arguments[0]; that.xhrOpen.apply(this, arguments); }; window.XMLHttpRequest.prototype.send = function () { this.addEventListener('readystatechange', function () { if (this.readyState === 4) { if (!this.status || this.status >= 400) { // 錯誤收集 } } }); that.xhrSend.apply(this, arguments); }; }, }; AJAX.init(); 複製程式碼
fetch
const FETCH = { backup: window.fetch, init() { window.fetch = function (url, conf) { return ( FETCH.backup.apply(this, arguments) .then((res) => { if (!res.status || res.status >= 400) { // 錯誤收集 } return res; }) ); }; }, }; FETCH.init(); 複製程式碼