百分百空手接大锅
背景
愉快的双休周末刚过完,早上来忽然被运营通知线上业务挂了,用户无法下单。卧槽,赶紧进入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_errorunhandleRejection
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层或者云端最好也来一层。严重的异常可以直接给你打电话,目前云平台都有相应支持。这样有异常及时发现,锅嘛,接到手里就可以精准扔出去了。
来源:juejin.cn/post/7244363578429030459