vue語法錯誤 + Promise錯誤 + js 錯誤,通過釘釘報警

 

一、背景:

爲了使系統更加穩定,在用戶使用期間,若發現異常,可及時應對,採取了“報警機制”。

通常“報警機制”分爲2種,一種是後端對api監控及自定義監控,出現異常,通過釘釘或郵件的形式通知,第二種是前端對js語法,vue語法,自定義報錯進行監控,以此來規範代碼質量,保證系統預警

二、流程步驟

1. 收集錯誤(錯誤類型包含 vue錯誤 + js 錯誤 + Promise 錯誤 + 自定義錯誤)

2. 關聯釘釘

3. 錯誤信息發送

三、前期準備內容

1. 釘釘軟件,自定義機器人接入,文檔鏈接

2. 簽名計算(前後端聯調,統一加密方式)

四、參考代碼

1. 收集錯誤階段(main.js)

 1 import { createApp } from 'vue'
 2 import { errorHandler, detectOS, digits, getBrowserInfo, format } from '@/assets/scripts/errorPlugin' // 收集錯誤信息
 3 
 4 const app = createApp(App)
 5 // 1. 用於組件生命週期中的錯誤、自定義事件處理函數內部錯誤、v-on DOM 監聽器內部拋出的錯誤、處理返回 Promise 鏈的錯誤
 6 app.config.errorHandler = errorHandler
 7 // 2. 處理 JS 的額外錯誤
 8 // eslint-disable-next-line max-params
 9 window.onerror = function (message, source, line, column, error) {
10     // 瞭解文檔: https://juejin.cn/post/6844904093979246606
11     if (message === 'ResizeObserver loop limit exceeded') {
12         console.warn('Ignored: ResizeObserver loop limit exceeded')
13         return false
14     }
15     if (message == 'cancel') return false
16     let errMsg = null
17     if (message == 'Script error.') {
18         // 跨域
19         errMsg = `
20         --infoType: JS 無法訪問, 請在控制檯查看具體錯誤
21         --apName : 用戶端-${process.env.NODE_ENV === 'development' ? '測試環境' : '生產環境'}
22         --url: ${window.location.href}
23         --browser:${detectOS()}-${digits()} ${getBrowserInfo()}
24         --time: ${format('yyyy-MM-dd hh:mm:ss')}
25         --userInfo: ${sessionStorage.getItem('AI_INFO')}
26         --info: 瀏覽器跨域請求一個腳本執行出錯
27         `
28         return false
29     }
30     // ------排除這兩個文件錯誤信息的檢查開始-----
31     let noNeedFile = ['app', 'contextMenuFilter']
32     let noContinue = false
33     noNeedFile.map((res) => {
34         if (source.indexOf(res) != -1) noContinue = true
35     })
36     console.log('排除文件了')
37     if (noContinue) return false
38     console.log('沒排除文件')
39     // ------排除這兩個文件錯誤信息的檢查結束-----
40     errMsg = `
41         --infoType: JS 錯誤
42         --apName : 用戶端-${process.env.NODE_ENV === 'development' ? '測試環境' : '生產環境'}
43         --url: ${window.location.href}
44         --browser:${detectOS()}-${digits()} ${getBrowserInfo()}
45         --time: ${format('yyyy-MM-dd hh:mm:ss')}
46         --userInfo: ${sessionStorage.getItem('AI_INFO')}
47         --info: ${message}-${source}-${JSON.stringify(error)}
48         `
49     errorHandler(errMsg, null, 'JS錯誤')
50 }
51 // 3. 處理 Promise 錯誤
52 window.addEventListener('unhandledrejection', (event) => {
53     console.log('event', event.reason)
54     // 全局存在的未處理的 Promise 異常,比如: Promise.reject()
55     // 場景: 接口異常
56     if (event.reason == 'cancel') return false
57     let errMsg = `
58     --infoType: 捕獲Promise異常
59     --apName : 用戶端-${process.env.NODE_ENV === 'development' ? '測試環境' : '生產環境'}
60     --url: ${window.location.href}
61     --browser:${detectOS()}-${digits()} ${getBrowserInfo()}
62     --time: ${format('yyyy-MM-dd hh:mm:ss')}
63     --userInfo: ${sessionStorage.getItem('AI_INFO')}
64     --errorInfo: ${JSON.stringify(event.reason)}
65     `
66 
67     errorHandler(errMsg, null, 'Promise錯誤')
68 })
69 app.mount('#app')
View Code

2. 關聯釘釘(src\assets\scripts\robot.js)

View Code

3. 發送錯誤信息到釘釘軟件(src\assets\scripts\errorPlugin.js) 

import ChatBot from './robot.js'

export const errorHandler = (err, vm, info) => {
    let token = sessionStorage.getItem('AI_TOKEN') || null
    if (!token) return
    let errInfo = null
    if (info !== 'JS錯誤' || info !== 'Promise錯誤') {
        errInfo = `
        --infoType: vue異常錯誤
        --apName : 用戶端-${process.env.NODE_ENV === 'development' ? '測試環境' : '生產環境'}
        --url: ${window.location.href}
        --browser:${detectOS()}-${digits()} ${getBrowserInfo()}
        --time: ${format('yyyy-MM-dd hh:mm:ss')}
        --userInfo: ${sessionStorage.getItem('AI_INFO')}
        --errorInfo: ${err}-${JSON.stringify(info)}
        `
    } else {
        errInfo = err
    }
    // 將捕獲的錯誤, 通過釘釘報警
    robotDD(errInfo)
}

