记录--百分百空手接大锅

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

背景

愉快的双休周末刚过完,早上来忽然被运营通知线上业务挂了,用户无法下单。卧槽,赶紧进入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
  1. unhandleRejection

    3.1 promise_unhandledrejection_error

    3.2 ajax_error

  2. vueException

  3. 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层或者云端最好也来一层。严重的异常可以直接给你打电话,目前云平台都有相应支持。这样有异常及时发现,锅嘛,接到手里就可以精准扔出去了。

本文转载于:

https://juejin.cn/post/7244363578429030459

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 

热门相关:流鱼无恙   上神来了   戏精老公今天作死没   锦庭娇   上神来了