记录--百分百空手接大锅
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
背景
愉快的双休周末刚过完,早上来忽然被运营通知线上业务挂了,用户无法下单。卧槽,赶紧进入debug模式,一查原来是服务端返回的数据有问题,赶紧问了服务端,大佬回复说是业务部门配置套餐错误。好在主责不在我们,不过赶紧写了复盘文档,主动找自己的责任,扛起这口大锅,都怪我们前端,没有做好前端监控,导致线上问题持续两天才发现。原本以为运营会把推辞一下说不,锅是她们的,可惜人家不太懂人情世故,这锅就扣在了技术部头上。虽然但是,我还是静下心来把前端异常监控搞了出来,下次一定不要主动接锅,希望看到本文的朋友们也不要随便心软接锅^_^
监控
因为之前基于sentry做了埋点处理,基础已经打好,支持全自动埋点、手动埋点和数据上报。相关的原理可以参考之前的一篇文章如何从0-1构建数据平台(2)- 前端埋点。本次监控的数据上报也基于sentry.js。那么如何设计整个流程呢。具体步骤如下:
-
监控数据分类
-
监控数据定义
-
监控数据收集
-
监控数据上报
-
监控数据输出
-
监控数据预警
数据分类
我们主要是前端的数据错误,一般的异常大类分为逻辑异常和代码异常。基于我们的项目,由于涉及营收,我们就将逻辑错误专注于支付异常,其他的代码导致的错误分为一大类。然后再将两大异常进行细分,如下:
-
支付异常
1.1 支付成功
1.2 支付失败
-
代码异常
2.1 bindexception
1 2 3 4 5 6 7 8 9 | 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”。然后支付异常的属性大概为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | // 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) } |
数据收集
数据收集通过事件绑定进行收集,具体绑定如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | 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进行上报,具体如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | /* * 异常监控库 基于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层或者云端最好也来一层。严重的异常可以直接给你打电话,目前云平台都有相应支持。这样有异常及时发现,锅嘛,接到手里就可以精准扔出去了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
2022-07-21 web前端工程化合集