原發於知乎專欄:zhuanlan.zhihu.com/ne-fe
說到監控,大家第一時間想到的肯定是 Zabbix、Nagios 等各種強大的後端監控服務。誠然,這些強大的平臺通過採集伺服器以及鏈路上各種中介軟體的資料,為我們的應用穩定起到了不可或缺的保駕護航作用。
然而在網際網路的另一端,執行在使用者終端上的程式碼卻缺少這樣強大的監控能力。
本文將從 採集、資料處理、分析、報警 4 個維度進一步闡述如何把前端監控做到極致。
小福利
如果你還沒有使用前端監控服務,那麼可以先看看這個小福利。只用兩行程式碼就能打造一個前端異常實時監控平臺,還帶報錯數統計功能。
其實現思路正如開題所言,通過 window.onerror 採集到所有的未捕獲異常,並通過 new Image 的方式構造一個 404 的 HTTP 請求,最後在服務端實時過濾 access.log 中匹配的請求並計數即可。
實際執行效果如下:
瀏覽器端效果
服務端效果
採集
Script Error
當我們採集前端報錯的時候,第一個遇到的問題就是 Script Error。Script Error 不是一種具體的錯誤,而是瀏覽器對跨域錯誤出於安全機制考慮的一種處理方式。
Webkit 原始碼中對 Script Error 的處理
簡單的說,如果你的頁面和頁面中引用的 JavaScript 檔案不同源(協議、域名、埠不一致),那麼這些指令碼丟擲的錯誤都屬於跨域錯誤。那麼我們在做前端監控捕獲這些錯誤的時候,應該怎麼避免採集到 Script Error 呢?
crossorigin 生效需要伺服器端和瀏覽器端同時支援。伺服器端支援比較簡單,即返回跨域指令碼的伺服器(一般為 CDN 伺服器)正確的帶上 CORS 響應頭 —— Access-Control-Allow-Origin: * —— 即可,目前常見的 CDN 服務均支援這一特性。而瀏覽器端的支援情況就沒有這麼樂觀了。
crossorigin 屬性前端支援情況
突破跨域報錯限制
那麼怎樣能突破 crossorigin 的這些限制,儘可能的捕獲到更詳細的錯誤呢?
另一種解決方案是通過 Patch 原生方法來儘可能的捕獲到錯誤,這也是很多監控指令碼預設提供的能力。比如說我們可以通過如下程式碼來 Patch 原生的 setTimeout 方法:
const prevSetTimeout = window.setTimeout;
window.setTimeout = function(callback, timeout) {
const self = this;
return prevSetTimeout(function() {
try {
callback.call(this);
} catch (e) {
// 捕獲到詳細的錯誤,在這裡處理日誌上報等了邏輯
// ...
throw e;
}
}, timeout);
} 複製程式碼
同理,我們還可以 Patch 更多的原生方法,比如 Array.prototype.forEach、setInterval、requestAnimationFrame等等。
誠然這種方法能幫我們儘可能捕獲到更多異常,但是因為 Patch 了 JavaScript 原生的方法,總是感覺會存在很多的不確定性。
在這裡還要提一下去年 QCon 上百姓網前端同學劉小杰提出的一種基於 Babel 的自動新增 try...catch... 的方法,感興趣的同學可以去深入看看,會有不少啟發。
框架層解決方案
在不少現代前端框架中,都提供了框架層的異常處理方案,比如 AngularJS 的 ErrorHandler 和 Vue 的 Vue.config.errorHandler。在這裡我們以 React 16 的 componentDidCatch 為例,說明如何使用框架的能力採集錯誤。
以下是 React 官網中的示例:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
this.setState({ hasError: true });
// 在這裡可以做異常的上報
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
複製程式碼
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
複製程式碼
資料處理
傳統的監控服務一般都會使用 MySQL 等資料庫進行資料持久化,但當資料量指數級增長時,MySQL 這種 OLTP 資料庫已經不再適合用來提供監控資料分析服務。
在大資料時代,搭建一套標準化的、針對監控業務的大資料解決方案已經不是什麼難事,下圖即為一個簡單的資料架構示意圖:
在資料處理過程中,值得一提的是資料取樣率的功能設計。
不難看出目前的取樣率設計方案都或多或少存在缺陷和妥協,那麼有沒有一種更優的解決方案呢?
經過大量的實踐後,我們認為在日誌服務進入資料處理流程之前進行取樣率控制是比較理想的方案,理由如下:
- 日誌寫入成本低
- rotate 機制保證儲存不會浪費
- 瞭解真實打點請求資料量
- 避免採集端繞過取樣率限制
分析
解決了資料採集和處理的問題,我們應該怎麼著手進行分析呢?讓我們先看一個真實案例:
當你吃著火鍋唱著歌的時候,突然看到實時監控資料暴漲,這個時候你的第一反應是什麼呢?是不是手足無措不知道應該怎麼處理?當線上出現緊急狀況時,我們的首要思路是找到問題觸發的特徵,比如是否集在某個頁面或者某種瀏覽器等等。
通過監控平臺提供的分析功能,初步定為到問題原因後,再進行深入的調查。
報錯數高一定是不穩定嗎
這裡試舉兩個反例來說明報錯數高不一定就是前端不穩定。
如上圖所示,雖然該應用 1 天爆出了上萬的 JavaScript 異常,但是我們在分析過程中發現,95% 的報錯都集中在 3 個 userId 上。再對這 3 個 userId 進行深入的調查不難發現,這是 3 個爬取資料的爬蟲賬號,不巧爬資料的指令碼寫的有 Bug,被前端監控系統忠實的捕捉到了。
又如上圖所示,某天的資料出現暴增,可能是因為頁面的訪問量出現暴增。
前端發生故障最常見的原因就是新發布的版本存在 Bug,那麼這種問題在監控平臺中如何提供分析思路呢?
當然,也並不是所有的波動都是前端變更引起。比如說後端介面突然故障,也會導致前端因為無法讀取到某個介面結果而報錯。
報警
說到報警,絕大多數的監控平臺都提供規則報警的能力。然而規則報警最大的問題在於隨著業務的不斷髮展,原本配置的規則將會出現閾值過低或過高的問題。若閾值配置過低,則會產生大量的誤報警,繼而引起整個監控能力的報警疲勞。
結語
前端監控看似簡單,但想要監控真正發揮價值,還需要從各個方面進行不斷的優化和打磨。當然,最重要的是,要意識到前端監控的必要性,及早開始進行監控,才能更好的避免線上故障的產生。
如果你對我們正在做的事情有興趣,歡迎加入阿里巴巴和我們一起親手打造屬於自己的資料產品,簡歷請發 shuangyang.ys@alibaba-inc.com