淺談前端異常監控平臺實現方案

異常捕獲是改善軟件質量的跟蹤手段之一,常見的方式是記錄日志,從日志分析異常問題進而跟進。對于前端項目來說,異常可能是后端接口數據導致,可能是前端本身業務邏輯問題導致,不管是什么導致的異常,只要能夠精準的捕獲到就能夠分析出問題所在。可能有小伙說有測試階段,全面的測試機制的確能夠降低異常的出現,但是測試大部份情況是在非生產環境上進行的,覆蓋面有限。
日志是收集異常的最佳方式,一個異常監控平臺就需要包括異常采集、異常存儲、異常統計與分析、異常報告、異常告警,而對于一個通用平臺來說,就需要項目管理、版本管理、團隊管理、倉庫管理等等。本文主要介紹一下異常采集需要考慮的問題,并跟大家分享兩種現成的解決方案。
異常介紹
異常,是每種編程語言都需要考慮的一種結構,如何友好的跟蹤異常而不影響生產環境上的業務,這就需要從項目開發到上線整個過程做一定的規范。下面就來談談前端的異常及處理方式。
異常分類
先來說說JavaScript的錯誤類型,ECMA-262 定義了 7 種錯誤類型,說明如下:
Error:普通異常,通常與 throw 語句和try/catch 語句一起使用,利用屬性 name 可以聲明或了解異常的類型,利用message 屬性可以設置和讀取異常的詳細信息。
EvalError:Eval 函數執行異常。
SyntaxError:語法解析不合理,即語法錯誤。
RangeError:在數字超出合法范圍時拋出,比如數組下標越界就會報這種錯誤。
ReferenceError:在讀取不存在的變量時拋出,比如沒定義變量 a,后面卻使用這個變量 a,就會報這種錯。
TypeError:當一個值的類型錯誤時拋出該異常,比如傳遞給函數的參數與預期的不符,就會報這種錯誤。
URIError:以一種錯誤的方式使用全局 URI 處理函數而產生的錯誤
異常處理
前端捕獲異常分為全局捕獲和單點捕獲。全局捕獲代碼集中,易于管理;單點捕獲作為補充,對某些特殊情況進行捕獲,但分散,不利于管理,容易遺漏。在項目開發過程中,定義一個錯誤捕獲模塊,將項目所有的異常(全局異常和單點異常)都交給錯誤模塊來統一處理,這就需要項目約定。
try-catch
try-catch 語句,是 JavaScript 處理異常的一種標準方式。基本語法如下:
try {
} catch (error) {
// 錯誤處理
}
try 塊中的代碼發生了錯誤,就會立即退出代碼執行過程,然后執行 catch 塊。catch 塊會接收到一個包含錯誤信息的對象。一般是error.message。
finally
finally 在 try-catch 語句中是可選的,如果 finally 子句已經使用,則其代碼無論如何都會執行。無論 try 或 catch 語句塊中包含什么代碼——甚至 return 語句,都不會阻止 finally 子句的執行。只要代碼中包含 finally 子句,那么無論 try 還是 catch 語句塊中的 return 語句都將被忽略。因此,在使用 finally 子句之前,一定要非常清楚想讓代碼怎么樣。看下面這個函數:
const errorHelper = () => {
try {
return devpoint;
} catch (error) {
return "error";
} finally {
return "不管有無錯誤,我都執行了!";
}
};
console.log(errorHelper()); // 函數本身是發生了異常,但是最終打印的結果為:不管有無錯誤,我都執行了!
上面的函數代碼實際上是有異常的,因為變量 devpoint 并沒有定義,不過最終執行了 finally 子句輸出了 不管有無錯誤,我都執行了!。
throw
與 try-catch 語句相配的 throw 操作符,用于隨時的主動拋出自定義錯誤。
const errorHelper = () => {
try {
return devpoint;
} catch (error) {
return "error";
} finally {
throw new Error("devpoint變量未定義");
}
};
console.log(errorHelper());
window.onerror
window.onerror,是全局異常捕獲,對于單點異常捕獲不到的異常就到這里了。
異常采集
觸發異常有很多原因,為了更好的分析,除了捕獲程序的錯誤信息外,還需要采集執行程序的外部環境,對于前端項目,外部環境就包括系統(Window、IOS、Android)和系統版本、瀏覽器(Chrome、IE、火狐等)和版本、IP地址、用戶信息、運行的頁面、網絡環境、API接口數據。針對這些信息就需要設計采集的日志結構。
在采集異常日志的時候,有個原則需要注意:采集日志行為不影響用戶體驗及應用本身的性能。
下面是一個參考的日志結構:
projectId:項目信息eventId:事件ID,日志的唯一標志stack:錯誤stack信息requestId:開發者定義的異常標志level:異常級別,可以是 error、info、warnbrowser:瀏覽器信息device:設備信息os:操作系統信息release:應用版本信息url:異常觸發頁面urluser:用戶信息,可以是iPcreateAt:異常產生時間network:網絡信息eventKey:觸發的鍵dataRes:API響應數據screenWidth:屏幕寬度screenHeight:屏幕高度message:異常詳細信息
異常上報
收集到異常數據如何上報呢?即需要將異常日志收集到云端存儲,供項目開發跟進分析,一種方式是直接通過API異步上報,在捕獲信息比較多的情況下,還是會占用網絡請求,影響應用本身。可以考慮將采集的異常日志存儲在本地,最佳的選擇是IndexedDB,容量大,支持異步操作,可以自定義查詢。
IndexedDB 是WEB離線存儲的一種方式,因此存儲只是暫時的,還需要設計一個同步機制,將本地存儲的日志同步到云端服務器上。為了更好的同步,就需要設計暫存區、歸檔區,新產生的日志存儲在暫存區,已成功同步的日志存儲在歸檔區。有了本地存儲,同步的過程批量同步。
后端存儲,可以考慮使用leveldb,在性能方面,基本可以碾壓了mongodb和sqlite。
LevelDB是google公司開發出來的一款超高性能kv存儲引擎,以其驚人的讀性能和更加驚人的寫性能在輕量級nosql數據庫中鶴立雞群,此開源項目目前是支持處理十億級別規模Key-Value型數據持久性存儲的C++ 程序庫。在優秀的表現下對于內存的占用也非常小,大量數據都直接存儲在磁盤上,可以理解為以空間換取時間。
第三方平臺
上面簡單介紹實現異常監控平臺的幾個關鍵點,現在就跟大家分享兩個可以用于前端異常跟蹤的工具Google Analytics 和 Sentry 。
Google Analytics
沒錯,Google Analytics一般想到的是用于網站流量統計分析。可以借助Google Analytics的事件統計來跟蹤異常,下面是簡單的方法:
function postEvents(error) {
var category = error.level || "warn",
action = error.action || "",
label = error.message || "";
ga("send", "event", category, action, label);
}
缺點就是無法方便的確定觸發異常的環境條件,后續也無法跟蹤版本等等。
Sentry
sentry 是一個實時事件日志記錄和聚合平臺。它專門用于監視錯誤和提取執行適當的事后操作所需的所有信息, 而無需使用標準用戶反饋循環的任何麻煩。
這是一個比較專業的異常監控工具,基本支持所有主流編程語言,這里只是簡單介紹一個前端的使用。
首先在頁面上加入以下腳本:
<script src="https://cdn.ravenjs.com/3.20.1/raven.min.js" crossorigin="anonymous"></script>
<script type="text/javascript">
try {
if ((typeof Raven) != "undefined"){
Raven.config('https://ffd39f4582184540a7214fb82bb3e888@sentry.io/248888',{
release: 'release_0.0.5',
allowSecretKey: true
}).install()
}
} catch (error) {
}
</script>
然后項目中可以寫一個統一的入口:
function ExceptionJs() {
const ravenJs = typeof Raven != "undefined" ? Raven : {};
this.capture = function (error) {
try {
if (!(error instanceof Error)) {
if (typeof error === "object") {
error = JSON.stringify(error);
}
}
if (typeof ravenJs.captureException === "function") {
ravenJs.captureException(error);
}
} catch (e) {
// 這里是確保異常跟蹤腳本出錯了不至于影響應用程序
}
};
}
在需要的位置加入以下代碼:
const exceptionHelper = new ExceptionJs(),
try {
} catch (error) {
exceptionHelper.capture(error);
}
現在來看看收集上來的異常信息:
下面這個是異常的統計

異常列表

異常詳情

sentry 工具還提供了異常跟蹤處理的功能,有興趣的小伙伴可以去嘗試體驗一下。