如何監控網頁崩潰?

blank

如何監控網頁崩潰?

本文是如何監控網頁的卡頓?的下篇。今天我們把話題聚焦在如何監控網頁的崩潰上。

崩潰和卡頓有何差別?

卡頓也就是網頁暫時響應比較慢,JS可能無法及時執行,這也是上篇網頁卡頓監控所依賴的技術點。

但崩潰就不一樣了,網頁都崩潰了,頁面看不見了,JS 都不運行了,還有什麼辦法可以監控網頁的崩潰,並將網頁崩潰上報呢?

但,天無絕人之路,方法總是有的。

load與beforeunload事件

搜遍互聯網,幾乎找不到方法,最終碰上了這篇文章。本文利用window 對象的load 和beforeunload 事件實現了網頁崩潰的監控。

window.addEventListener('load',function(){sessionStorage.setItem('good_exit','pending');setInterval(function(){sessionStorage.setItem('time_before_crash',newDate().toString());},1000);});window.addEventListener('beforeunload',function(){sessionStorage.setItem('good_exit','true');});if(sessionStorage.getItem('good_exit')&&sessionStorage.getItem('good_exit')!=='true'){/*insert crash logging code here*/alert('Hey, welcome back from your crash, looks like you crashed on: '+sessionStorage.getItem('time_before_crash'));}

一圖胜千言:

blank
使用load 和beforeunload 事件實現崩潰監控

這個方案巧妙的利用了頁面崩潰無法觸發beforeunload事件來實現的。

在頁面加載時( load事件)在sessionStorage記錄good_exit狀態為pending ,如果用戶正常退出( beforeunload事件)狀態改為true ,如果crash了,狀態依然為pending ,在用戶第2次訪問網頁的時候(第2個load事件),查看good_exit的狀態,如果仍然是pending就是可以斷定上次訪問網頁崩潰了!

但這個方案有問題:

  1. 採用sessionStorage存儲狀態,但通常網頁崩潰/卡死後,用戶會強制關閉網頁或者索性重新打開瀏覽器,sessionStorage存儲但狀態將不復存在;
  2. 如果將狀態存儲在localStorage甚至Cookie中,如果用戶先後打開多個網頁,但不關閉,good_exit存儲的一直都是pending,完了,每有一次網頁打開,就會有一個crash上報。

全民直播一開始採用的就是這個方案,發現就算頁面做了優化,crash不下降,與PV保持比例,才意識到這個方案的問題之處。

基於Service Worker的崩潰統計方案

隨著PWA 概念的流行,大家對Service Worker 也逐漸熟悉起來。基於以下原因,我們可以使用Service Worker 來實現網頁崩潰的監控:

  1. Service Worker 有自己獨立的工作線程,與網頁區分開,網頁崩潰了,Service Worker 一般情況下不會崩潰;
  2. Service Worker 生命週期一般要比網頁還要長,可以用來監控網頁的狀態;
  3. 網頁可以通過navigator.serviceWorker.controller.postMessage API向掌管自己的SW發送消息。

基於以上幾點,我們可以實現一種基於心跳檢測的監控方案:

blank
  • p1:網頁加載後,通過postMessage API每5s給sw發送一個心跳,表示自己的在線,sw將在線的網頁登記下來,更新登記時間;
  • p2:網頁在beforeunload時,通過postMessage API告知自己已經正常關閉,sw將登記的網頁清除;
  • p3:如果網頁在運行的過程中crash了,sw中的running狀態將不會被清除,更新時間停留在奔潰前的最後一次心跳;
  • sw:Service Worker每10s查看一遍登記中的網頁,發現登記時間已經超出了一定時間(比如15s)即可判定該網頁crash了。

一些簡化後的檢測代碼,給大家作為參考:

//頁面JavaScript代碼if(navigator.serviceWorker.controller!==null){letHEARTBEAT_INTERVAL=5*1000;//每五秒發一次心跳letsessionId=uuid();letheartbeat=function(){navigator.serviceWorker.controller.postMessage({type:'heartbeat',id:sessionId,data:{}//附加訊息,如果頁面crash,上報的附加數據});}window.addEventListener("beforeunload",function(){navigator.serviceWorker.controller.postMessage({type:'unload',id:sessionId});});setInterval(heartbeat,HEARTBEAT_INTERVAL);heartbeat();}
  • sessionId本次頁面會話的唯一id;
  • postMessage附帶一些訊息,用於上報crash需要的數據,比如當前頁面的地址等等。
constCHECK_CRASH_INTERVAL=10*1000;//每10s檢查一次constCRASH_THRESHOLD=15*1000;// 15s超過15s沒有心跳則認為已經crashconstpages={}lettimerfunctioncheckCrash(){constnow=Date.now()for(varidinpages){letpage=pages[id]if((now-page.t)>CRASH_THRESHOLD){//上報crashdeletepages[id]}}if(Object.keys(pages).length==0){clearInterval(timer)timer=null}}worker.addEventListener('message',(e)=>{constdata=e.data;if(data.type==='heartbeat'){pages[data.id]={t:Date.now()}if(!timer){timer=setInterval(function(){checkCrash()},CHECK_CRASH_INTERVAL)}}elseif(data.type==='unload'){deletepages[data.id]}})

都挺簡單的代碼,不細說了。

方案的可行性

兼容性:

Service Worker的普及率已經相當高了,鑑於國內各種瀏覽器都是Chrome內核,而且版本已經在Chrome 45以上,已經覆蓋了相當一部分用戶。作為監控,數據覆蓋大部分就好。

blank
Service Worker 兼容性

可靠性:

這應該是我目前已知可以相對準確判斷出網頁崩潰的方式了。不過我們的方案還在測試環境,上線一段時間後再給大家共享數據。

對瀏覽器廠商的建議

題圖的Crash列表,可以在Chrome中訪問chrome://crashes/看到,如果廠商可以提供一個API,在頁面打開時,可以獲知用戶上一次崩潰的訊息就很棒了!

What do you think?

Written by marketer

blank

前端佈道者克軍確認出席中國React大會,並發表主題演講

blank

《解析GraphQL 的查詢語法》【譯】