前言
我们在开发小程序的时候,明明在本地编辑器上好好的,但是总会有那么坑人的BUG在线上出错。 怎么搞,让我一个一个去看代码好烦啊,这个时候日志的重要性就出来了,它可以帮助我们快速定位线上BUG出现的问题,及时改进。
本文通过微信自带的日志方法二次封装
实现
1、基础
封装一个简单的日志对象
- info、warn、error是上报日志的等级
- setFilterMsg、addFilterMsg可以添加过滤条件,方便后续过滤日志
- BaseInfo可以收集基础的信息,当前页面,页面栈等,可以根据自己的业务添加信息
- Response可以收集网络信息,添加到请求和响应拦截器里。本文的网络是基于axios封装的,可根据自己的需求修改
const log = wx.getRealtimeLogManager ? wx.getRealtimeLogManager() : null
const mylog = {
info() {
if (!log) return
log.info.apply(log, arguments)
},
warn() {
if (!log) return
log.warn.apply(log, arguments)
},
error() {
if (!log) return
log.error.apply(log, arguments)
},
setFilterMsg(msg) {
if (!log || !log.setFilterMsg) return
if (typeof msg !== 'string') return
log.setFilterMsg(msg)
},
addFilterMsg(msg) {
if (!log || !log.addFilterMsg) return
if (typeof msg !== 'string') return
log.addFilterMsg(msg)
},
/**
* @description:
* @param {*} mode 用于区分是是哪里收集的信息
* @return {*}
*/
BaseInfo(mode='') { // 收集基础信息
let pages = getCurrentPages() ? getCurrentPages() : ''
let pageStack = {}
if (pages.length >= 1) {
pages.forEach((item, index) => {
pageStack[index] = item.route
})
}
let currentPage = pages.length ? pages[pages.length - 1] : ''
let path = currentPage.route ? currentPage.route : ''
const BaseInfo = { // 基础信息
Path: path ? path : '',
pageStack,
mode
}
return BaseInfo
},
Response(data) { // 收集网路信息
return {
Header: data.config ? data.config.headers : '',
ResponseData: data.data ? data.data : '',
XTraceId: data.headers['X-Trace-Id'] ? data.headers['X-Trace-Id'] : '',
status: data.status ? data.status : '',
Url: data.request ? data.request.url : '',
requestData: data.request ? data.request.data : '',
requestMethod: data.request ? data.request.method : ''
}
},
/**
*
* @param {*} data.data 待捕获对象
* @param {*} data.conditions 捕获条件
* @param {*} mode 捕获模式,true:存在即调用回调,反之则不存在调用
* @param {*} fun 回调
*/
catchError(data={data:null,conditions: null}, fun = ()=>{}, mode=true) { // 错误捕获器
if(mode ? (data.data.indexOf(data.conditions) >= 0) : (data.data.indexOf(data.conditions) < 0)){
fun()
}
}
}
2、重写Page方法
重写Page方法可以使得我们不必每个页面都添加一次日志。 并且可以知道用户当前页面的层级(栈),页面停留时间等,定位BUG的时候模拟用户行为等
import mylog from './mylog' // 这个是1中上文所封装的日志方法,通过export default暴露出去
const resetPage = (_data) => {
data = _data
let oldPage = Page
// 写function,怕有的基础库不兼容
Page = function (obj) {
let oldOnShow = obj.onShow
let oldOnHide = obj.onHide
let oldOnLoad = obj.onLoad
let oldOnUnload = obj.onUnload
obj.timer = null // unload的定时器
obj.timer2 = null // onhide的定时器
obj.timer3 = null // onshow的定时器
obj.pageTime = 0 // unload的计数器
obj.pageTime2 = 0 // onhide的计数器
obj.pageTime3 = 0 // onshow的计数器
// 不可以直接写oldOnShow()或者oldOnHide(),否则会报错没有this。这里的this在Page构造函数实例化的时候才会指定
obj.onShow = function () {
const BaseInfo = mylog.BaseInfo('onShow')
mylog.setFilterMsg('onShow')
mylog.info(({BaseInfo,
loadTime: {
message: '该时间是最后一个pageStack停留的时间',
mode: 'onshow',
time: this.pageTime
}
}))
oldOnShow.call(this)
}
obj.onHide = function () {
const BaseInfo = mylog.BaseInfo('onHide')
mylog.setFilterMsg(BaseInfo.userinfo.userName)
mylog.addFilterMsg('onHide')
mylog.info({BaseInfo,
loadTime: {
message: '该时间是最后一个pageStack停留的时间',
mode: 'onHide',
time: this.pageTime
}
})
oldOnHide.call(this)
}
obj.onLoad = function (options) {
const BaseInfo = mylog.BaseInfo('onLoad')
mylog.setFilterMsg('onLoad')
mylog.info({BaseInfo, options})
// 记录用户行为,页面停留时间
obj.timer = setInterval(()=>{
this.pageTime += 1
}, 1000)
obj.timer2 = setInterval(()=>{
this.pageTime2 += 1
}, 1000)
obj.timer3 = setInterval(()=>{
this.pageTime3 += 1
}, 1000)
oldOnLoad.call(this, options)
}
obj.onUnload = function() {
clearInterval(this.timer)
clearInterval(this.timer2)
clearInterval(this.timer3)
const BaseInfo = mylog.BaseInfo('loadTime')
mylog.setFilterMsg('loadTime')
mylog.info({BaseInfo,
loadTime: {
message: '该时间是最后一个pageStack停留的时间',
mode: 'onUnload',
time: this.pageTime
}
})
this.pageTime = 0
this.pageTime2 = 0
this.pageTime3 = 0
oldOnUnload.call(this)
}
return oldPage(obj)
}
}
3、日志的使用
3.1、应用重写的Page
app.js中
import reSetPage from './reSetPage' // 这个就是上文2中所重写的Page。通过export default暴露出去
import mylog from './mylog' // 这个是1中上文所封装的日志方法,通过export default暴露出去
App({
data: {},
onLaunch() {
resetPage(this.data)
},
onShow() {},
onError(error) {
const BaseInfo = mylog.BaseInfo('weChatError')
mylog.addFilterMsg('wechatError')
mylog.error({ BaseInfo, errorMsg: error })
},
globalData: {}
})
3.2、在网络请求中,加入日志
在请求拦截器和响应拦截器中加入日志
本文只在响应拦截器中添加,如请求拦截器中有需要,可自行添加
这边以axios为例
import axios from 'axios'
import mylog from '../log' // 这个是1中上文所封装的日志方法,通过export default暴露出去
...... // 这边是你自己封装的网络请求类或者方法,本文不做解释
// 注:axios要在小程序中使用,需要使用axios-miniprogram-adapter,有需要可自行搜索使用方法
// 响应拦截器
instance.interceptors.response.use(
response => {
const BaseInfo = mylog.BaseInfo('netWrokSuccess')
const successResponse = mylog.Response(response)
if (JSON.stringify(response.data).length <= 700) { // 微信后台限制日志长度
mylog.setFilterMsg('networkSuccess')
mylog.info({ successResponse, BaseInfo })
} else {
mylog.setFilterMsg('networkSuccess')
delete successResponse.ResponseData
successResponse.ResponseData = JSON.stringify(response.data).slice(0, 700)
mylog.info({ successResponse, BaseInfo })
}
//否则的话抛出错误
if (response.status === 200) {
return Promise.resolve(response)
} else {
return Promise.reject(response)
}
},
error => {
if (error.response) {
const BaseInfo = mylog.BaseInfo('netWorkError')
const errResponse = mylog.Response(error.response)
mylog.setFilterMsg('networkError')
mylog.addFilterMsg(error.response.status)
mylog.error({ errResponse, BaseInfo })
}
return Promise.reject(error)
}
)
4、可能出现的问题
- 使用重写Page的方法添加日志的话,会在不同的页面报错。报错内容大致就是onHide、onShow等生命周期无法cell。这是因为你报错的页面没有实例化这个生命周期。需要你手动在这个页面添加这个生命周期即可,不需要向里面写什么东西
- 查看日志的时候,腾讯的日志并没有帮你格式化,看起来很不方便。写了个油猴的插件,这个是基础的格式化,可以根据自己的业务进行二次修改
5、查看日志
可以在小程序的后台-开发-开发管理里查看