百分百空手接大锅

背景

愉快的双休周末刚过完,早上来忽然被运营通知线上业务挂了,用户无法下单。卧槽,赶紧进入debug模式,一查原来是服务端返回的数据有问题,赶紧问了服务端,大佬回复说是业务部门配置套餐错误。好在主责不在我们,不过赶紧写了复盘文档,主动找自己的责任,扛起这口大锅,都怪我们前端,没有做好前端监控,导致线上问题持续两天才发现。原本以为运营会把推辞一下说不,锅是她们的,可惜人家不太懂人情世故,这锅就扣在了技术部头上。虽然但是,我还是静下心来把前端异常监控搞了出来,下次一定不要主动接锅,希望看到本文的朋友们也不要随便心软接锅^_^

监控

因为之前基于sentry做了埋点处理,基础已经打好,支持全自动埋点、手动埋点和数据上报。相关的原理可以参考之前的一篇文章如何从0-1构建数据平台(2)- 前端埋点。本次监控的数据上报也基于sentry.js。那么如何设计整个流程呢。具体步骤如下:

  1. 监控数据分类

  2. 监控数据定义

  3. 监控数据收集

  4. 监控数据上报

  5. 监控数据输出

  6. 监控数据预警

数据分类

我们主要是前端的数据错误,一般的异常大类分为逻辑异常和代码异常。基于我们的项目,由于涉及营收,我们就将逻辑错误专注于支付异常,其他的代码导致的错误分为一大类。然后再将两大异常进行细分,如下:

  1. 支付异常

    1.1 支付成功

    1.2 支付失败

  2. 代码异常

    2.1 bindexception

     2.1.1  js_error
    
    
     2.1.2  img_error
    
    
     2.1.3  audio_error
    
    
     2.1.4  script_error
    
    
     2.1.5 video_error
    
  3. unhandleRejection

    3.1 promise_unhandledrejection_error

    3.2 ajax_error

  4. vueException

  5. peformanceInfo

数据定义

基于sentry的上报数据,一般都包括事件与属性。在此我们定义支付异常事件为“page_h5_pay_monitor”,定义代码异常事件为“page_monitor”。然后支付异常的属性大概为:




    pay_time,



    pay_orderid,



    pay_result,



    pay_amount,



    pay_type,



    pay_use_coupon,




    pay_use_coupon_id,




    pay_use_coupon_name,




    pay_use_discount_amount,




    pay_fail_reason,




    pay_platment




代码异常不同的错误类型可能属性会有所区别:




    // js_error



    monitor_type,



    monitor_message,



    monitor_lineno,



    monitor_colno,



    monitor_error,




    monitor_stack,




    monitor_url




    // src_error




    monitor_type,




    monitor_target_src,




    monitor_url



    // promise_error



    monitor_type,



    monitor_message,



    monitor_stack,



    monitor_url



    // ajax_error



    monitor_type,



    monitor_ajax_method,



    monitor_ajax_data,



    monitor_ajax_params,



    monitor_ajax_url,



    monitor_ajax_headers,



    monitor_url,



    monitor_message,



    monitor_ajax_code



    // vue_error



    monitor_type,



    monitor_message,



    monitor_stack,



    monitor_hook,



    monitor_url



    // peformanceInfo 为数据添加 loading_time 属性,该属性通过entryTypes获取



    try {



        const observer = new PerformanceObserver((list) => {



            for (const entry of list.getEntries()) {



                if (entry.entryType === 'paint') {



                    sa.store.set('loading_time', entry.startTime)



                }
            }

        })

        observer.observe({ entryTypes: ['paint'] })

    } catch (err) {

        console.log(err)

    }

数据收集

数据收集通过事件绑定进行收集,具体绑定如下:

import {


    BindErrorReporter,


    VueErrorReporter,


    UnhandledRejectionReporter


} from './report'

const Vue = require('vue')





// binderror绑定




const MonitorBinderror = () => {




    window.addEventListener(




    'error',




    function(error) {




        BindErrorReporter(error)



    },true )



}



// unhandleRejection绑定 这里由于使用了axios,因此ajax_error也属于promise_error



