本篇文章讀後,你將GET的技能: javascript
●收集前端錯誤(原生、React、Vue)html
●編寫錯誤上報邏輯前端
●利用Egg.js編寫一個錯誤日誌採集服務vue
●編寫webpack插件自動上傳sourcemapjava
●利用sourcemap還原壓縮代碼源碼位置 react
●利用Jest進行單元測試 webpack
有沒有心動的感受?趕忙學起來吧!程序員
js異常的特色是,出現不會致使JS引擎崩潰,最多隻會終止當前執行的任務。web
好比一個頁面有兩個按鈕,若是點擊按鈕致使頁面發生異常,這個時候頁面不會崩潰。ajax
只是這個按鈕的功能失效,其餘按鈕還會有效☟
上面的例子咱們用setTimeout分別啓動了兩個任務。
雖然第一個任務執行了一個錯誤的方法。程序執行中止了。可是另一個任務並無收到影響。
其實若是你不打開控制檯都看不到發生了錯誤。好像是錯誤是在靜默中發生的。
下面咱們來看看這樣的錯誤該如何收集。
JS做爲一門高級語言咱們首先想到的使用try-catch來收集。
若是在函數中錯誤沒有被捕獲,錯誤會上拋。
控制檯中打印出的分別是錯誤信息和錯誤堆棧。
讀到這裏你們可能會想那就在最底層作一個錯誤try-catch不就行了嗎。
確實做爲一個從java轉過來的程序員也是這麼想的。
可是理想很豐滿,現實很骨感。咱們看看下一個例子。
你們注意運行結果,異常並無被捕獲。
這是由於JS的try-catch功能很是有限一遇到異步就很差用了。
那總不能爲了收集錯誤給全部的異步都加一個try-catch吧,太坑爹了。
其實你想一想異步任務其實也不是由代碼形式上的上層調用的就好比本例中的setTimeout。
你們想一想eventloop就明白啦,其實這些異步函數都是就比如一羣沒孃的孩子出了錯誤找不到家大人。
固然我也想過一些黑魔法來處理這個問題好比代理執行或者用過的異步方法。
算了仍是仍是再看看吧。
window.onerror 最大的好處就是同步任務、異步任務均可捕獲。
onerror返回值
onerror還有一個問題你們要注意 若是返回true 就不會被上拋了。
否則控制檯中還會看到錯誤日誌。
文件中的位置☟
window.addEventListener('error',() => {})
其實 onerror
當然好可是仍是有一類異常沒法捕獲。這就是網絡異常的錯誤。
好比下面的例子。
<img src="./xxxxx.png">
試想一下咱們若是頁面上要顯示的圖片忽然不顯示了,而咱們渾然不知那就是麻煩了。
addEventListener就是☟
運行結果以下☟
Promise 的出現主要是爲了讓咱們解決回調地域問題。基本是咱們程序開發的標配了。
雖然咱們提倡使用 es7 async/await 語法來寫。
可是不排除不少祖傳代碼仍是存在Promise寫法。
new Promise((resolve, reject) => { abcxxx() });
這種狀況不管是onerror仍是監聽錯誤事件都是沒法捕獲的。
除非每一個Promise都添加一個catch方法。
但顯然,咱們不能這樣作。
window.addEventListener("unhandledrejection", e => { console.log('unhandledrejection',e) });
咱們能夠考慮將unhandledrejection事件捕獲的錯誤拋出交由錯誤事件統一處理就能夠了。
實際上async/await語法本質仍是Promise語法。
區別就是async方法能夠被上層的try/catch捕獲。
若是不去捕獲的話就會和Promise同樣,須要用unhandledrejection事件捕獲。
這樣的話咱們只須要在全局增長unhandlerejection就行了。
實際上咱們能夠將unhandledrejection事件拋出的異常再次拋出就能夠統一經過error事件進行處理了。
最終用代碼表示以下:
如今是前端工程化的時代,工程化導出的代碼通常都是被壓縮混淆後的。
好比:
setTimeout(() => { xxx(1223) }, 1000)
出錯的代碼指向被壓縮後的JS文件,而JS文件長下圖這個樣子。
若是想將錯誤和原有的代碼關聯起來,那就須要sourcemap文件的幫忙了。
簡單說,sourceMap就是一個文件,裏面儲存着位置信息。
仔細點說,這個文件裏保存的,是轉換後代碼的位置,和對應的轉換前的位置。
那麼如何利用sourceMap還原異常代碼發生的位置這個問題,咱們到異常分析這個章節再講。
利用vue-cli工具直接建立一個項目。
爲了測試的須要咱們暫時關閉eslint 這裏面仍是建議你們全程打開eslint。
在vue.config.js進行配置
咱們故意在(文件位置☟)
src/components/HelloWorld.vue
這個時候 錯誤會在控制檯中被打印出來,可是錯誤事件並無監聽到。
爲了對Vue發生的異常進行統一的上報,須要利用vue提供的errorHandle句柄。
一旦Vue發生異常都會調用這個方法。
咱們在src/main.js
npx create-react-app react-sample cd react-sample yarn start
咱們用useEffect hooks 製造一個錯誤:
而且在src/index.js中增長錯誤事件監聽邏輯:
window.addEventListener('error', args => { console.log('error', error) })
可是從運行結果看雖然輸出了錯誤日誌可是仍是服務捕獲。
錯誤邊界僅能夠捕獲其子組件的錯誤。
錯誤邊界沒法捕獲其自身的錯誤。
若是一個錯誤邊界沒法渲染錯誤信息,則錯誤會向上冒泡至最接近的錯誤邊界。
這也相似於 JavaScript 中 catch {} 的工做機制。
建立ErrorBoundary組件
在src/index.js中包裹App標籤☟
最終運行的結果:
其實上報就是要將捕獲的異常信息發送到後端。最經常使用的方式首推進態建立標籤方式。
由於這種方式無需加載任何通信庫,並且頁面是無需刷新的。
基本上目前包括百度統計 Google統計都是基於這個原理作的埋點。
new Image().src = 'http://localhost:7001/monitor/error'+ '?info=xxxxxx'
經過動態建立一個img,瀏覽器就會向服務器發送get請求。
能夠把你須要上報的錯誤數據放在querystring字符串中,利用這種方式就能夠將錯誤上報到服務器了。
實際上咱們也能夠用ajax的方式上報錯誤,這和咱們在業務程序中並無什麼區別。
咱們先看一下error事件參數:
其中核心的應該是錯誤棧,其實咱們定位錯誤最主要的就是錯誤棧。
錯誤堆棧中包含了絕大多數調試有關的信息。其中包括了異常位置(行號,列號),異常信息
因爲通信的時候只能以字符串方式傳輸,咱們須要將對象進行序列化處理。
大概分紅如下三步:
一、將異常數據從屬性中解構出來,存入一個JSON對象
二、將JSON對象轉換爲字符串
三、將字符串轉換爲Base64
固然在後端也要作對應的反向操做 這個咱們後面再說。
異常上報的數據必定是要有一個後端服務接收才能夠。
咱們就以比較流行的開源框架eggjs爲例來演示
# 全局安裝egg-cli npm i egg-init -g # 建立後端項目 egg-init backend --type=simple cd backend npm i # 啓動項目 npm run dev
首先在app/router.js添加一個新的路由
建立一個新的:
controller (app/controller/monitor)
看一下接收後的結果 ☟
下一步就是將錯誤記入日誌。實現的方法能夠本身用fs寫,也能夠藉助log4js這樣成熟的日誌庫。
固然在eggjs中是支持咱們定製日誌那麼就用這個功能定製一個前端錯誤日誌好了。
在/config/config.default.js中增長一個定製日誌配置
在/app/controller/monitor.js中添加日誌記錄:
談到異常分析最重要的工做實際上是將webpack混淆壓縮的代碼還原。
建立Webpack插件:
/source-map/plugin(文件位置)
webpack.config.js(文件位置)
在apply函數中增長讀取sourcemap文件的邏輯
/plugin/uploadSourceMapWebPlugin.js
/backend/app/router.js(文件位置)
添加sourcemap上傳接口:
/backend/app/controller/monitor.js
執行webpack打包時調用插件sourcemap被上傳至服務器。
考慮到這個功能須要較多邏輯,咱們準備把他開發成一個獨立的函數而且用Jest來作單元測試:
先看一下咱們的需求☟
首先建立一個/utils/stackparser.js文件☟
在同級目錄下建立測試文件stackparser.spec.js
以上需求咱們用Jest表示就是
整理以下:
下面咱們運行Jest
npx jest stackparser --watch
顯示運行失敗,緣由很簡單由於咱們尚未實現對吧。
下面咱們就實現一下這個方法
首先建立一個新的Error對象 將錯誤棧設置到Error中。
而後利用error-stack-parser這個npm庫來轉化爲stackFrame
運行效果以下☟
下一步咱們將錯誤棧中的代碼位置轉換爲源碼位置
咱們再用Jest測試一下☟
這時咱們再看一下結果:
這樣一來測試就經過啦~
記錄完成後,咱們再來看一下運行效果:
結束了這一步,咱們的ErrorStack工做就完成了。
Fundebug專一於JavaScript、微信小程序、微信小遊戲、支付寶小程序、React Native、Node.js和Java線上應用實時BUG監控。
自從2016年雙十一正式上線,Fundebug累計處理了10億+錯誤事件,付費客戶有陽光保險、荔枝FM、掌門1對一、核桃編程、微脈等衆多品牌企業。
Sentry 是一個開源的實時錯誤追蹤系統,能夠幫助開發者實時監控並修復異常問題。
它主要專一於持續集成、提升效率而且提高用戶體驗。
Sentry 分爲服務端和客戶端 SDK,前者能夠直接使用它家提供的在線服務,也能夠本地自行搭建;
後者提供了對多種主流語言和框架的支持,包括 React、Angular、Node、Django、RoR、PHP、Laravel、Android、.NET、JAVA 等。
同時它可提供了和其餘流行服務集成的方案,例如 GitHub、GitLab、bitbuck、heroku、slack、Trello 等。
目前公司的項目也都在逐步應用上 Sentry 進行錯誤日誌管理。
截止到目前爲止,咱們把前端異常監控的基本功能算是造成了一個MVP(最小化可行產品)。
後面須要升級的還有不少,對錯誤日誌的分析和可視化方面可使用ELK。
發佈和部署能夠採用Docker。對eggjs的上傳和上報最好要增長權限控制功能。