背景先容
小程序,英文 mini program。是一种不须要下载安装即可在微信中利用的运用,用户扫描小程序码或搜索小程序即可打开,触手可及,用完即走,不用关心是否安装太多运用的问题。
根据阿拉丁的统计显示,截止到 2021 年 6 月尾,微信小程序数量超过430万,日生动用户超4.1亿。小程序已经深度影响200+细分行业,已有11大平台推出各自小程序生态,小程序成为真正意义上的中国人定义的“互联网新技能标准”。

因业务发展的须要,我们在多个的小程序平台都有对应的投放,如微信小程序、百度智能小程序、支付宝小程序、字节小程序、360小程序等。在开拓迭代过程中,由于平台、系统、机型和版本的兼容等问题,小程序涌现线上问题时排查起来相对困难;
思考:为什么小程序启动的那么慢?线上白屏加载不出来数据到底是怎么回事?线上代码质量如何,是否有bug?线上涌现问题,如何快速创造并办理?用户到底操作了什么?投放了这么多平台小程序,各平台效果究竟怎么样?当前小程序是否有可优化空间?
实在上面这些问题都可以通过日志文件剖析得到解答,各个小程序后台也有采集一部分日志信息,比如微信后台有采集js非常日志、接口非常日志,但接口非常日志只有状态码层面的信息,脚本非常日志中缺少当前非常发生时的页面路径信息、系统信息、网络状态、用户行为轨迹等信息的记录,因此排查起来还是相对困难的。对付小程序性能数据的采集,各平台之间也没有一个统一的标准。 以是我们须要一种兼容多平台的小程序日志采集方案。
02
采集思路
采集小程序的信息,首先须要先理解下小程序的根本架构设计。小程序本色便是 hybrid,但是是受限的。小程序的渲染层和逻辑层分别由两个线程管理:
渲染层的界面利用 WebView 进行渲染;逻辑层采取 JSCore 或者 V8 等JS引擎 来运行 JavaScript 代码。这两个线程间的通信经由小程序 Native 侧中转,逻辑层发送网络要求也经由 Native 侧转发。对付平台方而言,这种设计极大增加了平台对运用的掌握,也减少了各种风险。
采集SDK当然也是在逻辑层中,因此能监听到逻辑层的交互相应。主流小程序JS逻辑层的开拓紧张依赖以下三个部分:
App:每个小程序都须要在 app.js 中调用 App 方法注册小程序实例,绑定生命周期回调函数、缺点监听和页面不存在监听函数等。全体小程序只有一个 App 实例,是全部页面共享的。Page:对付小程序中的每个页面,都须要在页面对应的 js 文件中进行注册,指定页面的初始数据、生命周期回调、事宜处理函数等。大略的页面可以利用 Page() 进行布局。wx:小程序开拓框架供应丰富的微信原生 API,可以方便的调起微信供应的能力,如获取用户信息,本地存储,支付功能等。以是想要有效的监控小程序的运行状态,就须要对这三个模块进行有效拦截处理。在小程序中App、Page、wx模块宏都是暴露在全局的,如果要进行拦截,直接重写全局中的变量即可。
03
整体方案
SDK的利用方涵盖小程序原生开拓、利用小程序框架开拓两种:
原生开拓我们供应单文件的利用形式;对付利用框架开拓的,我们支持通过npm包形式利用。SDK紧张有两大功能:数据采集和上报做事。SDK 的紧张浸染是网络小程序产生的日志。采集日志可以分为 3 类,非常日志、正平日记、性能日志,每越日记上报时还会上报 1 类通用的信息:
日志类型
二级分类
解释
非常日志
JS缺点、接口非常、资源下载非常
监控关键,需实时上报
正平日记
用户行为、log信息、路由信息
日志量大,由用户主动上报
性能日志
首屏韶光、页面渲染耗时、PV/UV
上报一次,同时统计PV/UV
通用信息
系统信息、用户信息、网络状态、场景值
其他 3 类日志附加的通用信息
我们的目标小程序涵盖了当前主流小程序,如微信、百度、头条、支付宝、360等。
公共日志采集
常规思路通过干系的API获取环境信息,紧张包括以下几类信息:
wx.getUserInfo() 获取用户信息获取(须要授权,SDK考试测验获取,没有的话为空)wx.getSystemInfoSync() 获取系统信息(获取一次后保存到内存中)wx.getNetworkType() 获取当前网络状态获取(每次获取)App.onLaunch() 获取场景信息获取(获取一次后保存到内存中)多端兼容思路根本信息的API各平台小程序实现接口基本同等。紧张差异在于模块宏的定义不同,比如微信小程序是wx,百度小程序是swan,头条小程序是tt。在SDK初始化的时候,通过判断模块宏是否存在确定当前所处的小程序环境并存储在高下文变量中,详细利用的时候通过高下文调用API。
public get context() { if (this._context) return this._context; if (typeof wx !== 'undefined') { this._context = wx; } if (typeof swan !== 'undefined') { this._context = swan; } if (typeof tt !== 'undefined') { this._context = tt; } if (typeof my !== 'undefined') { this._context = my; } if (typeof qq !== 'undefined') { this._context = qq; } if (typeof qh !== 'undefined') { this._context = qh; } return this._context;}// 利用示例 用户信息获取:ctx.getUserInfo()
当前页面路径获取多端兼容处理主流小程序:在当前页面路径信息获取上,紧张通过 getCurrentPage() 获取,但这个API须要在小程序加载完成后才可调用,但可通过App.onLaunch的回调中获取到首页路径信息;360小程序比较分外,须要通过 $router.history 获取。
以是这里供应了一个获取当前页面路径的方法,在方法内部磨平了各平台小程序的差异。
public get currentPage() { if (this.appName === 'qh') { // 360小程序兼容 const { path } = $router.history.current; return path; } let pages = getCurrentPages(); if (pages.length > 0) { return pages[pages.length - 1].route; } else { return this._indexPage; }}
非常日志采集
非常日志分为三种:脚本非常、接口非常、资源非常
由于小程序的分外通信机制,接口要求、资源下载都是通过小程序载体Native转发实行的。因此要采集这部分的非常,可以通过拦截 wx.request、wx.download 等干系API实现。
关于接口非常信息的上报,紧张涉及两个机遇:在 wx.request 的success成功回调中,如果相应的状态码 >= 400,解释网络要求发送出去了,但业务相应非正常状态,作为一次非常上报;如果直接走到 wx.request 的 fail 失落败回调,解释网络要求非常了也进行一次非常上报;
资源非常紧张通过拦截 downloadFile 文件下载API,不才载失落败的时候上报资源非常日志。
下面我们重点先容下关于脚本非常的日志采集。
脚本非常_常规思路JS常见的缺点类型有以下几种,个中类型缺点、引用缺点、未捕获的Promise缺点发生频次较高;其次是越界缺点和URI禁绝确缺点。
个中前5中缺点可以通过监听小程序App.onError采集到,Promise非常可通过App.onUnhandledRejection API采集,但这个API各平台的支持度是不一致的,详细如下:
App.onError可以采集到小程序中发生的脚本缺点、API调用报错;App.onUnhandleRejection各平台支持度不一致,微信在根本库2.10以上才支持,QQ部分支持,百度不支持;App.onPageNotFoung在页面路由不存在时会触发;App.onMemoryWarning在内存不敷时触发告警;脚本非常_多端兼容思路通过对非常堆栈的跟踪,查看源码可以看到微信通过try...catch我们的业务代码外层进行了包裹,涌现非常的时候通过 console.error 进行了打印输出,因此在开拓过程中,可以看到当涌现非常的时候,开拓者工具掌握台会有非常信息的打印。
因此我们可以拦截 console.error,这种拦截可采集到的非常包括下面几种:
脚本非常API调用非常Promise非常自定义上报实现过程中碰着的问题在详细的实现过程中,我们创造不同平台的详细实现是不一样的:
微信、头条小程序拦截到非常后,在Error工具上增加了一些包装信息后调用console.error;百度小程序将堆栈信息都转换成字符串后再调用console.error;因此我们在拦截console.error后须要对拦截到的信息二次处理,磨平平台差异。
详细方案便是通过正则匹配:
const ERROR_TYPES_REG = /(((Eval|Reference|Range|Internal|Type|Syntax)Error)|promise)
确定当前脚本非常类型,格式化信息后将堆栈信息和缺点内容分开上报,终极上报的脚本日志格式如下所示,包含缺点类型、缺点内容、堆栈信息。
"exceptions": [{ "errType": "MiniProgramError", "content": "app.checkAuthorize1 is not a function", "message": "TypeError: app.checkAuthorize1 is not a function", "stacktrace": "MiniProgramErroranonymous> (httpe.p.__callPageLifeTime__ (h"}]
性能日志采集
常规思路
通过官方供应API获取数据并采集上报,微信供应 wx.getPerformance() API,可以获取到小程序启动耗时、页面首次渲染耗时、注入脚本耗时;但其他平台小程序没有供应干系的接口。
多端兼容思路用户点击小程序后首先会去下载小程序资源包,启动后会先解析app.json文件,注册App(),然后实行APP的生命周期;然后会开始加载页面,解析页面json文件,渲染.wxml文件,实行逻辑层js文件并调用页面生命周期。APP和页面的生命周期函数是性能日志采集的关键点。
生命周期钩子
级别
解释
主流小程序框架
360小程序框架
onLaunch
App级
监听小程序初始化
支持
onLoad
onShow
App级
监听小程序启动或切前台
支持
支持
onLoad
Page级
监听页面加载
支持
支持
onShow
Page级
监听页面显示
支持
支持
onReady
Page级
监听页面初次渲染完成
支持
支持
onHide
Page级
监听页面隐蔽
支持
支持
我们对各平台小程序生命周期干系API进行比拟,创造主流小程序框架的实现基本同等,360小程序比较分外,它是基于Vue框架进行的二次封装,但大部分API还是同等的,只是在360小程序中,小程序初始化完成的钩子叫onLoad,须要做分外处理。
因此性能日志采集可通过拦截小程序App、Page的相应生命周期并打点实现。性能日志的采集方案上,我们紧张划分为运用级别性能和页面级别性能。
运用级别的性能:我们定义了首屏韶光,为进入小程序后首个页面的渲染完成韶光 - SDK的初始化韶光。
由于小程序入口比较多,有从分享卡片进入的、有点击图标进入的、有搜索进入的,不同办法进入小程序展示的首页会不一样,因此在每条日志我们都会携带当前页面入口路径。
页面级别性能紧张包含:
页面加载耗时:页面加载耗时有两种情形,首页页面加载耗时 = 页面加载完成韶光Page.onLoad - 小程序启动完成韶光App.onShow;其他页面加载耗时时间 = 当前页面的加载完成韶光Page.onLoad - 上一个页面的关闭韶光PrePage.onHide;页面显示耗时:当前页面的显示完成韶光Page.onShow - 当前页面的加载完成韶光Page.onLoad页面渲染耗时:当前页面渲染完成韶光Page.onReady - 当前页面显示韶光Page.onShow实现过程中碰着的问题在测试验证阶段,我们创造利用Taro框架开拓转换的微信小程序无法采集到页面级别的性能数据,详细定位后创造Taro2版本中将小程序页面和组件统一合并为Component组件处理,因此须要对Component进行拦截处理;Taro3之后对外暴露了小程序页面规范工具,可以通过拦截Page实现。详细实现逻辑如下:private interceptPage = (): void => { let self = this; let isTaro = (typeof (process) !== 'undefined' && typeof (process.env) !== 'undefined' && typeof (process.env.TARO_ENV) !== 'undefined') ? true : false; const primaryPage = Page; if (isTaro) { let primaryComponent = Component; Component = (obj: any) => { PAGE_LIFE_CYCLE.forEach(name => { if (typeof obj.methods[name] === 'function') { const primaryHookFn = obj.methods[name]; obj.methods[name] = function (info: any) { return self.rewritePageLifeCycle(name, this, primaryHookFn, info); } } }); primaryComponent && primaryComponent.call(this, obj); } } Page = (obj: any) => { PAGE_LIFE_CYCLE.forEach(name => { const primaryHookFn = obj[name]; obj[name] = function (info: any) { return self.rewritePageLifeCycle(name, this, primaryHookFn, info); } }); primaryPage && primaryPage.call(this, obj); }}
行为轨迹采集
一些较暗藏的缺点如果只有缺点栈信息,排查起来会比较难,如果有用户操作的路径,在排查时就方便多了。行为轨迹日志的采集上,我们会采集APP函数堆栈、Page函数堆栈、HTTP要求堆栈,在非常发生的时候上报最近10条行为轨迹日志赞助用户定位问题。
04
SDK特性
多端、多框架适配
轻量且支持多种模块化规范
SDK采取Rollup打包,压缩后体积 49KB,支持cmj、es6模块化规范。
多种利用形式1. 通过npm形式利用
import as mpMonitor from 'mp-monitor';mpMonitor.init({ projectId: '', // 项目标识 url: ''});
2. 通过单文件形式利用
const mpMonitor = require('./utils/mp-monitor');mpMonitor.init({ projectId: '', // 项目标识 url: ''});
支持自定义上报日志
业务方可通过 console.error(\'自定义上报内容\')
05
业务实践
1. SDK目前已完成集团北斗前端监控平台对接,支持微信、百度、头条、支付宝、QQ、360等多端监控;
2. 截止2021.10.15,已接入小程序13个,覆盖5个业务线
3. SDK上报模型和Sentry保持同等,自行搭建Sentry做事的业务方可直策应用SDK,配置上报url即可;
4. 平台接入效果展示
开拓者可通过首页图表不雅观察到当前小程序运行状态
通过性能图表上可以考虑当前小程序是否有优化空间,如果开启分包加载优化,当前首屏性能曲线图该当会有明显的低落
通过页面url统计到的pvuv数据不雅观察,在优化业务代码时可考虑将pv为0的页面做移除处理
页面加载瀑布图,可直不雅观的看到小程序页面的一个加载过程
行为轨迹展示,可以帮助开拓者更快的复现线上问题,提升问题办理效率
06
总 结
当前小程序日志采集SDK在内部抹平了多平台的差异,统一的小程序性能数据采集指标。上报非常发生时的页面路径信息、系统信息、网络状态、用户行为轨迹等干系信息,帮助开拓者更快速的定位并办理线上问题。
我们会连续优化细节,完善功能,也欢迎感兴趣的同学来一起互换。
如果你对源码感兴趣,请点赞+转发+关注+私信【小程序日志采集】。
欢迎点赞+转发+关注!
大家的支持是我分享最大的动力!
!
!