在開發工作中難免會出現bug,一般項目都是測試檢查通過后就可以發線上,可是在線上仍舊會出現各種意料之外或者未測試到的問題,這個時候有的用戶會向客服反饋說哪里哪里有問題,這是一種被動的錯誤上報方式,畢竟不是所有的用戶都會上報問題,更多的則是出現問題后直接離開我們的APP。所以異常監控這塊就顯得越來越重要。
頁面異常分類
在我們的項目中我將頁面異常分為以下幾種情況:
- javascript異常(語法錯誤,運行時錯誤,跨域腳本)
- 資源加載異常(img js css)
- ajax請求異常
- promise異常
- vue項目中全局異常捕獲
接下來介紹如何捕獲這些異常
前端頁面異常捕獲方式
window.onerror捕獲javascript異常
/**
* 捕獲javascript異常
* @param {String} message 錯誤信息
* @param {String} source 出錯文件
* @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 });
}
跨域腳本異常捕獲
一般涉及跨域的js運行錯誤時會拋出錯誤提示script error.,但沒有具體信息(如出錯文件,行列號提示等), 可利用資源共享策略來捕獲跨域js錯誤
客戶端:在script標簽增加crossorigin="anonymous"屬性
服務端:靜態資源響應頭Access-Control-Allow-Origin: *
window.addEventListener('error',cb,true)捕獲資源加載異常
img加載異常時會觸發img.onerror函數
// 捕獲資源加載異常
window.addEventListener('error',function(e){
const err = e.target.src || e.target.href
if(err){
console.log('捕獲到資源加載異常',err)
}
},true)
ajax接口請求異常捕獲
// 統一攔截ajax請求
function ajaxEventTrigger (event) {
var ajaxEvent = new CustomEvent(event, { detail: this })
window.dispatchEvent(ajaxEvent)
}
var oldXHR = window.XMLHttpRequest
function newXHR () {
var realXHR = new oldXHR()
realXHR.addEventListener('readystatechange', function () { ajaxEventTrigger.call(this, 'ajaxReadyStateChange') }, false)
return realXHR
}
window.XMLHttpRequest = newXHR
var startTime = 0
var gapTime = 0 // 計算請求延時
window.addEventListener('ajaxReadyStateChange', function (e) {
var xhr = e.detail
var status = xhr.status
var readyState = xhr.readyState
/**
* 計算請求延時
*/
if (readyState === 1) {
startTime = (new Date()).getTime()
}
if (readyState === 4) {
gapTime = (new Date()).getTime() - startTime
}
/**
* 上報請求信息
*/
if (readyState === 4) {
if(status === 200){
// 接口正常響應時捕獲接口響應耗時
console.log('接口',xhr.responseURL,'耗時',gapTime)
}else{
// 接口異常時捕獲異常接口及狀態碼
console.log('異常接口',xhr.responseURL,'狀態碼',status)
}
}
})
promise異常捕獲
promise 中的報錯順序是:
如果有catch 等捕獲函數,則走catch 捕獲函數。catch 捕獲函數如果沒有拋出新的異常,則下一個then將會認為沒有什么報錯,會繼續執行。
如果沒有catch 等捕獲函數,我們需要注冊 window.addEventListener('unhandledrejection') 來處理
/**
* Promise catch錯誤上報,需要在使用promise的地方顯示調用.catch(),否則不會捕獲錯誤
*/
if (typeof Promise !== 'undefined') {
var _promiseCatch = Promise.prototype.catch
Promise.prototype.catch = function (foo) {
return _promiseCatch.call(this, catCatch(foo))
}
}
function catCatch (foo) {
return function (args) {
let msg = args.stack ? args.stack : args
console.log('捕獲到catch中的異常',msg)
foo && foo.call(this, args)
}
}
/**
* 監聽promise未處理的reject錯誤, 跨域情況下監控不到
*/
window.addEventListener('unhandledrejection', event => {
console.log('捕獲到未處理的promise異常',event.reson)
})
vue項目全局異常捕獲
Vue.config.errorHandler = function (err, vm, info) {
// `info` 是 Vue 特定的錯誤信息,比如錯誤所在的生命周期鈎子
// 只在 2.2.0+ 可用
let msg = `錯誤發生在:${info}中,具體信息:${err.stack}`
console.log(msg)
}
捕獲到這些異常后我們需要將這些異常上報給服務器,我們直接以請求圖片的形式發送上報內容
異常上報
function report (msg) {
var reportUrl = 'http://xxxx/report'
new Image().src = reportUrl + encodeURIComponent(JSON.stringify(msg))
}