const robotDD = (errMsg) => {
    const robot = new ChatBot({
        webhook: 'https://oapi.dingtalk.com/robot/send?access_token=***',
        secret: '***'
    })
    // 規定發送的消息的類型和參數
    let textContent = {
        msgtype: 'text',
        text: {
            content: errMsg // 注意了,字符串裏面的錯誤漢字,其實就是你在釘釘報警設置的自定義字段,兩個地方需要相同,否則不會發送到羣裏
        }
    }
    // 機器人發送消息
    robot
        .send(textContent)
        .then((res) => {
            console.error(res)
        })
        .catch(() => {
            console.log('釘釘報警錯誤')
            // ElMessage.error({
            //     message: `釘釘報警錯誤`
            // })
        })
}
export const format = (fmt) => {
    //author: meizz
    var o = {
        'M+': new Date().getMonth() + 1, //月份
        'd+': new Date().getDate(), //
        'h+': new Date().getHours(), //小時
        'm+': new Date().getMinutes(), //
        's+': new Date().getSeconds(), //
        'q+': Math.floor((new Date().getMonth() + 3) / 3), //季度
        S: new Date().getMilliseconds() //毫秒
    }

    if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (new Date().getFullYear() + '').substr(4 - RegExp.$1.length))
    for (var k in o) if (new RegExp('(' + k + ')').test(fmt)) fmt = fmt.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length))
    return fmt
}

/**
 * 初始化設備信息
 */
export const initDeviceInfo = () => {
    let _deviceInfo = '' //設備信息
    console.log(navigator, 'navigator')
    if (navigator == null) {
        _deviceInfo = 'PC'
    }
    if (navigator.userAgent != null) {
        var su = navigator.userAgent.toLowerCase(),
            mb = ['ipad', 'iphone os', 'midp', 'rv:1.2.3.4', 'ucweb', 'android', 'windows ce', 'windows mobile']
        // 開始遍歷提前設定好的設備關鍵字,如果設備信息中包含關鍵字,則說明是該設備
        for (var i in mb) {
            if (su.indexOf(mb[i]) > 0) {
                _deviceInfo = mb[i]
                break
            }
        }
    }
    return _deviceInfo
}

/**
 * 獲取瀏覽器的信息
 */

export const getBrowserInfo = () => {
    var output = 'other'
    // Opera 8.0+
    var isOpera = (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0
    if (isOpera) {
        output = 'Opera'
    }
    // Firefox 1.0+
    var isFirefox = typeof InstallTrigger !== 'undefined'
    if (isFirefox) {
        output = 'Firefox'
    }
    // Safari 3.0+ "[object HTMLElementConstructor]"
    var isSafari =
        /constructor/i.test(window.HTMLElement) ||
        (function (p) {
            return p.toString() === '[object SafariRemoteNotification]'
        })(!window['safari'] || (typeof safari !== 'undefined' && safari.pushNotification))
    if (isSafari) {
        output = 'Safari'
    }
    // Internet Explorer 6-11
    var isIE = /*@cc_on!@*/ false || !!document.documentMode
    if (isIE) {
        output = 'IE'
    }
    // Edge 20+
    var isEdge = !isIE && !!window.StyleMedia
    if (isEdge) {
        output = 'Edge'
    }
    // Chrome 1 - 79
    var isChrome = !!window.chrome && navigator.userAgent.indexOf('Chrome') !== -1
    if (isChrome) {
        output = 'Chrome'
    }
    // Edge (based on chromium) detection
    var isEdgeChromium = isChrome && navigator.userAgent.indexOf('Edg') !== -1
    if (isEdgeChromium) {
        output = 'EdgeChromium'
    }
    return output
}

export const detectOS = () => {
    var userAgent = window.navigator.userAgent,
        platform = window.navigator.platform,
        macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'],
        windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE'],
        iosPlatforms = ['iPhone', 'iPad', 'iPod'],
        os = null

    if (macosPlatforms.indexOf(platform) !== -1) {
        os = 'Mac OS'
    } else if (iosPlatforms.indexOf(platform) !== -1) {
        os = 'iOS'
    } else if (windowsPlatforms.indexOf(platform) !== -1) {
        os = 'Windows'
    } else if (/Android/.test(userAgent)) {
        os = 'Android'
    } else if (!os && /Linux/.test(platform)) {
        os = 'Linux'
    }
    return os
}

export const digits = () => {
    var sUserAgent = navigator.userAgent
    var is64 = sUserAgent.indexOf('WOW64') > -1
    if (is64) {
        return '64bit'
    } else {
        return '32bit'
    }
}
View Code

五、跨域解決

import { defineConfig } from 'vite'

export default defineConfig(({ command, mode, ssrBuild }) => {
    return {
        base: '/',
        server: {
            host: '0.0.0.0',
            port: 8080,
            proxy: {
                '/api': {
                    target: 'https://oapi.dingtalk.com', // 代理地址
                    changeOrigin: true, // 是否允許跨域,爲true代表允許
                    rewrite: (path) => path.replace(/^\/api/, '')
                }
            }
        }
    }
})
View Code

 

 

六、最終實現效果

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章