vue语法错误 + Promise错误 + js 错误,通过钉钉报警

发布时间 2023-08-14 17:53:57作者: 小短腿奔跑吧
 

 

一、背景:

为了使系统更加稳定,在用户使用期间,若发现异常,可及时应对,采取了“报警机制”。

通常“报警机制”分为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

 五、最终实现效果