前端監(jiān)控
什么時候需要監(jiān)控
1.當你的應用頻繁報錯找不到原因的時候。2.需要分析用戶興趣愛好、購買習慣。3.需要優(yōu)化程序的時候,可以做監(jiān)控收集數(shù)據(jù),做針對性的優(yōu)化。4.需要保證服務可靠性穩(wěn)定性。
如果你的應用符合以上任意一條,就可以對應用實行監(jiān)控了。監(jiān)控的作用有兩個:事前預警和事后分析。
事前預警:提前設(shè)置一個閾值,當監(jiān)控的數(shù)據(jù)達到閾值時,通過短信或者郵件通知管理員。例如 API 請求數(shù)量突然間暴漲,就得進行報警,否則可能會造成服務器宕機。
事后分析:通過監(jiān)控日志文件,分析故障原因和故障發(fā)生點。從而做出修改,防止這種情況再次發(fā)生。
本章內(nèi)容分為前端監(jiān)控原理分析和如何對項目實行監(jiān)控兩個部分。第一部分有三個小節(jié):數(shù)據(jù)采集、數(shù)據(jù)上報、擴展;第二部分只有一個小節(jié):如何使用 sentry[1] 實現(xiàn)項目監(jiān)控。
好了,下面讓我們開始進入正文吧。
數(shù)據(jù)采集
性能數(shù)據(jù)采集
性能數(shù)據(jù)采集需要使用 window.performance[2] API。
Performance 接口可以獲取到當前頁面中與性能相關(guān)的信息,它是 High Resolution Time API 的一部分,同時也融合了 Performance Timeline API、Navigation Timing API、 User Timing API 和 Resource Timing API。
從 MDN 的文檔可以看出,window.performance.timing 包含了頁面加載各個階段的起始及結(jié)束時間。

這些屬性需要結(jié)合下圖一起看,更好理解:

