從 0 到 1 搭建前端異常監控系統

本篇文章讀後,你將GET的技能: javascript

●收集前端錯誤(原生、React、Vue)html

●編寫錯誤上報邏輯前端

●利用Egg.js編寫一個錯誤日誌採集服務vue

●編寫webpack插件自動上傳sourcemapjava

●利用sourcemap還原壓縮代碼源碼位置 react

●利用Jest進行單元測試 webpack

有沒有心動的感受?趕忙學起來吧!程序員

如何捕獲異常

JS異常:

js異常的特色是,出現不會致使JS引擎崩潰,最多隻會終止當前執行的任務。web

好比一個頁面有兩個按鈕,若是點擊按鈕致使頁面發生異常,這個時候頁面不會崩潰。ajax

只是這個按鈕的功能失效,其餘按鈕還會有效☟

上面的例子咱們用setTimeout分別啓動了兩個任務。

雖然第一個任務執行了一個錯誤的方法。程序執行中止了。可是另一個任務並無收到影響。

其實若是你不打開控制檯都看不到發生了錯誤。好像是錯誤是在靜默中發生的。

下面咱們來看看這樣的錯誤該如何收集。

try-catch:

JS做爲一門高級語言咱們首先想到的使用try-catch來收集。

若是在函數中錯誤沒有被捕獲,錯誤會上拋。

image.png

控制檯中打印出的分別是錯誤信息和錯誤堆棧。

讀到這裏你們可能會想那就在最底層作一個錯誤try-catch不就行了嗎。

確實做爲一個從java轉過來的程序員也是這麼想的。

可是理想很豐滿,現實很骨感。咱們看看下一個例子。

image.png

你們注意運行結果,異常並無被捕獲。

這是由於JS的try-catch功能很是有限一遇到異步就很差用了。

那總不能爲了收集錯誤給全部的異步都加一個try-catch吧,太坑爹了。

其實你想一想異步任務其實也不是由代碼形式上的上層調用的就好比本例中的setTimeout。

你們想一想eventloop就明白啦,其實這些異步函數都是就比如一羣沒孃的孩子出了錯誤找不到家大人。

固然我也想過一些黑魔法來處理這個問題好比代理執行或者用過的異步方法。

算了仍是仍是再看看吧。

異常任務捕獲

window.onerror:

window.onerror 最大的好處就是同步任務、異步任務均可捕獲。

image.png

onerror返回值

onerror還有一個問題你們要注意 若是返回true 就不會被上拋了。

否則控制檯中還會看到錯誤日誌。

監聽error事件:

文件中的位置☟

window.addEventListener('error',() => {})

其實 onerror 當然好可是仍是有一類異常沒法捕獲。這就是網絡異常的錯誤。

好比下面的例子。

<img src="./xxxxx.png">

試想一下咱們若是頁面上要顯示的圖片忽然不顯示了,而咱們渾然不知那就是麻煩了。

addEventListener就是☟

運行結果以下☟

Promise異常捕獲:

Promise 的出現主要是爲了讓咱們解決回調地域問題。基本是咱們程序開發的標配了。

雖然咱們提倡使用 es7 async/await 語法來寫。

可是不排除不少祖傳代碼仍是存在Promise寫法。

new Promise((resolve, reject) => {
  abcxxx()
});

這種狀況不管是onerror仍是監聽錯誤事件都是沒法捕獲的。

image.png

除非每一個Promise都添加一個catch方法。

但顯然,咱們不能這樣作。

window.addEventListener("unhandledrejection", e => {
 console.log('unhandledrejection',e)
});

咱們能夠考慮將unhandledrejection事件捕獲的錯誤拋出交由錯誤事件統一處理就能夠了。

async/await異常捕獲:

實際上async/await語法本質仍是Promise語法。

區別就是async方法能夠被上層的try/catch捕獲。

image.png

若是不去捕獲的話就會和Promise同樣,須要用unhandledrejection事件捕獲。

這樣的話咱們只須要在全局增長unhandlerejection就行了。

image.png

小結:

