记录--百分百空手接大锅
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
背景
愉快的双休周末刚过完,早上来忽然被运营通知线上业务挂了,用户无法下单。卧槽,赶紧进入debug模式,一查原来是服务端返回的数据有问题,赶紧问了服务端,大佬回复说是业务部门配置套餐错误。好在主责不在我们,不过赶紧写了复盘文档,主动找自己的责任,扛起这口大锅,都怪我们前端,没有做好前端监控,导致线上问题持续两天才发现。原本以为运营会把推辞一下说不,锅是她们的,可惜人家不太懂人情世故,这锅就扣在了技术部头上。虽然但是,我还是静下心来把前端异常监控搞了出来,下次一定不要主动接锅,希望看到本文的朋友们也不要随便心软接锅^_^
监控
因为之前基于sentry做了埋点处理,基础已经打好,支持全自动埋点、手动埋点和数据上报。相关的原理可以参考之前的一篇文章如何从0-1构建数据平台(2)- 前端埋点。本次监控的数据上报也基于sentry.js。那么如何设计整个流程呢。具体步骤如下:
-
监控数据分类
-
监控数据定义
-
监控数据收集
-
监控数据上报
-
监控数据输出
-
监控数据预警
数据分类
我们主要是前端的数据错误,一般的异常大类分为逻辑异常和代码异常。基于我们的项目,由于涉及营收,我们就将逻辑错误专注于支付异常,其他的代码导致的错误分为一大类。然后再将两大异常进行细分,如下:
-
支付异常
1.1 支付成功
1.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
-
unhandleRejection
3.1 promise_unhandledrejection_error
3.2 ajax_error
-
vueException
-
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层或者云端最好也来一层。严重的异常可以直接给你打电话,目前云平台都有相应支持。这样有异常及时发现,锅嘛,接到手里就可以精准扔出去了。