如何監控網頁崩潰

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

寸志:如何監控網頁的卡頓?

如何監控網頁崩潰

崩潰和卡頓有何差別?

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

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

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

load 與 beforeunload 事件

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

http://jasonjl.me/blog/2015/06/21/taking-action-on-browser-crashes/

jasonjl.me

window.addEventListener('load', function () {

sessionStorage.setItem('good_exit', 'pending');

setInterval(function () {

sessionStorage.setItem('time_before_crash', new Date().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'));

}

一圖勝千言:

如何監控網頁崩潰

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

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

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

但這個方案有問題:

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

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

基於 Service Worker 的崩潰統計方案

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

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

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

如何監控網頁崩潰

  • 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) {

let HEARTBEAT_INTERVAL = 5 * 1000; // 每五秒發一次心跳

let sessionId = uuid();

let heartbeat = 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 需要的數據,比如當前頁面的地址等等。

const CHECK_CRASH_INTERVAL = 10 * 1000; // 每 10s 檢查一次

const CRASH_THRESHOLD = 15 * 1000; // 15s 超過15s沒有心跳則認為已經 crash

const pages = {}

let timer

function checkCrash() {

const now = Date.now()

for (var id in pages) {

let page = pages[id]

if ((now - page.t) > CRASH_THRESHOLD) {

// 上報 crash

delete pages[id]

}

}

if (Object.keys(pages).length == 0) {

clearInterval(timer)

timer = null

}

}

worker.addEventListener('message', (e) => {

const data = e.data;

if (data.type === 'heartbeat') {

pages[data.id] = {

t: Date.now()

}

if (!timer) {

timer = setInterval(function () {

checkCrash()

}, CHECK_CRASH_INTERVAL)

}

} else if (data.type === 'unload') {

delete pages[data.id]

}

})

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

方案的可行性

兼容性:

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

如何監控網頁崩潰

Service Worker 兼容性

可靠性:

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

對瀏覽器廠商的建議

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


分享到:


相關文章: