Open chenfei-hnu opened 2 years ago
当需求方案存疑时,结合数据给需求提出建议 当发生线上问题时,快速找到关键信息以定位问题 当想提升前端体验时,怎主动发现优化空间
从业务理解来说,我们需要知道用户的使用情况,包括PV、UV、访问时段、访问时长等。 从问题排障来说,我们需要知道用户的使用历史,包括用户发出的接口请求、页面报错等。 从体验优化来说,我们需要知道真实的性能数据,包括页面加载和资源加载的耗时。
1.Sentry 不实用 2.阿里云ARMS 太贵 3.岳鹰 不能私有化部署
前端工程师观察业务效果(页面访问PV,自定义上报)、分析线上问题(页面访问,接口请求,前端报错)、发现优化空间(页面性能,资源加载)的监控工具
整体 数据采集 -日志上报 -日志查询
细化 收集 - 处理日志 - 存储 - UI界面 JS SDK - 上报日志 - 处理日志 - HTTP Server - 日志落库 - ElasticSearch - 查询日志 - Kibana
再细化 JS-SDK web/小程序(页面访问日志,资源加载日志,页面性能日志,接口请求日志,前端报错日志,自定义日志)- 上报日志 - HTTP Server - 日志落库 - ElasticSearch,时序数据库 - Kibana(看具体日志,下钻分析),Grafana(整体分析,曲线)
debug 调试模式,将收集到的日志数规打到console(可用于开发阶段的险证) silence 静音模式,会执行湿辑,但不会真正上报日志(可用于测试环境不上报日志) sendPv 是否上报页面PV sendApi 是否上报API接口请求 sendResource 是否上报资源请求 sendError 是否上报js error sendPerf 是否上报页面性能 spa 是否SPA单页应用页面 bizUserld 绑定业务中的用户ID bizUserType 绑定业务中的用户类型 apilgnoreList 需要忽略的api请求列表,要包含protocol协议头, path部分可填可不填['https://restapi.amap.com','https://miao.baidu.com/abdr'] apiBizCodeParser 解析HTTP响应中的业务状态码(非HTTP状态码)
new Image().src 优点:简单,兼容性好,没有跨域问题 缺点:URL有长度限制,不利于日志聚合上报
POST 优点:请求的body参数没有长度限制,可以以日志聚合上报 缺点:需要跨域,且页面退出时POST请求发不出去
WebSocket 优点:只建立一次链接,后续日志上报的性能较好 缺点:对上报的服务器压力大,容易连接数溢出
最终使用POST:延迟上报 + 聚合上报 + 重试机制 + 退出兜底 sendBeacon
步骤 1.main 2.initSDK 3.network,error,resource,pageview,pert,custom 4.sender 先定义一些全局变量 const win = window; const siliceFn = [].slice.call;
watchApi
export default function(config: SdkConfig) { const proto = win.XMLHttpRequest.prorotype; const originalOpen = proto.open; const originalSend = proto.send; proto.open = function (mthod: string, url: string){ //可以加定制逻辑 省略... originalOpen.apply(this,sliceFn.call(arguments) as any); } proto.send= function(){ const that = this; function handler() { if (that.readyState === 4) { // 上报 that.responseURL 省略... } } const originalFn = that.onreadystatechange; that.onreadystatechange = function () { handler.apply(this,sliceFn.call(arguments) as any); originalFn && originalFn.apply(this, sliceFn.call(arguments) as any); }; return originalSend.apply(this, sliceFn.call(arguments) as any); } }
export default function(config: SdkConfig) { const originalOnError = win.onerror; function errorHandler(message: string, source?: string, lineno?: number,colno?: number, error?: Error) { if(originalOnError){ try { originalOnError.call(win, message,source,lineno,colno,error); } catch (err) {} } if(error !== null){ // 上报错误日志 error.stack 省略... } } // 重写onerror window.onerror = function(message,source,lineno,colno,error) { errorHandler(message as string,source,lineno,colno,error); } // 收集未处理的Promise reject addEventListener(win,'unhandledrejection',function(evtL PromiseRejectionEvent) { const message = evt.reason?.message || evt.reason || evt.type; errorHandler(message,undefined,undefined,evt.reason || evt.type); }) }
监听hashchange + 劫持 history
export default function(config: SdkConfig) { let lastVisit = ''; functin onLoad() { sender.reportPV(); lastVisit = location.href; } function onHashChange() { sender.reportPV({ spa: config.spa, from: lastVisit, }); lastVisit = location.href; } // 监听hash路由 addEventListener(win, 'hashchange', onHashChange); addEventListener(win, 'load', onLoad); function hijackHistory(fnName: string) { const hostory: any = win.history || {}; const originnalFn = history[fnName]; history[fnName] = function(state: object, title: string, newUrl: string) { const curUrl = location.href; originalFn && originalFn.call(thistory,state,title,newUrl); if(newUrl !== curUrl) { dispatchCustomEvent('historystatechange', newUrl); } } } hijackHistory('pushState'); hijackHistory('repalceState'); // 监听history 路由 addEventListener(win,'historystatechange',onHistoryStateChange); }
PerformaceEntry performace.getEntriesByType('resource') 通过initiatorType字段过滤资源类型 压缩体积 压缩变量名,提取公共变量 使用自执行函数的方式打包 life风格 如何支持多端小程序
function getPlatformInfo() { // 微信小程序 if(typeof wx !== 'undefined' && wx.getSystemInfo && !isEmptyObj(wx)) { return {env: Env.wx, global: wx}; } // 百度小程序 if(typeof swan !== 'undefined' && swan.getSystemInfo && !isEmptyObj(swan)) { return {env: Env.swan, global: swan}; }} // 支付宝小程序 if(typeof my !== 'undefined' && my.getSystemInfo && !isEmptyObj(my)) { return {env: Env.my, global: my}; }} // 头条小程序 if(typeof tt !== 'undefined' && my.getSystemInfo && !isEmptyObj(tt)) { return {env: Env.tt, global: tt}; }} // 京东小程序 if(typeof jd !== 'undefined' && my.getSystemInfo && !isEmptyObj(jd)) { return {env: Env.jd, global: jd}; }} return {env: '', global: {}}; }
function hijackRequest() { const originRequest = global.request; const newRequest = (options: any) => { const originComplete = options.complate; const startTime = Date.now(); // 请求响应劫持 options.complete = (response: any) => { emitter.emit('onRequest',{options,response,startTime}); originComplete && originComplete(response); } return originRequest.call(global,options); } Object.defineProperty(global,'request',{ value: newRequest, configurable: true, enumerable: true, writable: true }) }
function hijackOnLoad() { const originPage = Page; Page = (options: any) => { const originPageOnLoad = options.onLoad; options.onLoad = function(options: Object) { emitter.emit('onLoad'); originPageOnLoad && originPageOnLoad.call(this,options); }; originPage(options); } } function hijackOnError() { const originApp = App; App = (options: any) => { const originOnErrror = options.onError; options.onError = function(error: any) { emitter.emit('onError',error); originOnErrror && originOnErrror.call(this,error); }; originApp(options); } }
通过用户ID -》 前端访问日志 -》 命令行工具 -》 页面复现 -》 接口请求 trace 通过下面链路排查who - when - where - what 查看业务状态码成功率的折线图 页面QPS查看页面访问成功,重复访问情况 近期有无新发布 找到对应请求日志,有无报错 筛选一分钟内日志 查看日志参数细节 分析结论 前端监控与告警 稳定情况 10分钟内页面访问QPS增幅大于100%,或降幅大于50%触发告警(针对公司上班人员,在工作时间内进行监控)
理解业务最快的办法是数据,解决问题的依据也是数据 借助日志和数据,主动争取话语权 选中现有的监控方案 需求:观察上线效果,分析线上问题,发现优化空间 功能组织: 页面访问,接口请求,JS报错,资源加载 其他考虑:费用,私有化部署
整体设计:数据采集,日志上报,日志查询(ElasticSearch + Kibana + Grafana) SDK的设计:使用方式的定义,日志上报的设计 监控项的采集原理(WEB/小程序):接口监控,JS报错,页面PV,资源加载
最佳实践:从整体到局部 who - when - where - what 排障案例分析:多看多用,在排查问题的过程中锻炼自己 从前端监控到告警规则:普适规则,因地制宜
1.为什么需要前端监控
当需求方案存疑时,结合数据给需求提出建议 当发生线上问题时,快速找到关键信息以定位问题 当想提升前端体验时,怎主动发现优化空间
2.基础需求
从业务理解来说,我们需要知道用户的使用情况,包括PV、UV、访问时段、访问时长等。 从问题排障来说,我们需要知道用户的使用历史,包括用户发出的接口请求、页面报错等。 从体验优化来说,我们需要知道真实的性能数据,包括页面加载和资源加载的耗时。
可选方案
1.Sentry 不实用 2.阿里云ARMS 太贵 3.岳鹰 不能私有化部署
自研方案
1.定位
前端工程师观察业务效果(页面访问PV,自定义上报)、分析线上问题(页面访问,接口请求,前端报错)、发现优化空间(页面性能,资源加载)的监控工具
2.模块设计
整体 数据采集 -日志上报 -日志查询
细化 收集 - 处理日志 - 存储 - UI界面
JS SDK - 上报日志 - 处理日志 - HTTP Server - 日志落库 - ElasticSearch - 查询日志 - Kibana
再细化 JS-SDK web/小程序(页面访问日志,资源加载日志,页面性能日志,接口请求日志,前端报错日志,自定义日志)- 上报日志 - HTTP Server - 日志落库 - ElasticSearch,时序数据库 - Kibana(看具体日志,下钻分析),Grafana(整体分析,曲线)
SDK配置
debug 调试模式,将收集到的日志数规打到console(可用于开发阶段的险证) silence 静音模式,会执行湿辑,但不会真正上报日志(可用于测试环境不上报日志) sendPv 是否上报页面PV sendApi 是否上报API接口请求 sendResource 是否上报资源请求 sendError 是否上报js error sendPerf 是否上报页面性能 spa 是否SPA单页应用页面 bizUserld 绑定业务中的用户ID bizUserType 绑定业务中的用户类型 apilgnoreList 需要忽略的api请求列表,要包含protocol协议头, path部分可填可不填['https://restapi.amap.com','https://miao.baidu.com/abdr'] apiBizCodeParser 解析HTTP响应中的业务状态码(非HTTP状态码)
请求方式
new Image().src 优点:简单,兼容性好,没有跨域问题 缺点:URL有长度限制,不利于日志聚合上报
POST 优点:请求的body参数没有长度限制,可以以日志聚合上报 缺点:需要跨域,且页面退出时POST请求发不出去
WebSocket 优点:只建立一次链接,后续日志上报的性能较好 缺点:对上报的服务器压力大,容易连接数溢出
最终使用POST:延迟上报 + 聚合上报 + 重试机制 + 退出兜底 sendBeacon
步骤 1.main 2.initSDK 3.network,error,resource,pageview,pert,custom 4.sender 先定义一些全局变量 const win = window; const siliceFn = [].slice.call;
接口监控
watchApi
JS报错监控
页面PV监控
监听hashchange + 劫持 history
资源加载监控
PerformaceEntry performace.getEntriesByType('resource') 通过initiatorType字段过滤资源类型 压缩体积 压缩变量名,提取公共变量 使用自执行函数的方式打包 life风格 如何支持多端小程序
小程序接口监控
小程序页面监控
最佳实践
通过用户ID -》 前端访问日志 -》 命令行工具 -》 页面复现 -》 接口请求 trace 通过下面链路排查who - when - where - what 查看业务状态码成功率的折线图 页面QPS查看页面访问成功,重复访问情况 近期有无新发布 找到对应请求日志,有无报错 筛选一分钟内日志 查看日志参数细节 分析结论 前端监控与告警 稳定情况 10分钟内页面访问QPS增幅大于100%,或降幅大于50%触发告警(针对公司上班人员,在工作时间内进行监控)
总结
为什么需要前端监控
理解业务最快的办法是数据,解决问题的依据也是数据 借助日志和数据,主动争取话语权 选中现有的监控方案 需求:观察上线效果,分析线上问题,发现优化空间 功能组织: 页面访问,接口请求,JS报错,资源加载 其他考虑:费用,私有化部署
前端监控自研
整体设计:数据采集,日志上报,日志查询(ElasticSearch + Kibana + Grafana) SDK的设计:使用方式的定义,日志上报的设计 监控项的采集原理(WEB/小程序):接口监控,JS报错,页面PV,资源加载
如何利用好前端监控
最佳实践:从整体到局部 who - when - where - what 排障案例分析:多看多用,在排查问题的过程中锻炼自己 从前端监控到告警规则:普适规则,因地制宜