const MonitorUnhandledRejection = () => {



    window.addEventListener('unhandledrejection', function(error) {



        if (error && error.reason) {



            const { message, code, stack, isAxios, config } = error.reason



            if (isAxios && config) {



                // console.log(config)



                const { data, params, headers, url, method } = config



                UnhandledRejectionReporter({



                isAjax: true,



                data: JSON.stringify(data),



                params: JSON.stringify(params),



                headers: JSON.stringify(headers),



                url,



                method,



                message: message || error.message,



                code



                })



            } else {



                UnhandledRejectionReporter({



                isAjax: false,



                message,



                stack



                })



            }



        }


    })


}


// vueException绑定


const MonitorVueError = () => {


    Vue.config.errorHandler = function(error, vm, info) {


        const { message, stack } = error


        VueErrorReporter({


            message,


            stack,


            vuehook: info


        })


    }


}


// 输出绑定方法


export const MonitorException = () => {


    try {


        MonitorBinderror()


        MonitorUnhandledRejection()


        MonitorVueError()


    } catch (error) {


        console.log('monitor exception init error', error)


    }


}


数据上报

数据上报都是基于sentry进行上报,具体如下:




/*



* 异常监控库 基于sentry jssdk



* 监控类别:



* 1、window onerror 监控未定义属性使用 js资源加载失败问题



* 2、window addListener error 监控未定义属性使用 图片资源加载失败问题



* 3、unhandledrejection 监听promise对象未catch的错误




* 4、vue.errorHandler 监听vue脚本错误




* 5、自定义错误 包括接口错误 或其他diy错误




* 上报事件: page_monitor




*/




// 错误类别常量




const ERROR_TYPE = {



    JS_ERROR: 'js_error',



    IMG_ERROR: 'img_error',



    AUDIO_ERROR: 'audio_error',



    SCRIPT_ERROR: 'script_error',



    VIDEO_ERROR: 'video_error',



    VUE_ERROR: 'vue_error',



    PROMISE_ERROR: 'promise_unhandledrejection_error',



    AJAX_ERROR: 'ajax_error'



}



const MONITOR_NAME = 'page_monitor'



const PAY_MONITOR_NAME = 'page_h5_pay_monitor'



const MEMBER_PAY_MONITOR_NAME = 'page_member_pay_monitor'



export const BindErrorReporter = function(error) {



    if (error) {



        if (error.error) {



            const { colno, lineno } = error



            const { message, stack } = error.error



            // 过滤



            // 客户端会有调用calljs的场景 可能有一些未知的calljs



            if (message && message.toLowerCase().indexOf('calljs') !== -1) {



                return



            }



            sa.track(MONITOR_NAME, {



            //属性



            })



        } else if (error.target) {



            const type = error.target.nodeName.toLowerCase()



            const monitorType = type + '_error'


            const src = error.target.src


            sa.track(MONITOR_NAME, {


            //属性


            })


        }


    }


}


export const UnhandledRejectionReporter = function({


    isAjax = false,


    method,


    data,


    params,


    url,


    headers,


    message,


    stack,


    code


}) {


        if (!isAjax) {


            // 过滤一些特殊的场景


            // 1、自动播放触发问题


            if (message && message.toLowerCase().indexOf('user gesture') !== -1) {


                return


            }

            sa.track(MONITOR_NAME, {

                //属性

            })

        } else {

            sa.track(MONITOR_NAME, {

                //属性

            })

        }

    }

    export const VueErrorReporter = function({ message, stack, vuehook }) {

    sa.track(MONITOR_NAME, {

        //属性

    })

}

export const H5PayErrorReport = ({

    isSuccess = true,

    amount = 0,

    type = -1,

    couponId = -1,

    couponName = '',

    discountAmount = 0,

    reason = '',

    orderid = 0,

}) => {

    // 事件名:page_member_pay_monitor

    sa.track(PAY_MONITOR_NAME, {

        //属性

    })

}

以上,通过sentry的sa.track进行上报,具体不作展开

输出与预警

数据被上报到大数据平台,被存储到hdfs中,然后我们直接做定时任务读取hdfs进行一定的过滤通过钉钉webhook输出到钉钉群,另外如果有需要做数据备份可以通过hdfs到数据仓库再到kylin进行存储。

总结

数据监控对于大的,特别是涉及营收的平台是必要的,我们在设计项目的时候一定要考虑到,最好能说服服务端,让他们服务端也提供相应的代码监控。ngnix层或者云端最好也来一层。严重的异常可以直接给你打电话,目前云平台都有相应支持。这样有异常及时发现,锅嘛,接到手里就可以精准扔出去了。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYoPyZO3' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片