摘要:另外這樣的異常捕獲不能捕獲的異常錯誤信息,這點需要注意。最終大致的流程圖如下結語前端異常捕獲與上報是前端異常監控的前提,了解并做好了異常數據的收集和分析才能實現一個完善的錯誤響應和處理機制,最終達成數據可視化。
關于
微信公眾號:前端呼啦圈(Love-FED)
我的博客:勞卜的博客
知乎專欄:前端呼啦圈
前言Hello,大家好,又與大家見面了,這次給大家分享下前端異常監控中需要了解的異常捕獲與上報機制的一些要點,同時包含了實戰性質的參考代碼和流程。
首先,我們為什么要進行異常捕獲和上報呢?
正所謂百密一疏,一個經過了大量測試及聯調的項目在有些時候還是會有十分隱蔽的bug存在,這種復雜而又不可預見性的問題唯有通過完善的監控機制才能有效的減少其帶來的損失,因此對于直面用戶的前端而言,異常捕獲與上報是至關重要的。
雖然目前市面上已經有一些非常完善的前端監控系統存在,如sentry、bugsnag等,但是知己知彼,才能百戰不殆,唯有了解原理,摸清邏輯,使用起來才能得心應手。
異常捕獲方法 1. try catch通常,為了判斷一段代碼中是否存在異常,我們會這一寫:
try { var a = 1; var b = a + c; } catch (e) { // 捕獲處理 console.log(e); // ReferenceError: c is not defined }
使用try catch能夠很好的捕獲異常并對應進行相應處理,不至于讓頁面掛掉,但是其存在一些弊端,比如需要在捕獲異常的代碼上進行包裹,會導致頁面臃腫不堪,不適用于整個項目的異常捕獲。
2. window.onerror相比try catch來說window.onerror提供了全局監聽異常的功能:
window.onerror = function(errorMessage, scriptURI, lineNo, columnNo, error) { console.log("errorMessage: " + errorMessage); // 異常信息 console.log("scriptURI: " + scriptURI); // 異常文件路徑 console.log("lineNo: " + lineNo); // 異常行號 console.log("columnNo: " + columnNo); // 異常列號 console.log("error: " + error); // 異常堆棧信息 }; console.log(a);
如圖:
window.onerror即提供了我們錯誤的信息,還提供了錯誤行列號,可以精準的進行定位,如此似乎正是我們想要的,但是接下來便是填坑過程。
異常捕獲問題 1. Script error.我們合乎情理地在本地頁面進行嘗試捕獲異常,如:
這里我們把靜態資源放到異域上進行優化加載,但是捕獲的異常信息卻是:
經過分析發現,跨域之后window.onerror是無法捕獲異常信息的,所以統一返回Script error.,解決方案便是script屬性配置 crossorigin="anonymous" 并且服務器添加Access-Control-Allow-Origin。
一般的CDN網站都會將Access-Control-Allow-Origin配置為*,意思是所有域都可以訪問。
2. sourceMap解決跨域或者將腳本存放在同域之后,你可能會將代碼壓縮一下再發布,這時候便出現了壓縮后的代碼無法找到原始報錯位置的問題。如圖,我們用webpack將代碼打包壓縮成bundle.js:
// webpack.config.js var path = require("path"); // webpack 4.1.1 module.exports = { mode: "development", entry: "./client/index.js", output: { filename: "bundle.js", path: path.resolve(__dirname, "client") } }
最后我們頁面引入的腳本文件是這樣的:
!function(e){var o={};function n(r){if(o[r])return o[r].exports;var t=o[r]={i:r,l:!1,exports:{}}...;
所以我們看到的異常信息是這樣的:
lineNo可能是一個非常小的數字,一般是1,而columnNo會是一個很大的數字,這里是730,因為所有代碼都壓縮到了一行。
那么該如何解決呢?聰明的童鞋可能已經猜到啟用source-map了,沒錯,我們利用webpack打包壓縮后生成一份對應腳本的map文件就能進行追蹤了,在webpack中開啟source-map功能:
module.exports = { ... devtool: "#source-map", ... }
打包壓縮的文件末尾會帶上這樣的注釋:
!function(e){var o={};function n(r){if(o[r])return o[r].exports;var t=o[r]={i:r,l:!1,exports:{}}...; //# sourceMappingURL=bundle.js.map
意思是該文件對應的map文件為bundle.js.map。下面便是一個source-map文件的內容,是一個JSON對象:
version: 3, // Source map的版本 sources: ["webpack:///webpack/bootstrap", ...], // 轉換前的文件 names: ["installedModules", "__webpack_require__", ...], // 轉換前的所有變量名和屬性名 mappings: "aACA,IAAAA,KAGA,SAAAC...", // 記錄位置信息的字符串 file: "bundle.js", // 轉換后的文件名 sourcesContent: ["http:// The module cache var installedModules = {};..."], // 源代碼 sourceRoot: "" // 轉換前的文件所在的目錄
如果你想詳細了解關于sourceMap的知識,可以前往:JavaScript Source Map 詳解
如此,既然我們拿到了對應腳本的map文件,那么我們該如何進行解析獲取壓縮前文件的異常信息呢?這個我會在下面異常上報的時候進行介紹。
3. MVVM框架現在越來越多的項目開始使用前端框架,在MVVM框架中如果你一如既往的想使用window.onerror來捕獲異常,那么很可能會竹籃打水一場空,或許根本捕獲不到,因為你的異常信息被框架自身的異常機制捕獲了。比如Vue 2.x中我們應該這樣捕獲全局異常:
Vue.config.errorHandler = function (err, vm, info) { let { message, // 異常信息 name, // 異常名稱 script, // 異常腳本url line, // 異常行號 column, // 異常列號 stack // 異常堆棧信息 } = err; // vm為拋出異常的 Vue 實例 // info為 Vue 特定的錯誤信息,比如錯誤所在的生命周期鉤子 }
目前script、line、column這3個信息打印出來是undefined,不過這些信息在stack中都可以找到,可以通過正則匹配去進行獲取,然后進行上報。
同樣的在react也提供了異常處理的方式,在 React 16.x 版本中引入了 Error Boundary:
class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } componentDidCatch(error, info) { this.setState({ hasError: true }); // 將異常信息上報給服務器 logErrorToMyService(error, info); } render() { if (this.state.hasError) { return "出錯了"; } return this.props.children; } }
然后我們就可以這樣使用該組件:
詳見官方文檔:Error Handling in React 16
異常上報以上介紹了前端異常捕獲的相關知識點,那么接下來我們既然成功捕獲了異常,那么該如何上報呢?
在腳本代碼沒有被壓縮的情況下可以直接捕獲后上傳對應的異常信息,這里就不做介紹了,下面主要講解常見的處理壓縮文件上報的方法。
1. 提交異常當捕獲到異常時,我們可以將異常信息傳遞給接口,以window.onerror為例:
window.onerror = function(errorMessage, scriptURI, lineNo, columnNo, error) { // 構建錯誤對象 var errorObj = { errorMessage: errorMessage || null, scriptURI: scriptURI || null, lineNo: lineNo || null, columnNo: columnNo || null, stack: error && error.stack ? error.stack : null }; if (XMLHttpRequest) { var xhr = new XMLHttpRequest(); xhr.open("post", "/middleware/errorMsg", true); // 上報給node中間層處理 xhr.setRequestHeader("Content-Type", "application/json"); // 設置請求頭 xhr.send(JSON.stringify(errorObj)); // 發送參數 } }2. sourceMap解析
其實source-map格式的文件是一種數據類型,既然是數據類型那么肯定有解析它的辦法,目前市面上也有專門解析它的相應工具包,在瀏覽器環境或者node環境下比較流行的是一款叫做"source-map"的插件。
通過require該插件,前端瀏覽器可以對map文件進行解析,但因為前端解析速度較慢,所以這里不做推薦,我們還是使用服務器解析。如果你的應用有node中間層,那么你完全可以將異常信息提交到中間層,然后解析map文件后將數據傳遞給后臺服務器,中間層代碼如下:
const express = require("express"); const fs = require("fs"); const router = express.Router(); const fetch = require("node-fetch"); const sourceMap = require("source-map"); const path = require("path"); const resolve = file => path.resolve(__dirname, file); // 定義post接口 router.post("/errorMsg/", function(req, res) { let error = req.body; // 獲取前端傳過來的報錯對象 let url = error.scriptURI; // 壓縮文件路徑 if (url) { let fileUrl = url.slice(url.indexOf("client/")) + ".map"; // map文件路徑 // 解析sourceMap let smc = new sourceMap.SourceMapConsumer(fs.readFileSync(resolve("../" + fileUrl), "utf8")); // 返回一個promise對象 smc.then(function(result) { // 解析原始報錯數據 let ret = result.originalPositionFor({ line: error.lineNo, // 壓縮后的行號 column: error.columnNo // 壓縮后的列號 }); let url = ""; // 上報地址 // 將異常上報至后臺 fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ errorMessage: error.errorMessage, // 報錯信息 source: ret.source, // 報錯文件路徑 line: ret.line, // 報錯文件行號 column: ret.column, // 報錯文件列號 stack: error.stack // 報錯堆棧 }) }).then(function(response) { return response.json(); }).then(function(json) { res.json(json); }); }) } }); module.exports = router;
這里我們通過前端傳過來的異常文件路徑獲取服務器端map文件地址,然后將壓縮后的行列號傳遞給sourceMap返回的promise對象進行解析,通過originalPositionFor方法我們能獲取到原始的報錯行列號和文件地址,最后通過ajax將需要的異常信息統一傳遞給后臺存儲,完成異常上報。下圖可以看到控制臺打印出了經過解析后的真是報錯位置和文件:
附:source-map API
3. 注意點以上是異常捕獲和上報的主要知識點和流程,還有一些需要注意的地方,比如你的應用訪問量很大,那么一個小異常都可能會把你的服務器搞掛,所以上報的時候可以進行信息過濾和采樣等,設置一個調控開關,服務器也可以對相似的異常進行過濾,在一個時間段內不進行多次存儲。另外window.onerror這樣的異常捕獲不能捕獲promise的異常錯誤信息,這點需要注意。
最終大致的流程圖如下:
前端異常捕獲與上報是前端異常監控的前提,了解并做好了異常數據的收集和分析才能實現一個完善的錯誤響應和處理機制,最終達成數據可視化。本文詳細實例代碼地址:https://github.com/luozhihao/error-catch-report
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/93710.html
摘要:回過頭來發現,我們的項目,雖然在服務端層面做好了日志和性能統計,但在前端對異常的監控和性能的統計。對于前端的性能與異常上報的可行性探索是有必要的。這是我們頁面加載性能優化需求中主要上報的相關信息。 概述 對于后臺開發來說,記錄日志是一種非常常見的開發習慣,通常我們會使用try...catch代碼塊來主動捕獲錯誤、對于每次接口調用,也會記錄下每次接口調用的時間消耗,以便我們監控服務器接口...
摘要:如何在新的技術背景下讓前端數據采集工作更加完善高效,是本文討論的重點。具體來說,我們對前端的數據采集具體主要分為路由切換性能資源錯誤日志上報路由切換等前端技術的快速發展使單頁面應用盛行。 隨著業務的快速發展,我們對生產環境下的問題感知能力越來越關注。作為距離用戶最近的一層,前端的表現是否可靠、穩定、好用,很大程度上決定著用戶對整個產品的體驗和感受。因此,對于前端的監控不容忽視。 搭建一...
摘要:我已淪為狗從月份一開始,新項目正式啟動,產品以客戶端的形式發布,目前用的是的內嵌網頁的形式開發,對于后端來說,一切都是那么自然簡單可對于前端來說,徹底將我帶入到了的黑洞,自此萬劫不復說實話,我內心無比鄙視這個,因為綁定了,默認使用內核,按理 我已淪為IE狗!!! 從9月份一開始,新項目正式啟動,產品以客戶端的形式發布,目前用的是.NET的WebBrowser內嵌網頁的形式開發,對于后端...
摘要:前端異常監控如果是移除的流程,那么編程就一定是將放進去的流程。過濾掉運行時錯誤上報加載錯誤事件捕獲異常最新的規范中定義了事件用于全局捕獲對象沒有處理器時異常情況。 前端異常監控 如果debug是移除bug的流程,那么編程就一定是將bug放進去的流程。如果沒有用戶反饋問題,那就代表我們的產品棒棒噠,對不對? 主要內容 Web規范中相關前端異常 異常按照捕獲方式分類 異常的捕獲方式 日志...
閱讀 1164·2023-04-26 01:35
閱讀 2522·2021-11-02 14:44
閱讀 7680·2021-09-22 15:38
閱讀 2208·2021-09-06 15:11
閱讀 3727·2019-08-30 15:53
閱讀 803·2019-08-29 16:54
閱讀 634·2019-08-26 13:48
閱讀 1775·2019-08-26 13:47