搭建前端監(jiān)控,如何采集異常數(shù)據(jù)?
作者:楊成功
簡介:專注前端工程與架構(gòu)產(chǎn)出
來源:SegmentFault 思否社區(qū)
大家好,我是楊成功。
前兩篇,我們介紹了為什么前端應(yīng)該有監(jiān)控系統(tǒng),以及搭建前端監(jiān)控的總體步驟,前端監(jiān)控的 Why 和 What 想必你已經(jīng)明白了。接下來我們解決 How 如何實(shí)現(xiàn)的問題。
如果不了解前端監(jiān)控,建議先看前兩篇:
什么是異常數(shù)據(jù)?
接口異常
axios
.post('/test')
.then((res) => { console.log(res);
})
.catch((err) => { // err 就是捕獲到的錯誤對象
handleError(err);
});
async () => { try { let res = await axios.post('/test'); console.log(res);
} catch (err) { // err 就是捕獲到的錯誤對象
handleError(err);
}
};當(dāng)捕獲到異常之后,統(tǒng)一交給 handleError 函數(shù)處理,這個函數(shù)會將接收到的異 常進(jìn)行處理,并調(diào)用 上報接口 將異常數(shù)據(jù)傳到服務(wù)器,從而完成采集。
// 全局請求:src/request/axios.jsconst instance = axios.create({ baseURL: 'https://api.test.com'
timeout: 15000, headers: { 'Content-Type': 'application/json',
},
})export default instance
// a 頁面:src/page/a.jsximport http from '@/src/request/axios.js';async () => { let res = await http.post('/test'); console.log(res);
};
攔截器中捕獲異常
// 響應(yīng)攔截器instance.interceptors.response.use( (response) => { return response.data;
}, (error) => { // 發(fā)生異常會走到這里
if (error.response) { let response = error.response; if (response.status >= 400) { handleError(response);
}
} else { handleError(null);
} return Promise.reject(error);
},
);
前端異常
try { // 任意同步代碼} catch (err) { console.log(err);
}
try { Promise.reject(new Error('出錯了')).catch((err) => console.log('1:', err));
} catch (err) { console.log('2:', err);
}
1:Error: 出錯了
// js 錯誤捕獲window.addEventListener('error', (error) => { // error 就是js的異常});
為啥不用 window.onerror ?
const loadCss = ()=> { let link = document.createElement('link')
link.type = 'text/css'
link.rel = 'stylesheet'
link.href = 'https://baidu.com/15.css'
document.getElementsByTagName('head')[10].append(link)
}render() { return <div>
<img src='./bbb.png'/>
<button onClick={loadCss}>加載樣式<button/>
</div>}
// 捕獲階段全局監(jiān)聽window.addEventListene( 'error', (error) => { if (error.target != window) { console.log(error.target.tagName, error.target.src);
} handleError(error);
}, true,
);
// promise 錯誤捕獲window.addEventListener('unhandledrejection', (error) => { // 打印異常原因
console.log(error.reason); handleError(error); // 阻止控制臺打印
error.preventDefault();
});
異常處理函數(shù)
const handleError = (error: any, type: 1 | 2) { if(type == 1) { // 處理接口異常
} if(type == 2) { // 處理前端異常
}
}
處理前端異常:handleError(error, 1)
處理接口異常:handleError(error, 2)
處理接口異常
code:http 狀態(tài)碼
url:接口請求地址
method:接口請求方法
params:接口請求參數(shù)
error:接口報錯信息
const handleError = (error: any, type: 1 | 2) { if(type == 1) { // 此時的 error 響應(yīng),它的 config 字段中包含請求信息
let { url, method, params, data } = error.config
let err_data = {
url, method, params: { query: params, body: data }, error: error.data?.message || JSON.stringify(error.data),
})
}
}
params: { query: params, body: data }
處理前端異常
ReferenceError:引用錯誤
RangeError:超出有效范圍
TypeError:類型錯誤
URIError:URI 解析錯誤
const handleError = (error: any, type: 1 | 2) { if(type == 2) { let err_data = null
// 監(jiān)測 error 是否是標(biāo)準(zhǔn)類型
if(error instanceof Error) { let { name, message } = error
err_data = { type: name, error: message
}
} else {
err_data = { type: 'other', error: JSON.strigify(error)
}
}
}
}
function test() { console.aaa('ccc');
}test();
const handleError = (error: any) => { if (error instanceof Error) { let { name, message } = error; console.log(name, message); // 打印結(jié)果:TypeError console.aaa is not a function
}
};
獲取環(huán)境數(shù)據(jù)
app:應(yīng)用的名稱/標(biāo)識
env:應(yīng)用環(huán)境,一般是開發(fā),測試,生產(chǎn)
version:應(yīng)用的版本號
user_id:觸發(fā)異常的用戶 ID
user_name:觸發(fā)異常的用戶名
page_route:異常的頁面路由
page_title:異常的頁面名稱
在 Vue 中
import store from '@/store'; // vuex 導(dǎo)出目錄let user_info = store.state;let user_id = user_info.id;let user_name = user_info.name;
{ path: '/test', name: 'test', meta: { title: '測試頁面'
}, component: () => import('@/views/test/Index.vue')
},這樣配置之后,獲取當(dāng)前頁面路由和頁面名稱就簡單了:
window.vm = new Vue({...})let route = vm.$routelet page_route = route.pathlet page_title = route.meta.title
最后一步,我們再獲取當(dāng)前環(huán)境。當(dāng)前環(huán)境用一個環(huán)境變量 VUE_APP_ENV 表示,有三個值:
dev:開發(fā)環(huán)境
test:測試環(huán)境
pro:生產(chǎn)環(huán)境
.env.development:VUE_APP_ENV=dev
.env.staging:VUE_APP_ENV=test
.env.production:VUE_APP_ENV=pro
{ env: process.env.VUE_APP_ENV;
}
# 測試環(huán)境打包$ num run build --mode staging# 生產(chǎn)環(huán)境打包$ num run build --mode production
在 React 中
import { TestStore } from '@/stores'; // mobx 導(dǎo)出目錄let { user_info, cur_path, cur_page_title } = TestStore;// 用戶信息:user_info// 頁面信息:cur_path,cur_page_title
import { useLocation } from 'react-router';import { observer, useLocalObservable } from 'mobx-react';import { TestStore } from '@/stores';export default observer(() => { const { pathname, search } = useLocation(); const test_inst = useLocalObservable(() => TestStore); useEffect(() => {
test_inst.setCurPath(pathname, search);
}, [pathname]);
});
process.env.REACT_APP_ENV = 'dev';
if (argv.length >= 2 && argv[0] == '--mode') { switch (argv[1]) { case 'staging':
process.env.REACT_APP_ENV = 'test'; break; case 'production':
process.env.REACT_APP_ENV = 'pro'; break; default:
}
}
{ env: process.env.REACT_APP_ENV;
}
總結(jié)

