監(jiān)控系統(tǒng)創(chuàng)建

https://juejin.cn/post/7046756775252459528
https://juejin.cn/post/6936562262480158728#heading-2
https://cloud.tencent.com/developer/article/1983779
https://zhuanlan.zhihu.com/p/495649475

為什么要?jiǎng)?chuàng)建前端監(jiān)控系統(tǒng)

主要是為了解決兩種問(wèn)題:

  • 如何及時(shí)發(fā)現(xiàn)問(wèn)題
  • 如何快速定位并解決問(wèn)題

前端監(jiān)控體系主要需要做的事情

  • 頁(yè)面整體訪問(wèn)情況,pv、uv、用戶(hù)行為上報(bào)
  • 頁(yè)面的性能情況,包括:加載耗時(shí)、接口耗時(shí)統(tǒng)計(jì)
  • 灰度發(fā)布和有效的監(jiān)控能力,方便及時(shí)發(fā)現(xiàn)問(wèn)題
  • 用戶(hù)反饋問(wèn)題,需要足夠的日志定位

總結(jié)起來(lái)就是兩點(diǎn):數(shù)據(jù)收集、數(shù)據(jù)上報(bào)

數(shù)據(jù)收集

一般是三個(gè)方面

  • 穩(wěn)定性(頁(yè)面穩(wěn)定性/異常):js錯(cuò)誤(js執(zhí)行錯(cuò)誤、promise異常)、資源錯(cuò)誤(js、css加載異常)、接口錯(cuò)誤(ajax、fetch請(qǐng)求錯(cuò)誤)、白屏
  • 流暢性(頁(yè)面訪問(wèn)速度)
  • 用戶(hù)行為回放(外部服務(wù)調(diào)用情況):pv、uv、用戶(hù)在某個(gè)頁(yè)面的停留時(shí)間

1、穩(wěn)定性(頁(yè)面穩(wěn)定性/異常)

1)腳本錯(cuò)誤

有兩類(lèi),分別為【語(yǔ)法錯(cuò)誤】、【運(yùn)行時(shí)錯(cuò)誤】,下面是主要監(jiān)控方式:

  • try catch:可以用在預(yù)知情況下監(jiān)控特定錯(cuò)誤,但發(fā)生語(yǔ)法錯(cuò)誤或者異步錯(cuò)誤時(shí),無(wú)法捕捉,常與window.onerror結(jié)合使用
  • window.onerror:捕獲JS運(yùn)行時(shí)錯(cuò)誤
  • window.addEventListener('unhandledrejection'):捕獲promise未處理reject的錯(cuò)誤,即promise被reject了,但是沒(méi)有reject處理器,則會(huì)走到這一步(https://developer.mozilla.org/zh-CN/docs/Web/API/Window/unhandledrejection_event
  • window.addEventListener('error'):資源加載錯(cuò)誤(js、css加載異常),但是他也能上報(bào)js運(yùn)行時(shí)錯(cuò)誤, 所以判斷只有是script、link、image時(shí)才去拿數(shù)據(jù)

為避免重復(fù)上報(bào)js運(yùn)行時(shí)錯(cuò)誤,此時(shí)只有event.srcElement inatanceof HTMLScriptElement或HTMLLinkElement或HTMLImageElement時(shí)才進(jìn)行數(shù)據(jù)采集。

// try catch 語(yǔ)法錯(cuò)誤
try {
    function empty()   // <-  throw error 語(yǔ)法錯(cuò)誤
} catch(e){
    console.log('語(yǔ)法錯(cuò)誤信息 ↙');
    console.log(e);
}
// try catch 異步錯(cuò)誤
try {
    setTimeout(function() {
        test // <- throw error 異步錯(cuò)誤
    },0)
} catch(e){
    console.log('異步錯(cuò)誤信息 ↙');
    console.log(e);
}
2)跨域資源的腳本報(bào)錯(cuò)Script error

常見(jiàn)的形式就是,我本來(lái)的網(wǎng)站域名是a.com,然后,里面使用script引入了一個(gè)cdn連接,cdn連接域名是b.com,這個(gè)時(shí)候在調(diào)用cdn里的方法時(shí)就會(huì)報(bào)Script error,但是獲取不到完整的信息,只能獲取到類(lèi)似于:"Script error.", "", 0, 0, undefined的信息

解決方式:

  • 為頁(yè)面上script標(biāo)簽添加crossorigin屬性
// 這一步告訴瀏覽器,目標(biāo)腳本通過(guò)匿名方式獲取。這意味著請(qǐng)求腳本時(shí)沒(méi)有潛在的用戶(hù)身份信息(如cookies、HTTP 證書(shū)等)發(fā)送到服務(wù)端
<script src="http://another-domain.com/app.js" crossorigin="anonymous"></script>
  • 響應(yīng)頭中增加 Access-Control-Allow-Origin 來(lái)支持跨域資源共享,使用*或者上面本來(lái)的網(wǎng)站即http://a.com
  • 當(dāng) Access-Control-Allow-Origin的值為http://a.com時(shí),響應(yīng)頭中需帶上Vary:Origin。避免引緩存導(dǎo)致的權(quán)限問(wèn)題。(ps:為*的時(shí)候無(wú)所謂,因?yàn)樗械挠蛎甲屗フ?qǐng)求了)