為了方便大家理解 timing 各個屬性的意義,我在知乎找到一位網(wǎng)友對于 timing 寫的簡介,在此轉(zhuǎn)載一下。
timing: {// 同一個瀏覽器上一個頁面卸載(unload)結(jié)束時的時間戳。如果沒有上一個頁面,這個值會和fetchStart相同。navigationStart: 1543806782096,// 上一個頁面unload事件拋出時的時間戳。如果沒有上一個頁面,這個值會返回0。unloadEventStart: 1543806782523,// 和 unloadEventStart 相對應,unload事件處理完成時的時間戳。如果沒有上一個頁面,這個值會返回0。unloadEventEnd: 1543806782523,// 第一個HTTP重定向開始時的時間戳。如果沒有重定向,或者重定向中的一個不同源,這個值會返回0。redirectStart: 0,// 最后一個HTTP重定向完成時(也就是說是HTTP響應的最后一個比特直接被收到的時間)的時間戳。// 如果沒有重定向,或者重定向中的一個不同源,這個值會返回0.redirectEnd: 0,// 瀏覽器準備好使用HTTP請求來獲取(fetch)文檔的時間戳。這個時間點會在檢查任何應用緩存之前。fetchStart: 1543806782096,// DNS 域名查詢開始的UNIX時間戳。//如果使用了持續(xù)連接(persistent connection),或者這個信息存儲到了緩存或者本地資源上,這個值將和fetchStart一致。domainLookupStart: 1543806782096,// DNS 域名查詢完成的時間.//如果使用了本地緩存(即無 DNS 查詢)或持久連接,則與 fetchStart 值相等domainLookupEnd: 1543806782096,// HTTP(TCP) 域名查詢結(jié)束的時間戳。//如果使用了持續(xù)連接(persistent connection),或者這個信息存儲到了緩存或者本地資源上,這個值將和 fetchStart一致。connectStart: 1543806782099,// HTTP(TCP) 返回瀏覽器與服務器之間的連接建立時的時間戳。// 如果建立的是持久連接,則返回值等同于fetchStart屬性的值。連接建立指的是所有握手和認證過程全部結(jié)束。connectEnd: 1543806782227,// HTTPS 返回瀏覽器與服務器開始安全鏈接的握手時的時間戳。如果當前網(wǎng)頁不要求安全連接,則返回0。secureConnectionStart: 1543806782162,// 返回瀏覽器向服務器發(fā)出HTTP請求時(或開始讀取本地緩存時)的時間戳。requestStart: 1543806782241,// 返回瀏覽器從服務器收到(或從本地緩存讀?。┑谝粋€字節(jié)時的時間戳。//如果傳輸層在開始請求之后失敗并且連接被重開,該屬性將會被數(shù)制成新的請求的相對應的發(fā)起時間。responseStart: 1543806782516,// 返回瀏覽器從服務器收到(或從本地緩存讀取,或從本地資源讀?。┳詈笠粋€字節(jié)時//(如果在此之前HTTP連接已經(jīng)關(guān)閉,則返回關(guān)閉時)的時間戳。responseEnd: 1543806782537,// 當前網(wǎng)頁DOM結(jié)構(gòu)開始解析時(即Document.readyState屬性變?yōu)椤發(fā)oading”、相應的 readystatechange事件觸發(fā)時)的時間戳。domLoading: 1543806782573,// 當前網(wǎng)頁DOM結(jié)構(gòu)結(jié)束解析、開始加載內(nèi)嵌資源時(即Document.readyState屬性變?yōu)椤癷nteractive”、相應的readystatechange事件觸發(fā)時)的時間戳。domInteractive: 1543806783203,// 當解析器發(fā)送DOMContentLoaded 事件,即所有需要被執(zhí)行的腳本已經(jīng)被解析時的時間戳。domContentLoadedEventStart: 1543806783203,// 當所有需要立即執(zhí)行的腳本已經(jīng)被執(zhí)行(不論執(zhí)行順序)時的時間戳。domContentLoadedEventEnd: 1543806783216,// 當前文檔解析完成,即Document.readyState 變?yōu)?'complete'且相對應的readystatechange 被觸發(fā)時的時間戳domComplete: 1543806783796,// load事件被發(fā)送時的時間戳。如果這個事件還未被發(fā)送,它的值將會是0。loadEventStart: 1543806783796,// 當load事件結(jié)束,即加載事件完成時的時間戳。如果這個事件還未被發(fā)送,或者尚未完成,它的值將會是0.loadEventEnd: 1543806783802}
通過以上數(shù)據(jù),我們可以得到幾個有用的時間:
// 重定向耗時redirect: timing.redirectEnd - timing.redirectStart,// DOM 渲染耗時dom: timing.domComplete - timing.domLoading,// 頁面加載耗時load: timing.loadEventEnd - timing.navigationStart,// 頁面卸載耗時unload: timing.unloadEventEnd - timing.unloadEventStart,// 請求耗時request: timing.responseEnd - timing.requestStart,// 獲取性能信息時當前時間time: new Date().getTime(),
還有一個比較重要的時間就是白屏時間,它指從輸入網(wǎng)址,到頁面開始顯示內(nèi)容的時間。
將以下腳本放在 </head> 前面就能獲取白屏時間。
<script>whiteScreen = new Date() - performance.timing.navigationStart// 通過 domLoading 和 navigationStart 也可以whiteScreen = performance.timing.domLoading - performance.timing.navigationStart</script>
通過這幾個時間,就可以得知頁面首屏加載性能如何了。
另外,通過 window.performance.getEntriesByType('resource') 這個方法,我們還可以獲取相關(guān)資源(js、css、img...)的加載時間,它會返回頁面當前所加載的所有資源。

它一般包括以下幾個類型:
?sciprt?link?img?css?fetch?other?xmlhttprequest
我們只需用到以下幾個信息:
// 資源的名稱name: item.name,// 資源加載耗時duration: item.duration.toFixed(2),// 資源大小size: item.transferSize,// 資源所用協(xié)議protocol: item.nextHopProtocol,
現(xiàn)在,寫幾行代碼來收集這些數(shù)據(jù)。
// 收集性能信息const getPerformance = () => {if (!window.performance) returnconst timing = window.performance.timingconst performance = {// 重定向耗時redirect: timing.redirectEnd - timing.redirectStart,// 白屏時間whiteScreen: whiteScreen,// DOM 渲染耗時dom: timing.domComplete - timing.domLoading,// 頁面加載耗時load: timing.loadEventEnd - timing.navigationStart,// 頁面卸載耗時unload: timing.unloadEventEnd - timing.unloadEventStart,// 請求耗時request: timing.responseEnd - timing.requestStart,// 獲取性能信息時當前時間time: new Date().getTime(),}return performance}// 獲取資源信息const getResources = () => {if (!window.performance) returnconst data = window.performance.getEntriesByType('resource')const resource = {xmlhttprequest: [],css: [],other: [],script: [],img: [],link: [],fetch: [],// 獲取資源信息時當前時間time: new Date().getTime(),}data.forEach(item => {const arry = resource[item.initiatorType]arry && arry.push({// 資源的名稱name: item.name,// 資源加載耗時duration: item.duration.toFixed(2),// 資源大小size: item.transferSize,// 資源所用協(xié)議protocol: item.nextHopProtocol,})})return resource}
小結(jié)
通過對性能及資源信息的解讀,我們可以判斷出頁面加載慢有以下幾個原因:
1.資源過多、過大2.網(wǎng)速過慢3.DOM 元素過多
除了用戶網(wǎng)速過慢,我們沒辦法之外,其他兩個原因都是有辦法解決的,關(guān)于如何做性能優(yōu)化我們將在下一章學習。
PS:其實頁面加載慢還有其他原因,例如沒有使用按需加載、沒有使用 CDN 等等。不過在這里我們強調(diào)的是僅通過對性能和資源信息的解讀來得知原因。
錯誤數(shù)據(jù)采集
目前所能捕捉的錯誤有三種:
1.資源加載錯誤,通過 addEventListener('error', callback, true) 在捕獲階段捕捉資源加載失敗錯誤。2.js 執(zhí)行錯誤,通過 window.onerror 捕捉 js 錯誤。3.promise 錯誤,通過 addEventListener('unhandledrejection', callback)捕捉 promise 錯誤,但是沒有發(fā)生錯誤的行數(shù),列數(shù)等信息,只能手動拋出相關(guān)錯誤信息。
我們可以建一個錯誤數(shù)組變量 errors 在錯誤發(fā)生時,將錯誤的相關(guān)信息添加到數(shù)組,然后在某個階段統(tǒng)一上報,具體如何操作請看下面的代碼:
// 捕獲資源加載失敗錯誤 js css img...addEventListener('error', e => {const target = e.targetif (target != window) {monitor.errors.push({type: target.localName,url: target.src || target.href,msg: (target.src || target.href) + ' is load error',// 錯誤發(fā)生的時間time: new Date().getTime(),})}}, true)// 監(jiān)聽 js 錯誤window.onerror = function(msg, url, row, col, error) {monitor.errors.push({type: 'javascript',row: row,col: col,msg: error && error.stack? error.stack : msg,url: url,// 錯誤發(fā)生的時間time: new Date().getTime(),})}// 監(jiān)聽 promise 錯誤 缺點是獲取不到行數(shù)數(shù)據(jù)addEventListener('unhandledrejection', e => {monitor.errors.push({type: 'promise',msg: (e.reason && e.reason.msg) || e.reason || '',// 錯誤發(fā)生的時間time: new Date().getTime(),})})
小結(jié)
通過錯誤收集,可以了解到網(wǎng)站發(fā)生錯誤的類型及數(shù)量,從而做出相應的調(diào)整,以減少錯誤發(fā)生。
數(shù)據(jù)上報
性能數(shù)據(jù)上報
性能數(shù)據(jù)可以在頁面加載完之后上報,盡量不要對頁面性能造成影響。
window.onload = () => {// 在瀏覽器空閑時間獲取性能及資源信息// https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallbackif (window.requestIdleCallback) {window.requestIdleCallback(() => {monitor.performance = getPerformance()monitor.resources = getResources()})} else {setTimeout(() => {monitor.performance = getPerformance()monitor.resources = getResources()}, 0)}}
當然,你也可以設(shè)一個定時器,循環(huán)上報。不過每次上報最好做一下對比去重再上報,避免同樣的數(shù)據(jù)重復上報。
錯誤數(shù)據(jù)上報
我在 DEMO(在小節(jié)末尾) 里提供的代碼,是用一個 errors 數(shù)組收集所有的錯誤,再在某一階段統(tǒng)一上報(延時上報)。
其實,也可以改成在錯誤發(fā)生時上報(即時上報)。這樣可以避免“收集完錯誤,但延時上報還沒觸發(fā),用戶卻已經(jīng)關(guān)掉網(wǎng)頁導致錯誤數(shù)據(jù)丟失”的問題。
// 監(jiān)聽 js 錯誤window.onerror = function(msg, url, row, col, error) {const data = {type: 'javascript',row: row,col: col,msg: error && error.stack? error.stack : msg,url: url,// 錯誤發(fā)生的時間time: new Date().getTime(),}// 即時上報axios.post({ url: 'xxx', data, })}
另外,還可以使用 navigator.sendBeacon()[3] 來進行上報。
window.addEventListener('unload', logData, false);function logData() {navigator.sendBeacon("/log", analyticsData);}
它的技術(shù)特點是:
使用 sendBeacon() 方法會使用戶代理(瀏覽器)在有機會時異步地向服務器發(fā)送數(shù)據(jù),同時不會延遲頁面的卸載或影響下一導航的載入性能。這就解決了提交分析數(shù)據(jù)時的所有的問題:數(shù)據(jù)可靠,傳輸異步并且不會影響下一頁面的加載。
DEMO 代碼
<html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><script>function monitorInit() {const monitor = {// 數(shù)據(jù)上傳地址url: '',// 性能信息performance: {},// 資源信息resources: {},// 錯誤信息errors: [],// 用戶信息user: {// 屏幕寬度screen: screen.width,// 屏幕高度height: screen.height,// 瀏覽器平臺platform: navigator.platform,// 瀏覽器的用戶代理信息userAgent: navigator.userAgent,// 瀏覽器用戶界面的語言language: navigator.language,},// 手動添加錯誤addError(error) {const obj = {}const { type, msg, url, row, col } = errorif (type) obj.type = typeif (msg) obj.msg = msgif (url) obj.url = urlif (row) obj.row = rowif (col) obj.col = colobj.time = new Date().getTime()monitor.errors.push(obj)},// 重置 monitor 對象reset() {window.performance && window.performance.clearResourceTimings()monitor.performance = getPerformance()monitor.resources = getResources()monitor.errors = []},// 清空 error 信息clearError() {monitor.errors = []},// 上傳監(jiān)控數(shù)據(jù)upload() {// 自定義上傳// axios.post({// url: monitor.url,// data: {// performance,// resources,// errors,// user,// }// })},// 設(shè)置數(shù)據(jù)上傳地址setURL(url) {monitor.url = url},}// 獲取性能信息const getPerformance = () => {if (!window.performance) returnconst timing = window.performance.timingconst performance = {// 重定向耗時redirect: timing.redirectEnd - timing.redirectStart,// 白屏時間whiteScreen: whiteScreen,// DOM 渲染耗時dom: timing.domComplete - timing.domLoading,// 頁面加載耗時load: timing.loadEventEnd - timing.navigationStart,// 頁面卸載耗時unload: timing.unloadEventEnd - timing.unloadEventStart,// 請求耗時request: timing.responseEnd - timing.requestStart,// 獲取性能信息時當前時間time: new Date().getTime(),}return performance}// 獲取資源信息const getResources = () => {if (!window.performance) returnconst data = window.performance.getEntriesByType('resource')const resource = {xmlhttprequest: [],css: [],other: [],script: [],img: [],link: [],fetch: [],// 獲取資源信息時當前時間time: new Date().getTime(),}data.forEach(item => {const arry = resource[item.initiatorType]arry && arry.push({// 資源的名稱name: item.name,// 資源加載耗時duration: item.duration.toFixed(2),// 資源大小size: item.transferSize,// 資源所用協(xié)議protocol: item.nextHopProtocol,})})return resource}window.onload = () => {// 在瀏覽器空閑時間獲取性能及資源信息 https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallbackif (window.requestIdleCallback) {window.requestIdleCallback(() => {monitor.performance = getPerformance()monitor.resources = getResources()console.log('頁面性能信息')console.log(monitor.performance)console.log('頁面資源信息')console.log(monitor.resources)})} else {setTimeout(() => {monitor.performance = getPerformance()monitor.resources = getResources()console.log('頁面性能信息')console.log(monitor.performance)console.log('頁面資源信息')console.log(monitor.resources)}, 0)}}// 捕獲資源加載失敗錯誤 js css img...addEventListener('error', e => {const target = e.targetif (target != window) {monitor.errors.push({type: target.localName,url: target.src || target.href,msg: (target.src || target.href) + ' is load error',// 錯誤發(fā)生的時間time: new Date().getTime(),})console.log('所有的錯誤信息')console.log(monitor.errors)}}, true)// 監(jiān)聽 js 錯誤window.onerror = function(msg, url, row, col, error) {monitor.errors.push({type: 'javascript', // 錯誤類型row: row, // 發(fā)生錯誤時的代碼行數(shù)col: col, // 發(fā)生錯誤時的代碼列數(shù)msg: error && error.stack? error.stack : msg, // 錯誤信息url: url, // 錯誤文件time: new Date().getTime(), // 錯誤發(fā)生的時間})console.log('所有的錯誤信息')console.log(monitor.errors)}// 監(jiān)聽 promise 錯誤 缺點是獲取不到行數(shù)數(shù)據(jù)addEventListener('unhandledrejection', e => {monitor.errors.push({type: 'promise',msg: (e.reason && e.reason.msg) || e.reason || '',// 錯誤發(fā)生的時間time: new Date().getTime(),})console.log('所有的錯誤信息')console.log(monitor.errors)})return monitor}const monitor = monitorInit()</script><link rel="stylesheet" href="test.css"><title>Document</title></head><body><button class="btn1">錯誤測試按鈕1</button><button class="btn2">錯誤測試按鈕2</button><button class="btn3">錯誤測試按鈕3</button><img src="https://avatars3.githubusercontent.com/u/22117876?s=460&v=4" alt=""><img src="test.png" alt=""><script src="192.168.10.15/test.js"></script><script>document.querySelector('.btn1').onclick = () => {setTimeout(() => {console.log(button)}, 0)}document.querySelector('.btn2').onclick = () => {new Promise((resolve, reject) => {reject({msg: 'test.js promise is error'})})}document.querySelector('.btn3').onclick = () => {throw ('這是一個手動扔出的錯誤')}</script></body></html>
擴展
SPA
window.performance API 是有缺點的,在 SPA 切換路由時,window.performance.timing 的數(shù)據(jù)不會更新。所以我們需要另想辦法來統(tǒng)計切換路由到加載完成的時間。拿 Vue 舉例,一個可行的辦法就是切換路由時,在路由的全局前置守衛(wèi) beforeEach 里獲取開始時間,在組件的 mounted 鉤子里執(zhí)行 vm.$nextTick 函數(shù)來獲取組件的渲染完畢時間。
router.beforeEach((to, from, next) => {store.commit('setPageLoadedStartTime', new Date())})
mounted() {this.$nextTick(() => {this.$store.commit('setPageLoadedTime', new Date() - this.$store.state.pageLoadedStartTime)})}
除了性能和錯誤監(jiān)控,其實我們還可以收集更多的信息。
用戶信息收集
navigator
使用 window.navigator[4] 可以收集到用戶的設(shè)備信息,操作系統(tǒng),瀏覽器信息...

UV(Unique visitor)
是指通過互聯(lián)網(wǎng)瀏覽這個網(wǎng)頁的訪客,00:00-24:00 內(nèi)相同的設(shè)備訪問只被計算一次。一天內(nèi)同個訪客多次訪問僅計算一個 UV。
在用戶訪問網(wǎng)站時,可以生成一個隨機字符串+時間日期,保存在本地。在網(wǎng)頁發(fā)生請求時(如果超過當天24小時,則重新生成),把這些參數(shù)傳到后端,后端利用這些信息生成 UV 統(tǒng)計報告。
PV(Page View)
即頁面瀏覽量或點擊量,用戶每 1 次對網(wǎng)站中的每個網(wǎng)頁訪問均被記錄 1 個PV。用戶對同一頁面的多次訪問,訪問量累計,用以衡量網(wǎng)站用戶訪問的網(wǎng)頁數(shù)量。
頁面停留時間
傳統(tǒng)網(wǎng)站
用戶在進入 A 頁面時,通過后臺請求把用戶進入頁面的時間捎上。過了 10 分鐘,用戶進入 B 頁面,這時后臺可以通過接口捎帶的參數(shù)可以判斷出用戶在 A 頁面停留了 10 分鐘。
SPA
可以利用 router 來獲取用戶停留時間,拿 Vue 舉例,通過 router.beforeEach、destroyed 這兩個鉤子函數(shù)來獲取用戶停留該路由組件的時間。
瀏覽深度
通過 document.documentElement.scrollTop 屬性以及屏幕高度,可以判斷用戶是否瀏覽完網(wǎng)站內(nèi)容。
頁面跳轉(zhuǎn)來源
通過 document.referrer 屬性,可以知道用戶是從哪個網(wǎng)站跳轉(zhuǎn)而來。
小結(jié)
通過分析用戶數(shù)據(jù),我們可以了解到用戶的瀏覽習慣、愛好等等信息,想想真是恐怖,毫無隱私可言。
前端監(jiān)控部署
前面說的都是監(jiān)控原理,但要實現(xiàn)還是得自己動手寫代碼。為了避免麻煩,我們可以用現(xiàn)有的工具 sentry[5] 去做這件事。
sentry 是一個用 python 寫的性能和錯誤監(jiān)控工具,你可以使用 sentry 提供的服務(免費功能少),也可以自己部署服務?,F(xiàn)在來看一下如何使用 sentry 提供的服務實現(xiàn)監(jiān)控。
注冊賬號
打開 https://sentry.io/signup/ 網(wǎng)站,進行注冊。


選擇項目,這里用 Vue 做示例。

安裝 sentry 依賴
選完項目,下面會有具體的 sentry 依賴安裝指南。

根據(jù)提示,在你的 Vue 項目執(zhí)行這段代碼 npm install --save @sentry/browser @sentry/integrations @sentry/tracing,安裝 sentry 所需的依賴。
再將下面的代碼拷到你的 main.js,放在 new Vue() 之前。
import * as Sentry from "@sentry/browser";import { Vue as VueIntegration } from "@sentry/integrations";import { Integrations } from "@sentry/tracing";Sentry.init({dsn: "xxxxx", // 這里是你的 dsn 地址,注冊完就有integrations: [new VueIntegration({Vue,tracing: true,}),new Integrations.BrowserTracing(),],// We recommend adjusting this value in production, or using tracesSampler// for finer controltracesSampleRate: 1.0,});
然后點擊第一步中的 skip this onboarding,進入控制臺頁面。
如果忘了自己的 DSN,請點擊左邊的菜單欄選擇 Settings -> Projects -> 點擊自己的項目 -> Client Keys(DSN)。
創(chuàng)建第一個錯誤
在你的 Vue 項目執(zhí)行一個打印語句 console.log(b)。
這時點開 sentry 主頁的 issues 一項,可以發(fā)現(xiàn)有一個報錯信息 b is not defined:

這個報錯信息包含了錯誤的具體信息,還有你的 IP、瀏覽器信息等等。
但奇怪的是,我們的瀏覽器控制臺并沒有輸出報錯信息。
這是因為被 sentry 屏蔽了,所以我們需要加上一個選項 logErrors: true。

然后再查看頁面,發(fā)現(xiàn)控制臺也有報錯信息了:

上傳 sourcemap
一般打包后的代碼都是經(jīng)過壓縮的,如果沒有 sourcemap,即使有報錯信息,你也很難根據(jù)提示找到對應的源碼在哪。
下面來看一下如何上傳 sourcemap。
首先創(chuàng)建 auth token。




這個生成的 token 一會要用到。
安裝 sentry-cli 和 @sentry/webpack-plugin:
npm install sentry-cli-binary -gnpm install --save-dev @sentry/webpack-plugin
安裝完上面兩個插件后,在項目根目錄創(chuàng)建一個 .sentryclirc 文件(不要忘了在 .gitignore 把這個文件添加上,以免暴露 token),內(nèi)容如下:
[auth]token=xxx[defaults]url=https://sentry.io/org=woai3cproject=woai3c
把 xxx 替換成剛才生成的 token。
org 是你的組織名稱。

project 是你的項目名稱,根據(jù)下面的提示可以找到。


在項目下新建 vue.config.js 文件,把下面的內(nèi)容填進去:
const SentryWebpackPlugin = require('@sentry/webpack-plugin')const config = {configureWebpack: {plugins: [new SentryWebpackPlugin({include: './dist', // 打包后的目錄ignore: ['node_modules', 'vue.config.js', 'babel.config.js'],}),],},}// 只在生產(chǎn)環(huán)境下上傳 sourcemapmodule.exports = process.env.NODE_ENV == 'production'? config : {}
填完以后,執(zhí)行 npm run build,就可以看到 sourcemap 的上傳結(jié)果了。

我們再來看一下沒上傳 sourcemap 和上傳之后的報錯信息對比。
未上傳 sourcemap


已上傳 sourcemap


可以看到,上傳 sourcemap 后的報錯信息更加準確。
切換中文環(huán)境和時區(qū)


選完刷新即可。
性能監(jiān)控

打開 performance 選項,就能看到你每個項目的運行情況。具體的參數(shù)解釋請看文檔 Performance Monitoring[6]。
小結(jié)
隨著 web 技術(shù)的發(fā)展,現(xiàn)在前端項目的規(guī)模也越來越大。在監(jiān)控系統(tǒng)的幫助下,我們可以更加清楚的了解項目的運行情況,根據(jù)采集到的錯誤數(shù)據(jù)和性能數(shù)據(jù)對項目做針對性的優(yōu)化。
下一章我們將講解如何做性能優(yōu)化。
參考資料
?7 天打造前端性能監(jiān)控系統(tǒng)[7]?zanePerfor[8]?sentry[9]
References
[1] sentry: https://docs.sentry.io/[2] window.performance: https://developer.mozilla.org/zh-CN/docs/Web/API/Performance[3] navigator.sendBeacon(): https://developer.mozilla.org/zh-CN/docs/Web/API/Navigator/sendBeacon[4] window.navigator: https://developer.mozilla.org/zh-CN/docs/Web/API/Window/navigator[5] sentry: https://docs.sentry.io/[6] Performance Monitoring: https://docs.sentry.io/product/performance/[7] 7 天打造前端性能監(jiān)控系統(tǒng): https://fex.baidu.com/blog/2014/05/build-performance-monitor-in-7-days/[8] zanePerfor: https://github.com/wangweianger/zanePerfor[9] sentry: https://docs.sentry.io/
