一、前端監控現狀
近年來,前端監控是越來越火,目前已經有很多成熟的産品供我們選擇使用,如下圖所示
有這麼多監控平台,那為什麼還要學習自研前端監控?
一方面人家是要錢的
另一方面自己的項目需要定制化的功能。
二、前端監控的目的
提升使用者體驗
更快的發現發現異常、定位異常、解決異常
了解業務資料,指導産品更新——資料驅動的思想
三、前端監控的流程
3.1 采集
前端監控的第一個步驟就是資料采集,采集的資訊包含環境資訊、性能資訊、異常資訊、業務資訊。
3.1.1 環境資訊
環境資訊是每個監控系統必備的内容,畢竟排查問題的時候需要知道來自哪個頁面、浏覽器是誰、操作使用者是誰……,這樣才能快速定位問題,解決問題。一般這些常見的環境資訊主要包含:
url:正在監控的頁面,該頁面可能會出現性能、異常問題。擷取方式為:
window.location.href
ua:通路該頁面時該使用者的userAgent資訊,包含作業系統和浏覽器的類型、版本等。擷取方式為:
window.navigator.userAgent
token:記錄目前使用者是誰。通過記錄該使用者是誰。
一方面友善将該使用者的所有監控資訊建立聯系,友善資料分析;
另一方面通過該辨別可以檢視該使用者的所有操作,友善複現問題。
3.1.2 性能資訊
頁面的性能直接影響了使用者留存率,,Google DoubleClick 研究表明:如果一個移動端頁面加載時長超過 3 秒,使用者就會放棄而離開。BBC 發現網頁加載時長每增加 1 秒,使用者就會流失 10%。,Google DoubleClick 研究表明:如果一個移動端頁面加載時長超過 3 秒,使用者就會放棄而離開。BBC 發現網頁加載時長每增加 1 秒,使用者就會流失 10%。是以我們的追求就是提高頁面的性能,為了提高性能需要監控哪些名額呢?
3.1.2.1 名額分類
名額有很多,我總結為以下兩個方面:網絡層面和頁面展示層面。
一、網絡層面
從網絡層面來看涉及的名額有:重定向耗時、DNS解析耗時、TCP連接配接耗時、SSL耗時、TTFB網絡請求耗時、資料傳輸耗時、資源加載耗時……,各個名額的解釋如下表所示:
名額 解釋
重定向耗時 重定向所耗費的時間
DNS解析耗時 浏覽器輸入網址後首先會進行DNS解析,其可以對伺服器是否工作作出回報
TCP連接配接耗時 指建立連接配接過程的耗時
SSL連接配接耗時 指資料安全性、完整性建立耗時
TTFB 網絡請求耗時 表示浏覽器接收第一個位元組的時間
資料傳輸耗時 浏覽器接收内容所耗費的時間
資源加載耗時 DOM建構完畢後到頁面加載完畢這段時間
二、頁面展示層面
頁面展示層面的名額是針對使用者體驗提出的幾個名額,包含FP、FCP、LCP、FMP、DCL、L等,這幾個名額其實就是chrome浏覽器中performance子產品的名額(如圖所示)。
各個名額的解釋如下表所示。
FP(First Paint) 首次繪制,标記浏覽器渲染任何在視覺上不同于導航前螢幕内容之内容的時間點.
FCP(First Contentful Paint) 首次内容繪制,标記浏覽器渲染來自 DOM 第一位内容的時間點,該内容可能是文本、圖像、SVG 甚至 元素.
LCP(Largest Contentful Paint) 最大内容渲染,表示可視區“内容”最大的可見元素開始出現在螢幕上的時間點。
FMP(First Meaningful Paint) 首次有效繪制,表示頁面的“主要内容”開始出現在螢幕上的時間點。它是我們測量使用者加載體驗的主要名額。
DCL(DomContentLoaded) 當 HTML 文檔被完全加載和解析完成之後,DOMContentLoaded 事件被觸發,無需等待樣式表、圖像和子架構的完成加載.
L(onLoad) 當依賴的資源全部加載完畢之後才會觸發
TTI(Time to Interactive) 可互動時間,用于标記應用已進入視覺渲染并能可靠響應使用者輸入的時間點
FID(First Input Delay) 首次輸入延遲,使用者首次和頁面互動(單擊連結、點選按鈕等)到頁面響應互動的時間
3.1.2.2 名額求解
上述這麼多名額該怎麼擷取呢?浏覽器給我們留了相應的接口——神奇的window.performance,通過該接口可以擷取一些列與性能相關的參數,下面以https://baidu.com 為例來看一下與這些名額相關的參數:
window.performance中的timing屬性中的内容不就是為了求解上述名額所需要的值嗎?看着上面的屬性值再對應下面的performance通路流程圖,整個過程是不是一目了然。
有了上面的值我們就一起求解上述的名額:
名額 計算
重定向耗時 redirectEnd - redirectStart
DNS解析耗時 domainLookupEnd - domainLookupStart
TCP連接配接耗時 connectEnd - connectStart
SSL連接配接耗時 connectEnd - secureConnectionStart
TTFB 網絡請求耗時 responseStart - requestStart
資料傳輸耗時 responseEnd - responseStart
資源加載耗時 loadEventStart - domContentLoadedEventEnd
Google工程師一直在推動以使用者為中心的性能名額,是以頁面展示層面的變化較大,求解方式稍有不同:
FP和FCP
通過window.performance.getEntriesByType(‘paint’)的方式擷取
const paint = window.performance.getEntriesByType('paint');
const FP = paint[0].startTime,
const FCP = paint[1].startTime,
LCP
function getLCP() {
// 增加一個性能條目的觀察者
new PerformanceObserver((entryList, observer) => {
let entries = entryList.getEntries();
const lastEntry = entries[entries.length - 1];
observer.disconnect();
console.log('LCP', lastEntry.renderTime || lastEntry.loadTime);
}).observe({entryTypes: ['largest-contentful-paint']});
}
FMP
function getFMP() {
let FMP;
console.log('FMP', entries);
}).observe({entryTypes: ['element']});
DCL
domContentLoadEventEnd – fetchStart
L
loadEventStart – fetchStart
TTI
domInteractive – fetchStart
FID
function getFID() {
let firstInput = entryList.getEntries()[0];
if (firstInput) {
const FID = firstInput.processingStart - firstInput.startTime;
console.log('FID', FID);
}
}).observe({type: 'first-input', buffered: true});
3.1.3 異常資訊
對于網站來說,異常資訊是最緻命、最影響使用者體驗的問題,需要重點監控。對于異常資訊可以分為兩類:運作時錯誤、接口錯誤。下面就分别來唠一唠這兩類錯誤。
一、運作時錯誤
當JavaScript運作時有可能會發生錯誤,可歸類為七種:文法錯誤、類型錯誤、範圍錯誤、引用錯誤、eval錯誤、URL錯誤、資源加載錯誤。為了捕獲代碼錯誤,需要考慮兩類場景:非Promise場景和Promise場景,因為兩種場景捕獲錯誤的政策不同。
1.非Promise場景
非Promise場景可通過監聽error事件來捕獲錯誤。對于error事件捕獲的錯誤分為兩類:資源錯誤和代碼錯誤。資源錯誤指的就是js、css、img等未加載,該錯誤隻能在捕獲階段擷取到,且為資源錯誤時event.target.localName存在值(用此區分資源錯誤與代碼錯誤);代碼錯誤指的就是文法錯誤、類型錯誤等這一類錯誤,可以擷取代碼錯誤的資訊、堆棧等,用于排查錯誤。
export function listenerError() {
window.addEventListener('error', (event) => {
if (event.target.localName) {
console.log('這是資源錯誤', event);
else {
console.log('這是代碼錯誤', event);
}, true)
2.Promise場景
Promise場景的處理方式有所不同,當Promise被reject且沒有reject處理器的時候,會觸發unhandlerejection事件,是以通過監聽unhandlerejection的事件來捕獲錯誤。
export function listenerPromiseError() {
window.addEventListener('unhandledrejection', (event) => {
console.log('這是Promise場景中錯誤', event);
})
二、接口錯誤
對于浏覽器來說,所有的接口均是基于XHR和Fetch實作的,為了捕獲接口中的錯誤,可以通過重寫該方法,然後通過接口傳回的資訊來判斷目前接口的狀況,下面以XHR為例來展示封裝過程。
function newXHR() {
const XMLHttpRequest = window.XMLHttpRequest;
const oldXHROpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = (method, url, async) => {
// 做一些自己的資料上報操作
return oldXHROpen.apply(this, arguments);
}
const oldXHRSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = (body) => {
return oldXHRSend.apply(this, arguments);
3.1.4 業務資訊
每個産品都會有自己的業務資訊,例如使用者線上時長、pv、uv、使用者分布等,通過擷取這些業務資訊才能更加清楚的了解目前産品的狀況,以便産品經理更好的去規劃産品的未來方向。由于每個産品業務資訊多種多樣,小夥伴本可以按照自己的需求進行撰寫代碼,此處我就不再贅述。
3.2上報
對于上報的方式無外乎兩種:一種是Ajax的方式上報;另一種是通過Image的形式進行上報。目前很多大廠采用的上報方式均是通過一個1*1像素的的gif圖檔進行上報,既然人家都采用該種政策,那我們就來唠一唠下面兩個問題。
為什麼采用Image的方式上報?
沒有跨域問題。因為資料伺服器和後端伺服器大機率是不同的域名,若采用Ajax的方式進行處理還要處理跨域問題,否則資料會被浏覽器攔截。
不會阻塞頁面加載,隻需new Image對象即可。
圖檔類型很多,為什麼采用gif這種格式進行上報?
其實歸結為一個字——小。對于1*1px的圖檔,BMP結構的檔案需要74位元組,PNG結構的檔案需要67位元組,GIF結構的檔案隻需要43位元組。同樣的響應,GIF可以比BMP節約41%的流量,比PNG節約35%的流量,是以選擇gif進行上報。
3.3分析
日志上報之後需要進行清洗,擷取自己所需要内容,并将分析内容進行存儲。根據資料量的大小可分為兩種方式:單機和叢集。
一、單機
通路量小、日志少的網站可以采用單機的方式對資料進行分析,例如用node讀取日志檔案,然後通過日志檔案中擷取所需要的資訊,最終将處理的資訊存儲到資料庫中。
二、叢集
很多産品的通路量很大,日志很多,此時就需要利用Hadoop進行分布式處理,擷取最終處理結果,其處理流程圖如下所示:
根據自己的日志量級決定自己的分析方式,合适的就是最好的,不用一味追求最優的、最先進的處理方式。
3.4報警
當異常類型超多一定門檻值之後需要進行報警通知,讓對應的從業人員去處理問題,及時止損。根據報警的級别不同,可以選擇不同的報警方式。