通過(guò)以上方式可以使用window.onerro獲取到具體的報(bào)錯(cuò)信息

3)接口異常

通過(guò)重寫(xiě)XMLHttpRequest和fetch的原生方法來(lái)實(shí)現(xiàn)

  • 重寫(xiě)XMLHttpRequest的open、send方法
  • 監(jiān)聽(tīng)load、error、abort事件
window.addEventListener("error", handler("error"), false);

4)資源加載異常

頁(yè)面內(nèi)的圖片、css、JS等Assets資源加載失敗,會(huì)在error事件里可以拿到資源加載失敗回調(diào):

window.addEventListener( 'error ', function(e){
//排除JSError
    if( ! (e instanceof ErrorEvent)){
//資源路徑
        e.target.src || e.target.href
        //資源類(lèi)型
        e.target.tagName
    }
}, true) // 為true則是捕獲階段處理函數(shù),否則為冒泡階段處理函數(shù)
5)白屏問(wèn)題

頁(yè)面加載過(guò)程中因?yàn)槿我鈫?wèn)題導(dǎo)致渲染沒(méi)有完成,呈現(xiàn)空白頁(yè)面的異常

一般基于不同方案會(huì)有不同的處理方式

1、基于mutationObserver:頁(yè)面加載完成后3s,頁(yè)面沒(méi)有節(jié)點(diǎn)變化(節(jié)點(diǎn)不變不代表不白屏)
2、基于native容器:頁(yè)面加載完成后3s,頁(yè)面還是全屏白色像素(依賴(lài)容器)
3、基于pointTiming:頁(yè)面加載完成后3s,頁(yè)面沒(méi)有fisrt-point,下方為實(shí)現(xiàn)(兼容性不好)
  • document.elementsFromPoint方法可以獲取到當(dāng)前視口內(nèi)指定坐標(biāo)處,由里到外排列的所有元素
  • 根據(jù) elementsFromPoint api,獲取屏幕水平中線和豎直中線所在的元素(9點(diǎn)監(jiān)測(cè))
image.png
 for (let i = 1; i <= 9; i++) {
      xElements = document.elementsFromPoint(
        (window.innerWidth * i) / 10,
        window.innerHeight / 2
      );
      yElements = document.elementsFromPoint(
        window.innerWidth / 2,
        (window.innerHeight * i) / 10
      );
      isWrapper(xElements[0]);
      isWrapper(yElements[0]);
    }
6)crash

頁(yè)面因?yàn)閮?nèi)存溢出、死循環(huán)等原因?qū)е卤罎⒌漠惓?/p>

1、基于LocalStroage里的頁(yè)面離開(kāi)狀態(tài)
  • 在頁(yè)面加載時(shí),標(biāo)記開(kāi)始加載
  • 在pagehide或者beforunload時(shí)標(biāo)記離開(kāi),二次進(jìn)入頁(yè)面時(shí)判斷是否正常離開(kāi)
  • 埋點(diǎn)發(fā)送滯后,起不到監(jiān)控告警作用
2、基于native
  • 監(jiān)控webview進(jìn)程狀態(tài),發(fā)送crash日志
  • 依賴(lài)容器
3、基于service worker
  • html請(qǐng)求進(jìn)入service worker時(shí),標(biāo)記頁(yè)面開(kāi)始加載
  • 每隔一段時(shí)間像sw發(fā)送一次心跳,當(dāng)一段時(shí)間沒(méi)有收到心跳則表示crash了
  • 兼容性差,一個(gè)頁(yè)面只能有一個(gè)sw,如果頁(yè)面也需要使用sw,則需進(jìn)行嵌入,切換頁(yè)面又可能pause

2、流暢性(頁(yè)面訪問(wèn)速度)

image.png
1)卡頓

響應(yīng)用戶(hù)交互的響應(yīng)時(shí)間如果大于100ms,用戶(hù)就會(huì)感覺(jué)卡頓

卡頓:頁(yè)面整個(gè)生命周期中,主線程持續(xù)執(zhí)行某一個(gè)任務(wù)的耗時(shí)大于50ms
瀏覽器的事件隊(duì)列機(jī)制決定,要實(shí)現(xiàn)小于100毫秒的響應(yīng),應(yīng)用必須在每50毫秒內(nèi)將控制返回給主線程

  • new PerformanceObserver
  • entry.duration(持續(xù)時(shí)間) > 100 判斷大于100ms,即可認(rèn)定為長(zhǎng)任務(wù)(entry是參數(shù),詳見(jiàn)第三個(gè)鏈接)
  • 使用 requestIdleCallback上報(bào)數(shù)據(jù)