實際上咱們能夠將unhandledrejection事件拋出的異常再次拋出就能夠統一經過error事件進行處理了。

最終用代碼表示以下:

前端工程化

Webpack工程化:

如今是前端工程化的時代,工程化導出的代碼通常都是被壓縮混淆後的。

好比:

setTimeout(() => {
    xxx(1223)
}, 1000)

image.png

出錯的代碼指向被壓縮後的JS文件,而JS文件長下圖這個樣子。

image.png

若是想將錯誤和原有的代碼關聯起來,那就須要sourcemap文件的幫忙了。

sourceMap是什麼?

簡單說,sourceMap就是一個文件,裏面儲存着位置信息。

仔細點說,這個文件裏保存的,是轉換後代碼的位置,和對應的轉換前的位置。

那麼如何利用sourceMap還原異常代碼發生的位置這個問題,咱們到異常分析這個章節再講。

VUE 工程

利用vue-cli工具直接建立一個項目。

image.png

爲了測試的須要咱們暫時關閉eslint 這裏面仍是建議你們全程打開eslint。

在vue.config.js進行配置

image.png

咱們故意在(文件位置☟)

src/components/HelloWorld.vue

這個時候 錯誤會在控制檯中被打印出來,可是錯誤事件並無監聽到。

errorHandle 句柄:

爲了對Vue發生的異常進行統一的上報,須要利用vue提供的errorHandle句柄。

一旦Vue發生異常都會調用這個方法。

咱們在src/main.js

image.png

React 工程:

npx create-react-app react-sample

cd react-sample

yarn start

咱們用useEffect hooks 製造一個錯誤:

而且在src/index.js中增長錯誤事件監聽邏輯:

window.addEventListener('error', args => {
    console.log('error', error)
})

可是從運行結果看雖然輸出了錯誤日誌可是仍是服務捕獲。

Error Boundary 組件

錯誤邊界僅能夠捕獲其子組件的錯誤。

錯誤邊界沒法捕獲其自身的錯誤。

若是一個錯誤邊界沒法渲染錯誤信息,則錯誤會向上冒泡至最接近的錯誤邊界。

這也相似於 JavaScript 中 catch {} 的工做機制。

建立ErrorBoundary組件

在src/index.js中包裹App標籤☟

最終運行的結果:

異常上報如何選擇通信方式

動態建立img標籤:

其實上報就是要將捕獲的異常信息發送到後端。最經常使用的方式首推進態建立標籤方式。

由於這種方式無需加載任何通信庫,並且頁面是無需刷新的。

基本上目前包括百度統計 Google統計都是基於這個原理作的埋點。

new Image().src = 'http://localhost:7001/monitor/error'+ '?info=xxxxxx'

經過動態建立一個img,瀏覽器就會向服務器發送get請求。

能夠把你須要上報的錯誤數據放在querystring字符串中,利用這種方式就能夠將錯誤上報到服務器了。

Ajax上報:

實際上咱們也能夠用ajax的方式上報錯誤,這和咱們在業務程序中並無什麼區別。

上報哪些數據:

上報哪些數據:

咱們先看一下error事件參數:

其中核心的應該是錯誤棧,其實咱們定位錯誤最主要的就是錯誤棧。

錯誤堆棧中包含了絕大多數調試有關的信息。其中包括了異常位置(行號,列號),異常信息

上報數據序列化:

因爲通信的時候只能以字符串方式傳輸,咱們須要將對象進行序列化處理。

大概分紅如下三步:

一、將異常數據從屬性中解構出來,存入一個JSON對象

二、將JSON對象轉換爲字符串

三、將字符串轉換爲Base64

固然在後端也要作對應的反向操做 這個咱們後面再說。

image.png

異常上報的後端服務器

搭建egg.js工程:

異常上報的數據必定是要有一個後端服務接收才能夠。

咱們就以比較流行的開源框架eggjs爲例來演示

# 全局安裝egg-cli
npm i egg-init -g 

# 建立後端項目
egg-init backend --type=simple

cd backend
npm i

