如何監(jiān)控網(wǎng)頁崩潰酬土?

轉(zhuǎn):https://zhuanlan.zhihu.com/p/40273861
利用serviceWorker.

本文是如何監(jiān)控網(wǎng)頁的卡頓?的下篇格带。今天我們把話題聚焦在如何監(jiān)控網(wǎng)頁的崩潰上撤缴。

崩潰和卡頓有何差別?

卡頓也就是網(wǎng)頁暫時(shí)響應(yīng)比較慢践惑,JS 可能無法及時(shí)執(zhí)行腹泌,這也是上篇網(wǎng)頁卡頓監(jiān)控所依賴的技術(shù)點(diǎn)。

但崩潰就不一樣了尔觉,網(wǎng)頁都崩潰了凉袱,頁面看不見了,JS 都不運(yùn)行了侦铜,還有什么辦法可以監(jiān)控網(wǎng)頁的崩潰专甩,并將網(wǎng)頁崩潰上報(bào)呢?

但钉稍,天無絕人之路涤躲,方法總是有的。

load 與 beforeunload 事件

搜遍互聯(lián)網(wǎng)贡未,幾乎找不到方法种樱,最終碰上了這篇文章。本文利用 window 對(duì)象的 load 和 beforeunload 事件實(shí)現(xiàn)了網(wǎng)頁崩潰的監(jiān)控俊卤。

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

  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'));
   }

一圖勝千言:

image.png

<figcaption style="margin-top: calc(0.666667em); padding: 0px 1em; font-size: 0.9em; line-height: 1.5; text-align: center; color: rgb(153, 153, 153);">使用 load 和 beforeunload 事件實(shí)現(xiàn)崩潰監(jiān)控</figcaption>

這個(gè)方案巧妙的利用了頁面崩潰無法觸發(fā) beforeunload 事件來實(shí)現(xiàn)的嫩挤。

在頁面加載時(shí)(load 事件)在 sessionStorage 記錄 good_exit 狀態(tài)為 pending,如果用戶正常退出(beforeunload 事件)狀態(tài)改為 true消恍,如果 crash 了岂昭,狀態(tài)依然為 pending,在用戶第2次訪問網(wǎng)頁的時(shí)候(第2個(gè)load事件)狠怨,查看 good_exit 的狀態(tài)约啊,如果仍然是 pending 就是可以斷定上次訪問網(wǎng)頁崩潰了!

但這個(gè)方案有問題:

  1. 采用 sessionStorage 存儲(chǔ)狀態(tài)佣赖,但通常網(wǎng)頁崩潰/卡死后恰矩,用戶會(huì)強(qiáng)制關(guān)閉網(wǎng)頁或者索性重新打開瀏覽器,sessionStorage 存儲(chǔ)但狀態(tài)將不復(fù)存在憎蛤;
  2. 如果將狀態(tài)存儲(chǔ)在 localStorage 甚至 Cookie 中枢里,如果用戶先后打開多個(gè)網(wǎng)頁,但不關(guān)閉,good_exit 存儲(chǔ)的一直都是 pending栏豺,完了彬碱,每有一次網(wǎng)頁打開,就會(huì)有一個(gè) crash 上報(bào)奥洼。

全民直播 一開始采用的就是這個(gè)方案巷疼,發(fā)現(xiàn)就算頁面做了優(yōu)化,crash 不下降灵奖,與 PV 保持比例嚼沿,才意識(shí)到這個(gè)方案的問題之處。

基于 Service Worker 的崩潰統(tǒng)計(jì)方案

隨著 PWA 概念的流行瓷患,大家對(duì) Service Worker 也逐漸熟悉起來骡尽。基于以下原因擅编,我們可以使用 Service Worker 來實(shí)現(xiàn)網(wǎng)頁崩潰的監(jiān)控:

  1. Service Worker 有自己獨(dú)立的工作線程攀细,與網(wǎng)頁區(qū)分開,網(wǎng)頁崩潰了爱态,Service Worker 一般情況下不會(huì)崩潰谭贪;
  2. Service Worker 生命周期一般要比網(wǎng)頁還要長耳高,可以用來監(jiān)控網(wǎng)頁的狀態(tài)排抬;
  3. 網(wǎng)頁可以通過 navigator.serviceWorker.controller.postMessage API 向掌管自己的 SW 發(fā)送消息哆键。

基于以上幾點(diǎn)凡傅,我們可以實(shí)現(xiàn)一種基于心跳檢測(cè)的監(jiān)控方案:

