文章目錄
- 前端監控分為三個方面
- 前端監控流程
- 異常監控
- 常見的錯誤捕獲方法主要是 try / catch 、window.onerror 和window.addEventListener 等。
- Promise 錯誤
- Vue 錯誤
- React 錯誤
- 性能監控
- 用戶行為監控
- 常見的埋點方案
- 來源
前端監控分為三個方面
- 異常監控(監控前端頁面的報錯)
- 性能監控(監控頁面的性能)
- 用戶行為監控(監控用戶的行為,計算PV、UV、在線時間等、數據監控即我們常說的埋點
前端監控流程
- 前端埋點
- 數據上報
- 加工匯總
- 可視化展示
- 監控報警
異常監控
- JS 代碼運行錯誤、語法錯誤等;
- AJAX 請求錯誤;
- 靜態資源加載錯誤;
- Promise 異步函數錯誤;
錯誤信息監控簡單來說就是要搜集報錯信息的發生的位置,以及報錯的類型,進行上報,便于我們能夠更好的掌握錯誤信息,從而能夠對癥下藥。按照 5W1H 的法則來說,我們需要關注以下的幾項信息:
- What ,發生了什么錯誤:語法錯誤、類型錯誤、數據錯誤、邏輯錯誤等;
- When ,什么時間發生的錯誤,可帶上時間戳進行上報;
- Who ,哪個用戶或者哪一類用戶發生了錯誤,包括用戶 ID 、設備信息、IP 信息等;
- Where ,哪個項目、哪些頁面發生錯誤,可以上報頁面的 URL 以及代碼報錯行數等信息;
- Why ,為什么會發生錯誤,也就是用戶在什么樣的場景下發生的錯誤,便于問題復現;
- How ,根據以上的信息如何進行問題的定位,然后怎么處理并解決問題;
常見的錯誤捕獲方法主要是 try / catch 、window.onerror 和window.addEventListener 等。
try / catch
這是我們在代碼調試的過程中最常用的一個方式,但它只能捕獲代碼常規的運行錯誤,語法錯誤和異步錯誤并能捕獲到。
// 常規運行時錯誤,可以捕獲 ?
try {console.log(notdefined);
} catch(e) {console.log('捕獲到異常:', 'ReferenceError');}// 語法錯誤,不能捕獲 ?
try {const notdefined,
} catch(e) {console.log('捕獲不到異常:', 'Uncaught SyntaxError');
}// 異步錯誤,不能捕獲 ?
try {setTimeout(() => {console.log(notdefined);}, 0)
} catch(e) {console.log('捕獲不到異常:', 'Uncaught ReferenceError');
}
window.onerror
當 JS 運行時錯誤發生時,window 會觸發一個 ErrorEvent 接口的 error 事件,并執行 window.onerror() 。
加載一個全局的 error 事件處理函數可用于自動收集錯誤報告。
最后需要補充的是:window.onerror 函數只有在返回 true 的時候,異常才不會向上拋出,否則即使是知道異常的發生,控制臺還是會顯示 Uncaught Error 。
```javascript
/*** @param { string } message 錯誤信息
* @param { string } source 發生錯誤的腳本URL
* @param { number } lineno 發生錯誤的行號
* @param { number } colno 發生錯誤的列號
* @param { object } error Error對象*/window.onerror = function(message, source, lineno, colno, error) {console.log('捕獲到的錯誤信息是:', message, source, lineno, colno, error )
}// 常規運行時錯誤,可以捕獲 ?
window.onerror = function(message, source, lineno, colno, error) {console.log('捕獲到異常:',{message, source, lineno, colno, error});
}
console.log(notdefined);
// message: "Uncaught ReferenceError: notdefined is not defined"
// source: "file:///C:/Users/qinzq42866/Desktop/error.html"
// lineno: 14
// colno: 19
// error: ReferenceError: notdefined is not defined at file
// 語法錯誤,不能捕獲 ?
window.onerror = function(message, source, lineno, colno, error) {console.log('未捕獲到異常:',{message, source, lineno, colno, error});
}
const notdefined,
// Uncaught SyntaxError: Missing initializer in const declaration// 異步錯誤,可以捕獲 ?
window.onerror = function(message, source, lineno, colno, error) {console.log('捕獲到異常:',{message, source, lineno, colno, error});
}
setTimeout(() => {console.log(notdefined);
}, 0)// message: "Uncaught ReferenceError: notdefined is not defined"// source: "file:///C:/Users/qinzq42866/Desktop/error.html"// lineno: 15// colno: 21// error: ReferenceError: notdefined is not defined at file// 資源錯誤,不能捕獲 ?
<script>
window.onerror = function(message, source, lineno, colno, error) {console.log('捕獲到異常:',{message, source, lineno, colno, error});
}</script>// GET https://yun.tuia.cn/image/kkk.png 404 (Not Found)
window.addEventListener
當一項靜態資源加載失敗時,加載資源的元素會觸發一個 Event 接口的 Error 事件,這些 Error 事件不會向上冒泡到 window ,但能被捕獲。而 window.onerror 不能檢測捕獲。
// 圖片、script、css加載錯誤,都能被捕獲 ?<script>window.addEventListener('error', (error) => {console.log('捕獲到異常:', error);}, true)</script>// fetch錯誤,不能捕獲 ?<script>window.addEventListener('error', (error) => {console.log('未捕獲到異常:', error);}, true)</script<script>fetch('https://tuia.cn/test')</script>
由于網絡請求異常不會發生事件冒泡,因此必須在事件捕獲的階段將其捕捉到才行,這種方式雖然能夠捕捉到網絡請求的異常,但是卻無法判斷 HTTP 的狀態,因此仍然需要配合服務端的日志進行配合分析。
需要注意的是:不同瀏覽器下返回的 Error 對象是不一樣的,需要做兼容處理。
Promise 錯誤
沒有寫 catch 的 Promise 中拋出的錯誤是無法被 onerror 或 try / catch 捕獲到的,這也是為什么我們一定要在 Promise 后面加上 catch 去捕獲和處理異常。
為了防止有漏掉的 Promise 異常信息,建議在全局增加一個對 unhandledrejection 的監聽,用來全局監聽 Uncaught Promise Error 。
說明:當 Promise 被 reject 且沒有 reject 處理器的時候,會觸發 unhandledrejection 事件;這可能發生在 window 下,但也可能發生在 Worker 中。 這對于調試回退錯誤處理非常有用。
window.addEventListener("unhandledrejection", event => {console.warn('UNHANDLED PROMISE REJECTION:', ${event.reason});});window.onunhandledrejection = event => {console.warn('UNHANDLED PROMISE REJECTION:', ${event.reason});};window.addEventListener("unhandledrejection", function(e){e.preventDefault()console.log('捕獲到異常:', e);});Promise.reject('promise error');
說明:如果去掉控制臺的異常顯示,需要加上 event.preventDefault() ;
Vue 錯誤
由于 Vue 會捕獲到所有 Vue 單文件組件或者 Vue.extend 繼承的代碼,所以在 Vue 里面出現的錯誤并不會直接拋給 window.onerror ,而是會被 Vue 自身的 Vue.config.errorHandler 捕獲。
Vue.config.errorHandler = (err, vm, info) => {console.error('通過vue errorHandler捕獲的錯誤');console.error(err);console.error(vm);console.error(info);}
React 錯誤
React 16 提供了一個內置函數 componentDidCatch ,使用它可以輕松的捕獲到 React 組件內部拋出的錯誤信息。
class ErrorBoundary extends React.Component {constructor(props) {super(props);this.state = { hasError: false };}componentDidCatch(error, errorInfo) {console.log('捕獲到錯誤:', error, errorInfo);}render() {if (this.state.hasError) {return `Something went wrong.`;}return this.props.children; }
}
性能監控
- 不同用戶和不同設備下的首屏加載時間,包括白屏時間;
- HTTP 接口的響應時間;
- 靜態資源、包括圖片的下載時間;
根據W3C性能小組引入的新的API(目前IE9以上的瀏覽器)–window.performance,實現前端性能監控
(function () {handleAddListener('load', getTiming)function handleAddListener(type, fn) {if (window.addEventListener) {window.addEventListener(type, fn)} else {window.attachEvent('on' + type, fn)}}function getTiming() {try {var time = performance.timing;var timingObj = {};var loadTime = (time.loadEventEnd - time.loadEventStart);if (loadTime < 0) {setTimeout(function () {getTiming();}, 200);return;}// 階段耗時timingObj['DNS解析耗時'] = (time.domainLookupEnd - time.domainLookupStart);timingObj['TCP連接耗時'] = (time.connectEnd - time.connectStart);timingObj['SSL安全連接耗時'] = (time.connectEnd - time.secureConnectionStart);//針對httpstimingObj['網絡請求耗時'] = (time.responseStart - time.requestStart);timingObj['數據傳輸耗時'] = (time.responseEnd - time.responseStart);timingObj['DOM解析耗時'] = (time.domInteractive - time.responseEnd);timingObj['資源加載耗時, 表示頁面中的同步加載資源'] = (time.loadEventStart - time.domContentLoadedEventEnd);timingObj['前端onload執行時間'] = (time.loadEventEnd - time.loadEventStart);//性能指標(上報字段名)timingObj["首次渲染"] = time.responseEnd - time.fetchStart// timingObj["首屏時間"] = first meaningful painttimingObj["首次可交互"] = time.domInteractive - time.fetchStarttimingObj["DOMReady"] = time.domContentLoadedEventEnd - time.fetchStarttimingObj["頁面完全加載"] = time.loadEventStart - time.fetchStarttimingObj["首包時間"] = time.responseStart - time.domainLookupStartfor (item in timingObj) {console.log(item + ":" + timingObj[item] + '毫秒(ms)');}console.log(performance.timing);console.log(performance);} catch (e) {console.log(timingObj)console.log(performance.timing);}}
})();
用戶行為監控
- PV / UV:PV 即 Page View ,也就是頁面的瀏覽數量,沒打開頁面一次就會統計一次;UV 即 User View
,也就是不同用戶訪問的次數,在 PV 的基礎上根據 User 信息的不同做了去重操作; - 用戶在每個頁面停留的時間信息。即從用戶打開該頁面到用戶離開該頁面的時間差,用于表示該頁面對用戶的留存程度;
- 用戶的來處。即從什么入口或什么渠道來到了當前頁面,通常會在 URL 中添加查詢參數來做區分統計;
- 用戶的頁面操作行為。即用戶在該頁面點擊了哪些按鈕,或者從什么鏈接去到了某些頁面等等,來分析用戶的去向。
import tracker from "../util/tracker";
export function pv() {tracker.send({kind: "business",type: "pv",startTime: performance.now(),pageURL: getPageURL(),referrer: document.referrer,uuid: getUUID(),});let startTime = Date.now();window.addEventListener("beforeunload",() => {let stayTime = Date.now() - startTime;tracker.send({kind: "business",type: "stayTime",stayTime,pageURL: getPageURL(),uuid: getUUID(),});},false);
}
常見的埋點方案
代碼埋點
嵌入代碼的形式
優點:精確(任意時刻,數據量全面)
缺點:代碼工作量點
可視化埋點
通過可視化交互的手段,代替代碼埋點
將業務代碼和埋點代碼分離,提供一個可視化交互的頁面,輸入為業務代碼,通過這個系統,可以在業務代碼中自定義的增加埋點事件等等,最后輸出的代碼耦合了業務代碼和埋點代碼
用系統來代替手工插入埋點代碼
無痕埋點
前端的任意一個事件被綁定一個標識,所有的事件都被記錄下來
通過定期上傳記錄文件,配合文件解析,解析出來我們想要的數據,并生成可視化報告供專業人員分析
無痕埋點的優點是采集全量數據,不會出現漏埋和誤埋等現象
缺點是給數據傳輸和服務器增加壓力,也無法靈活定制數據結構
來源
前端監控指的是什么?
前端 監控