前端異常---日志上報服務搭建
關於前端異常分類與捕獲可以看看我的這篇文章
JavaScript 網頁異常捕獲
既然異常已經捕獲到了,那我們怎么處理呢,如何上報,需要上報哪些內容?
一、日志分類
1、一般日志分類等級
log、debug、info、warn、error
2、分場景使用日志上報類型
log: 記錄流程信息
debug: 記錄調試關鍵信息
info: 記錄業務功能點,是否觸發成功或者失敗
warn: 頁面警告信息
error: 頁面錯誤或者業務異常信息
3、日志上報信息附帶信息
1、用戶id、session、用戶名
2、當前錯誤信息
3、可以用來重現、推斷當前錯誤發生的信息
4、上報時間
5、日志等級
等等
4、日志上報策略
1、達量上傳,設置一個緩存數量,到達即上報。因為不能一發生錯誤就要上報,會影響用戶的網絡。
2、日志埋點處各自有各自的上報等級。
需要有一個總配置地方,配置當前的上報等級
這樣各處埋點可以判斷當前需要上報的日志等級,等級小於設置值的話,不可上報。
3、本地緩存若暫存過多,需要刪除前面的數據
4、抽樣上報
5、設置緩存有效時間
等等
上報之后,接下來的步驟就是在服務端收集分析歸類展示,所以需要我們搭建一整套日志解析系統
今天我們來嘗試使用 badjs 相關項目,搭建一整套日志分析系統
二、badjs 服務安裝
1、前期預備工作
為了快速搭建,我們統一使用 docker 安裝
備注:windows 環境使用 docker,需要安裝 Docker Desktop
a、mysql 安裝
備注:mysql 安裝好后,需要從 badjs-web (需要先把項目下載下來)項目中的 db 目錄下
使用 create.sql
初始化 web 相關的數據庫
b、mongodb 安裝
docker 安裝 mongo
備注:不可設置密碼
2、項目安裝
github 克隆項目到本地
git clone https://github.com/BetterJS/badjs-installer
子項目下載以及依賴安裝
// 克隆下載 badjs-acceptor、badjs-mq、badjs-storage、badjs-web 項目
yarn clone
// 安裝各項目的依賴
yarn install
3、修改配置項
1、修改 badjs-acceptor 項目的 project.debug.json/project.json
注意:這里是日志上報的地方,客戶端初始化 badjs-report 時候需要設置的 url 屬性即是這里的服務地址
http://{badjs-acceptor:port}/badjs
// 修改 port 屬性: 從 80 改為 8083;
// 因為 node 默認沒有 80 端口的權限,需要你使用管理員權限才可以使用
{
"port": 8083
}
2、修改 badjs-web 項目的 project.debug.json/project.json
// 修改 mysql 屬性,配置我們 docker 安裝好的 mysql 用戶密碼與端口
{
"mysql" : {
"url" : "mysql://root:123456@localhost:3306/badjs"
}
}
4、啟動項目
// 啟動各項目
yarn start
查看 badjs-web 的啟動端口,訪問 http://localhost:port 可以看到日志后台管理服務頁面
三、badjs 各模塊關系
1、badjs-acceptor 接受客戶端上報的日志
badjs-acceptor 收到日志上報,發送到 badjs-mq
package.json 中配置 dispatcher 分發屬性, 表示向 badjs-mq 請求的信息:如請求端口(10001)
2、badjs-mq 消息隊列,保證消息有序穩定被接受
badjs-mq 接收 badjs-acceptor 的請求
package.json 中配置 acceptor 接收屬性, 表示用來接收信息所配置的接口信息:如端口(10001)。
badjs-mq 再分發到 badjs-storage
package.json 中配置 dispatcher 分發屬性, 表示向 badjs-storage 請求的信息:如端口(10000)
3、badjs-storage 存儲模塊
badjs-storage: 接收來自 badjs-mq 的請求,再寫入到 mongodb
package.json 中配置 acceptor 接收屬性, 表示用來接收信息所配置的接口信息:如端口(10000)
4、badjs-web 日志后台管理系統
badjs-web 查詢日志存儲,分類查看日志信息,解析日志內容
package.json 中配置 acceptor 接收屬性, badjs-acceptor 可請求的端口
package.json 中配置 storage 存儲屬性, 查詢 mongodb 數據
package.json 中配置 mysql 數據庫屬性, 查詢 mysql 數據
等等
四、上報日志插件 badjs-report
badjs-report 重寫了 window.onerror 來捕獲錯誤
1、安裝
yarn add badjs-report
2、初始化
import badjs from 'badjs-report'
badjs.init({
// 必須配置項
id: 1 // 此 id 為 badjs-web 啟動后,申請的項目的 id,上報的日志根據該 id 區分業務模塊
url: 'http://badjs-acceptor啟動后的地址', // 日志上報到的地方
// 選擇配置
uin: 123, // 指定用戶的 id (該插件默認讀取 qq uin)
delay: 1000, // 延遲多少毫秒,合並緩沖區中的上報(默認 1000)
ignore: [/Script error/i], // 忽略某個錯誤,遇到該錯誤不進行上報
random: 1, // 抽樣上報, 值可以設置 0-1 之間。1 表示 100% 上報(默認為 1)
repeat: 5, // 重復上報次數(對於同一個錯誤超過多少次不上報;避免單個用戶同一錯誤上報過多的情況)
onReport: function(id, errObj) {}, // 上報日志之后的回調。id 為上報的 id,errorObj 為上報的錯誤對象
submit: function(url) {}, // 覆蓋原來的上報方式,原來是使用 new Image() 形式上報,可以修改成自己想要上報的方式,比如使用 post 內部構造好的 url
ext: {}, // 擴展屬性,后端做擴展處理屬性。設置了 ext 的值,就會作為 'ext=設置的值' 合並到構造好的上報 url 中
offlineLog: false, // 是否開啟離線日志(默認不開啟為 false)
offlineLogExp: 5 // 離線有效時間(默認最近5天)
})
3、手動上報
a、badjs.report('error msg')
b、badjs.report({
msg: 'error msg', // 需要上報的錯誤信息
target: 'error.js', // 發生錯誤的 js 文件
rowNum: 1, // 發生錯誤的行數
colNum: 2 // 發生錯誤的列數
})
4、延遲上報
暫存
badjs.push('error msg')
badjs.push({
msg: 'error msg', // 需要上報的錯誤信息
target: 'error.js', // 發生錯誤的 js 文件
rowNum: 1, // 發生錯誤的行數
colNum: 2 // 發生錯誤的列數
})
立即上報
badjs.report({
msg: 'error msg', // 需要上報的錯誤信息
target: 'error.js', // 發生錯誤的 js 文件
rowNum: 1, // 發生錯誤的行數
colNum: 2 // 發生錯誤的列數
})
5、上報離線日志
badjs.reportOfflineLog()
五、項目使用示例
import BJ_REPORT from 'badjs-report'
import Vue from 'vue'
// 環境ID枚舉
let ENV_ID_ENUM = {
DEV: 1,
SIT: 2,
UAT: 3,
PRO: 4
}
let curEnv = '',
origin = window.location.origin
if (origin.indexOf('dev.xxx.com') > -1) {
// http://dev.xxx.com
// DEV 環境
curEnv = 'DEV'
} else if (origin.indexOf('sit.xxx.com') > -1) {
// http://sit.xxx.com
// SIT 環境
curEnv = 'SIT'
} else if (origin.indexOf('uat.xxx.com') > -1) {
// http://uat.xxx.com
// UAT 環境
curEnv = 'UAT'
} else if (origin.indexOf('m.xxx.com') > -1) {
// http://pro.xxx.com
// PRO 環境
curEnv = 'PRO'
}
let envID = ENV_ID_ENUM[curEnv] || ENV_ID_ENUM.DEV
// 初始化日志上報插件
BJ_REPORT.init({
id: envID, // 不指定 id 將不上報,
url: 'http://{badjs-acceptor:port}/badjs'
})
// 初始化項目時,可以暴露一個全局的 vm 實例,方便上傳需要的信息
window.rootvm = new Vue({}) // 初始化項目
// 初始化監聽異常
init(window.rootvm)
function init(rootInstance) {
Vue.config.errorHandler = function(err, curInstance, info) {
// vm 為拋出異常的 Vue 實例
// info 為 Vue 特定的錯誤信息,比如錯誤所在的生命周期鈎子
let {
message, // 異常信息
// name, // 異常名稱
// script, // 異常腳本url
line, // 異常行號
column, // 異常列號
stack // 異常堆棧信息
} = err
log(message, stack, line, column, curInstance)
console.log('vue errorHandler :>> ', err, curInstance, info)
}
window.addEventListener(
'error',
e => {
let { colno, lineno, message, filename } = e
log(message, filename, lineno, colno, rootInstance)
console.log('addEventListener error 捕獲階段>>>異常:', e)
},
true
)
window.addEventListener('unhandledrejection', function(e) {
// e.preventDefault(); // 阻止異常向上拋出
let { reason } = e
log(JSON.stringify(reason), '', '', '', rootInstance)
console.log('Promise 異常 unhandledrejection :', e)
})
}
function log(msg, target, rowNum, colNums, vminstance) {
let msgs = `***[${msg}]***`,
state = (vminstance && vminstance.$store && vminstance.$store.state) || {}
// 用戶信息
if (state.userInfo) {
let { phoneNumber, userId } = state.userInfo
msgs += `***[phone:${phoneNumber}--userId:${userId}]***`
}
// 路由信息
let { name, fullPath } = (vminstance && vminstance.$route) || {}
msgs += `***[router-name: ${name} -- router-fullpath: ${fullPath}]***`
BJ_REPORT.report({
msg: msgs,
target,
rowNum,
colNums
})
}