[圖片上傳失敗...(image-73fddc-1658822703499)]

  • p1:網(wǎng)頁加載后,通過 postMessage API 每 5s 給 sw 發(fā)送一個(gè)心跳挫望,表示自己的在線驾锰,sw 將在線的網(wǎng)頁登記下來扮碧,更新登記時(shí)間磁椒;
  • p2:網(wǎng)頁在 beforeunload 時(shí)凑阶,通過 postMessage API 告知自己已經(jīng)正常關(guān)閉,sw 將登記的網(wǎng)頁清除衷快;
  • p3:如果網(wǎng)頁在運(yùn)行的過程中 crash 了,sw 中的 running 狀態(tài)將不會(huì)被清除姨俩,更新時(shí)間停留在奔潰前的最后一次心跳蘸拔;
  • sw:Service Worker 每 10s 查看一遍登記中的網(wǎng)頁,發(fā)現(xiàn)登記時(shí)間已經(jīng)超出了一定時(shí)間(比如 15s)即可判定該網(wǎng)頁 crash 了环葵。

一些簡(jiǎn)化后的檢測(cè)代碼调窍,給大家作為參考:

// 頁面 JavaScript 代碼
if (navigator.serviceWorker.controller !== null) {
  let HEARTBEAT_INTERVAL = 5 * 1000; // 每五秒發(fā)一次心跳
  let sessionId = uuid();
  let heartbeat = function () {
    navigator.serviceWorker.controller.postMessage({
      type: 'heartbeat',
      id: sessionId,
      data: {} // 附加信息,如果頁面 crash张遭,上報(bào)的附加數(shù)據(jù)
    });
  }
  window.addEventListener("beforeunload", function() {
    navigator.serviceWorker.controller.postMessage({
      type: 'unload',
      id: sessionId
    });
  });
  setInterval(heartbeat, HEARTBEAT_INTERVAL);
  heartbeat();
}

  • **sessionId **本次頁面會(huì)話的唯一 id邓萨;
  • postMessage 附帶一些信息,用于上報(bào) crash 需要的數(shù)據(jù),比如當(dāng)前頁面的地址等等缔恳。
const CHECK_CRASH_INTERVAL = 10 * 1000; // 每 10s 檢查一次
const CRASH_THRESHOLD = 15 * 1000; // 15s 超過15s沒有心跳則認(rèn)為已經(jīng) 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) {
      // 上報(bào) 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]
  }
})

都挺簡(jiǎn)單的代碼宝剖,不細(xì)說了歉甚。

方案的可行性

兼容性:

Service Worker 的普及率已經(jīng)相當(dāng)高了纸泄,鑒于國內(nèi)各種瀏覽器都是 Chrome 內(nèi)核,而且版本已經(jīng)在 Chrome 45 以上聘裁,已經(jīng)覆蓋了相當(dāng)一部分用戶雪营。作為監(jiān)控衡便,數(shù)據(jù)覆蓋大部分就好。

image.png

可靠性:

這應(yīng)該是我目前已知可以相對(duì)準(zhǔn)確判斷出網(wǎng)頁崩潰的方式了征唬。不過我們的方案還在測(cè)試環(huán)境总寒,上線一段時(shí)間后再給大家共享數(shù)據(jù)摄闸。

對(duì)瀏覽器廠商的建議

題圖的 Crash 列表年枕,可以在 Chrome 中訪問 chrome://crashes/ 看到乎完,如果廠商可以提供一個(gè) API树姨,在頁面打開時(shí),可以獲知用戶上一次崩潰的信息就很棒了硝清!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末芦拿,一起剝皮案震驚了整個(gè)濱河市蔗崎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌裙盾,老刑警劉巖番官,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件徘熔,死亡現(xiàn)場(chǎng)離奇詭異酷师,居然都是意外死亡山孔,警方通過查閱死者的電腦和手機(jī)荷憋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門串前,熙熙樓的掌柜王于貴愁眉苦臉地迎上來荡碾,“玉大人坛吁,你說我怎么就攤上這事拨脉∷芫叮” “怎么了统舀?”我有些...
    開封第一講書人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵碉就,是天一觀的道長瓮钥。 經(jīng)常有香客問我碉熄,道長锈津,這世上最難降的妖魔是什么琼梆? 我笑而不...
    開封第一講書人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任茎杂,我火速辦了婚禮煌往,結(jié)果婚禮上携冤,老公的妹妹穿的比我還像新娘。我一直安慰自己菜循,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開白布癌幕。 她就那樣靜靜地躺著衙耕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪勺远。 梳的紋絲不亂的頭發(fā)上橙喘,一...
    開封第一講書人閱讀 52,337評(píng)論 1 310
  • 那天厅瞎,我揣著相機(jī)與錄音饰潜,去河邊找鬼彭雾。 笑死,一個(gè)胖子當(dāng)著我的面吹牛吴菠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蜂挪,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼刺覆,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼严肪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起谦屑,我...
    開封第一講書人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤驳糯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后酝枢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體帘睦,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年坦康,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了竣付。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡滞欠,死狀恐怖古胆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情筛璧,我是刑警寧澤逸绎,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布妖滔,位于F島的核電站,受9級(jí)特大地震影響桶良,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜沮翔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一陨帆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧采蚀,春花似錦疲牵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至妆够,卻和暖如春识啦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背神妹。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來泰國打工颓哮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鸵荠。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓冕茅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蛹找。 傳聞我的和親對(duì)象是個(gè)殘疾皇子姨伤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容