2)PV、UV、用戶(hù)停留時(shí)間

PV(page view) 是頁(yè)面瀏覽量,UV(Unique visitor)用戶(hù)訪問(wèn)量。PV 只要訪問(wèn)一次頁(yè)面就算一次,UV 同一天內(nèi)多次訪問(wèn)只算一次。
對(duì)于前端來(lái)講,只要每次進(jìn)入頁(yè)面上報(bào)一次 PV 就行,UV 的統(tǒng)計(jì)放在服務(wù)端來(lái)做

 window.addEventListener(
    "beforeunload",
    () => {},
    false// 冒泡的時(shí)候做回調(diào)
}
3)加載時(shí)間(onload時(shí)監(jiān)聽(tīng))

3、用戶(hù)行為回放(外部服務(wù)調(diào)用情況)【可以先不說(shuō)】

數(shù)據(jù)上報(bào)

主要有三種方式處理信息上報(bào)

丟點(diǎn):在瀏覽器點(diǎn)擊跳轉(zhuǎn)時(shí),跳轉(zhuǎn)前的點(diǎn)擊上報(bào)請(qǐng)求都會(huì)進(jìn)行一個(gè)三次握手,如果此時(shí),網(wǎng)絡(luò)較慢、服務(wù)器運(yùn)行緩慢或者上報(bào)請(qǐng)求還在處理階段,這時(shí),如果頁(yè)面被卸載了,瀏覽器都會(huì)自動(dòng)對(duì)當(dāng)前的請(qǐng)求進(jìn)行終止。這樣,這個(gè)http的請(qǐng)求就沒(méi)有建立,導(dǎo)致上報(bào)沒(méi)有真正發(fā)出。

延遲卸載:在卸載事件處理器中嘗試通過(guò)一個(gè)同步的 XMLHttpRequest 向服務(wù)器發(fā)送數(shù)據(jù)。這導(dǎo)致了頁(yè)面卸載被延遲。

img請(qǐng)求

  • 兼容性好
  • 部分瀏覽器可能造成丟點(diǎn)、get請(qǐng)求長(zhǎng)度限制,延遲頁(yè)面卸載
  • 就是動(dòng)態(tài)js創(chuàng)建img標(biāo)簽,把上報(bào)的url拼接,然后放在src上

Fetch/XHR(XMLHttpRequest)

  • 兼容性好
  • fetch丟點(diǎn),XHR不丟點(diǎn),但是頁(yè)面延遲卸載

Navigator.sendBeacon(推薦使用)

sendBeacon()會(huì)使用戶(hù)代理在有機(jī)會(huì)時(shí)異步地向服務(wù)器發(fā)送數(shù)據(jù),同時(shí)不會(huì)延遲頁(yè)面的卸載或影響下一導(dǎo)航的載入性能

  • 不丟點(diǎn),不會(huì)頁(yè)面延遲卸載
  • 兼容性不好
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前端的監(jiān)控主要分為三個(gè)方面: 性能監(jiān)控 白屏?xí)r間; 首屏?xí)r間; 用戶(hù)可交互時(shí)間; 總下載時(shí)間; TCP連接時(shí)間;...
    神刀閱讀 756評(píng)論 0 0
  • 前言想當(dāng)初自己也是從一個(gè)小白一步一步走到現(xiàn)在(小白中的老人 ),雖然還是一個(gè)大廠朋友都沒(méi)有,但是始終不放棄學(xué)習(xí)的機(jī)...
    livcll學(xué)院閱讀 389評(píng)論 0 0
  • 前言 最近雜七雜八的事情比較多,難得抽出時(shí)間來(lái)彌補(bǔ)一下之前的系列,欠大家的埋點(diǎn)系列現(xiàn)在開(kāi)始走起來(lái) 為什么需要埋點(diǎn)系...
    Small_Song閱讀 1,664評(píng)論 0 0
  • 前端監(jiān)控分為性能監(jiān)控和錯(cuò)誤監(jiān)控。其中監(jiān)控分為倆個(gè)環(huán)節(jié): 數(shù)據(jù)采集和數(shù)據(jù)上報(bào)。本文主要講的是如何進(jìn)行數(shù)據(jù)采集和數(shù)據(jù)上...
    Adam_5361閱讀 1,117評(píng)論 0 0
  • 1.為什要做前端監(jiān)控 更快發(fā)現(xiàn)問(wèn)題和解決問(wèn)題 做產(chǎn)品的決策依據(jù) 提升前端工程師的技術(shù)深度和廣度,打造簡(jiǎn)歷亮點(diǎn) 為業(yè)...
    niuqinyan閱讀 820評(píng)論 0 0

友情鏈接更多精彩內(nèi)容