# 啓動項目
npm run dev

編寫error上傳接口:

首先在app/router.js添加一個新的路由

image.png

建立一個新的:

controller (app/controller/monitor)

image.png

看一下接收後的結果 ☟

image.png

記入日誌文件:

下一步就是將錯誤記入日誌。實現的方法能夠本身用fs寫,也能夠藉助log4js這樣成熟的日誌庫。

固然在eggjs中是支持咱們定製日誌那麼就用這個功能定製一個前端錯誤日誌好了。

在/config/config.default.js中增長一個定製日誌配置

image.png

在/app/controller/monitor.js中添加日誌記錄:

image.png

最後實現的效果:

image.png

Webpack插件實現SourceMap上傳

談到異常分析最重要的工做實際上是將webpack混淆壓縮的代碼還原。

建立Webpack插件:

/source-map/plugin(文件位置)

加載webpack插件:

webpack.config.js(文件位置)

添加sourceMap讀取邏輯:

在apply函數中增長讀取sourcemap文件的邏輯

/plugin/uploadSourceMapWebPlugin.js

實現http上傳功能:

服務器端添加上傳接口:

/backend/app/router.js(文件位置)

image.png

添加sourcemap上傳接口:

/backend/app/controller/monitor.js

image.png

最終效果:

執行webpack打包時調用插件sourcemap被上傳至服務器。

image.png

解析ErrorStack

考慮到這個功能須要較多邏輯,咱們準備把他開發成一個獨立的函數而且用Jest來作單元測試:

先看一下咱們的需求☟

image.png

搭建Jest框架:

image.png

首先建立一個/utils/stackparser.js文件☟

image.png

在同級目錄下建立測試文件stackparser.spec.js

以上需求咱們用Jest表示就是

image.png

整理以下:

下面咱們運行Jest

npx jest stackparser --watch

image.png

顯示運行失敗,緣由很簡單由於咱們尚未實現對吧。

下面咱們就實現一下這個方法

反序列Error對象:

首先建立一個新的Error對象 將錯誤棧設置到Error中。

而後利用error-stack-parser這個npm庫來轉化爲stackFrame

image.png

運行效果以下☟

image.png

解析ErrorStack:

下一步咱們將錯誤棧中的代碼位置轉換爲源碼位置

image.png

咱們再用Jest測試一下☟

image.png

這時咱們再看一下結果:

image.png

這樣一來測試就經過啦~

將源碼位置記入日誌:

image.png

記錄完成後,咱們再來看一下運行效果:

image.png

結束了這一步,咱們的ErrorStack工做就完成了。

須要運用的兩種開源框架

Fundebug:

Fundebug專一於JavaScript、微信小程序、微信小遊戲、支付寶小程序、React Native、Node.js和Java線上應用實時BUG監控。 

自從2016年雙十一正式上線,Fundebug累計處理了10億+錯誤事件,付費客戶有陽光保險、荔枝FM、掌門1對一、核桃編程、微脈等衆多品牌企業。

Sentry:

Sentry 是一個開源的實時錯誤追蹤系統,能夠幫助開發者實時監控並修復異常問題。

它主要專一於持續集成、提升效率而且提高用戶體驗。

Sentry 分爲服務端和客戶端 SDK,前者能夠直接使用它家提供的在線服務,也能夠本地自行搭建;

後者提供了對多種主流語言和框架的支持,包括 React、Angular、Node、Django、RoR、PHP、Laravel、Android、.NET、JAVA 等。

同時它可提供了和其餘流行服務集成的方案,例如 GitHub、GitLab、bitbuck、heroku、slack、Trello 等。

目前公司的項目也都在逐步應用上 Sentry 進行錯誤日誌管理。

總結:

截止到目前爲止,咱們把前端異常監控的基本功能算是造成了一個MVP(最小化可行產品)。

後面須要升級的還有不少,對錯誤日誌的分析和可視化方面可使用ELK。

發佈和部署能夠採用Docker。對eggjs的上傳和上報最好要增長權限控制功能。

相關文章
相關標籤/搜索