proxy-monitor

1.12.5 • Public • Published

背景

作为程序猿,想必大家有过这样的经验。有报错信息,能必现的问题,调试修改起来一般都比较顺利。而那种没有报错的问题,难以发现、难以调试。很可能查了大半天,最后发现只需要改几行代码。付出与收益不成比。

举个例子:

某个小程序上调用某个 API,开发测试时候可以的,但后面因为一些原因突然没达到预期的效果。排查时候发现这个 API 因为一些原因走到了 fail 回调,但代码里只写了 success 回调,并没有写 fail 回调。

在实际开发中一般不会对每个 API 调用和其回调都打日志,多个 API,我们很难知道其调用过程,冷不丁就会出现意料外的情况而不知。

举个例子:

小程序上调用某个广告 API,但发现点广告关闭按钮,onClose 的回调一直没被调用。排查时候发现开发者在 page 的 onHide 回调里调用了 offClose,而进入广告会触发 page 的 onHide。

如果在小程序开发中能收集 API 调用与 API 回调的信息,对这些异常情况进行判断,在需要的时候能知道哪些 API 被执行,以及相互间的关系,那么在出现这类问题时能省掉大量排查时间。

快速开始

安装

在项目中 npm install --save proxy-monitor

在小程序中会用到 npm,参考文档

使用

以微信小程序为例 在小程序的 app.js 中

const ProxyMonitor = require('proxy-monitor').default;
const monitor = new ProxyMonitor(wx, {
  apiPrefix: 'wx',
  report(data) {
    console.log('report data', data);
  }
});
wx = monitor.proxy; //修改全局变量

调用某个 api,没有传 fail 回调,却应该走到 fail 回调,如:

wx.downloadFile({
  url: 'test',
  success() {
    console.log('download file success');
  }
});

那么能收到打印信息:

{
  "apiId": "2",
  "apiName": "wx.downloadFile",
  "property": "downloadFile",
  "traceId": "2",
  "traceApiName": "wx.downloadFile",
  "args": [{ "url": "test", "success": "function" }],
  "ret": "ok",
  "isAsync": null,
  "time": 1589512456512
}
 
{
  "apiId": "3",
  "apiName": "wx.downloadFile:args[0].fail:autoadd:callback",
  "property": "fail",
  "pid": "2",
  "parentApiName": "wx.downloadFile",
  "traceId": "2",
  "traceApiName": "wx.downloadFile",
  "args": [
    { "errMsg": "downloadFile:fail createDownloadTask:fail invalid url" }
  ],
  "ret": "fail",
  "isAsync": false,
  "time": 1589512456521
}

建议一般情况只打印、上报 ret 不为 ok 的日志,要了解多个 API 及回调的调用顺序与依赖关系再打印更多信息。

原理说明

ProxydefineProperty可以监听对象属性的setget,然后对函数进行包装,可以收集到参数、结果等信息。

ProxydefineProperty不同点是:

Proxy可以在对象的属性get时进行处理,可以理解为懒处理。对于api调用缺少fail回调的情况,我们可以补一个空函数。如:

{
  "apiId": "4",
  "apiName": "wx.showToast:args[0].fail:autoadd:callback",
  "property": "fail",
  "pid": "3",
  "parentApiName": "wx.showToast",
  "traceId": "3",
  "traceApiName": "wx.showToast",
  "args": [
    {
      "errMsg": "showToast:fail parameter error: parameter.title should be String instead of Number;"
    }
  ],
  "ret": "fail",
  "isAsync": false,
  "time": 1589532839302
}

这样能发现api失败而漏传fail回调的情况。

defineProperty需要事先对对象属性进行改写监听,如果对每个缺少failapi补充fail,那么可能会造成内存溢出的问题。

defineProperty性能会比Proxy好些,但不是高频调用,可忽略不计。

默认用Proxy进行处理,对不支持Proxy或用参数控制可以用defineProperty处理。

api的结果进行递归处理,可以得到多层api调用链,如:wx.getFileSystemManager.saveFile

综合处理api和其回调,可以分析判断一些异常情况,参考下面详细介绍。

详细介绍

ProxyMonitor 类

new ProxyMonitor(target[,options])

target

监听目标对象

options

选项

参数 类型
shouldHandleMethod function
shouldHandleResult function
shouldReportPropertySet function
report function
getApiId function
hookSetTimeout function
hookSetInterval function
shouldHookConsoleError function
apiCbkTimeout number
eventCbkTimeout number
dealType string

shouldHandleMethod(target,rootProperty,property):boolean

判断方法调用是否监听,缩小监听范围,避免大量无用信息

参数 类型 说明
target object 目标对,如 FileSystemManager 对象
rootProperty string 调用链,如 wx.getFileSystemManager.saveFile
property string 方法名,如 FileSystemManager下的saveFile

shouldHandleResult(target,rootProperty,property):boolean

判断是否对方法结果进行监听,如果该结果下的 api 属于高频调用,监听意义又不大,可以不做监听,如Canvas.getContext

参数 类型 说明
target object 目标对象
rootProperty string 调用链
property string 方法名

shouldReportPropertySet(target,rootProperty,property):boolean

判断是否监听对象的 set 操作,如多媒体对象的src的 set 操作可以监听,但其他的可以不做监听

参数 类型 说明
target object 目标对象
rootProperty string 调用链
property string 属性名

report(reportData)

接收处理 api 调用产生的数据

reportData对象

属性 类型 说明
apiId string id
apiName string 调用链
property string 属性名
pid string 父 id,如saveFiledownloadFile的回调中运行,那么其父 id 就是 downloadFile回调对应的 id
parentApiName string 父调用链
traceId string 链路 id,既根api的 id
traceApiName string api
args array 方法参数
res array 方法结果
ret string 运行标识,一般情况下可以打印、上报不为ok的数据,需要分析排查问题时上报更多内容
time number 执行时间
isAsync boolean api的回调是否是异步执行

例子:

{
 "apiId": "7",
 "apiName": "wx.getFileSystemManager.saveFile:args[0].fail:callback",
 "property": "fail",
 "pid": "5",
 "parentApiName": "wx.getFileSystemManager.saveFile",
 "traceId": "3",
 "traceApiName": "wx.downloadFile",
 "args": [
   { "errMsg": "saveFile:fail permission denied, open \"./test.txt\"" }
 ],
 "ret": "fail",
 "isAsync": true,
 "time": 1589524829196
}

对应代码是:

wx.downloadFile({
     url: 'http://down.qq.com/qzone/c.txt',
     success(res) {
       console.log('download success', res)
       fileSystemManager.saveFile({
         tempFilePath: res.tempFilePath,
         filePath: './test.txt',
         success(res) {
           console.log('save success')
         },
         fail(res) {
           console.log('save fail', res)
         }
       })
     },
     fail(res) {
       console.log('download fail', res)
     }
   })

parantApiNametraceApiName能关联离散的日志,帮助理解。借助zipkin这样的全链路日志系统,可以可视化观察 API 调用上下文,帮助开发者分析日志,排查问题。

ret的说明
名称 说明
ok 正常
fail fail 回调执行
onError 事件监听onErroronSocketErroronUpdateFailed的回调执行
exception api或回调执行出错。开发者可能会catch住错误但不打印,导致出问题而不知
fail fail回调执行
timeout successfail回调超时
timeout_maybe 事件监听的回调超时
errorLog 构造参数shouldHookConsoleErrortrueconsole.error的日志
cbkMultiWarn successfail被多次调用的警告(一般是平台侧 api 实现的问题)
cbkOrderWarn completesuccessfail调用前被调用(一般是平台侧 api 实现的问题)
cbkAsyncWarn 可能你期望apisuccessfail回调是异步的,但平台侧实现上是同步的,导致你的代码在时序上出现问题,这个会帮到你

getApiId():string

id 生成器

如果上报,需要生成唯一 id

hookSetTimeout(setTimeout)

包装setTimeout

因为setTimeout是异步的,setTimeout外的api调用和setTimeout回调里的api调用会失去关联关系。对setTimeout进行处理可以将上下关联起来。

一般情况下不用传

用法示例:

//app.js
const ProxyMonitor = require('proxy-monitor').default
const monitor = new ProxyMonitor(wx, {
  apiPrefix: 'wx',
  report(data) {
    console.log('report data', JSON.stringify(data))
  },
  hookSetTimeout: setTimeout
});
wx = monitor.proxy //修改全局变量
App({
  onLaunch: function () {
    let fileSystemManager = wx.getFileSystemManager();
    wx.downloadFile({
      url: 'http://down.qq.com/qzone/c.txt',
      success(res) {
        console.log('download success', res)

        monitor.setTimeout(function () {
          fileSystemManager.saveFile({
            tempFilePath: res.tempFilePath,
            filePath: './test.txt',
            success(res) {
              console.log('save success')
            },
            fail(res) {
              console.log('save fail', res)
            }
          })
        })
      },
      fail(res) {
        console.log('download fail', res)
      }
    })
  }
})

上报数据示例:

{
  "apiId": "8",
  "apiName": "wx.getFileSystemManager.saveFile",
  "property": "saveFile",
  "pid": "7",
  "parentApiName": "wx.setTimeout:callback",
  "traceId": "3",
  "traceApiName": "wx.downloadFile",
  "args": [
    {
      "tempFilePath": "http://tmp/wx69f996eabbd34128.o6zAJs-_DIy1PGG3-yf5SNVll3yY.bpVYCxdHxs3P5d793fc5b00a2348c3fb9ab59e5ca98a.txt",
      "filePath": "./test.txt",
      "success": "function",
      "fail": "function"
    }
  ],
  "ret": "ok",
  "isAsync": null,
  "time": 1589530097006
}

hookSetInterval(setInterval)

同上

shouldHookConsoleError:boolean

是否监听console.error,没有其他错误处理上报,可以设为 true

上报数据示例:

{
  "apiId": "7",
  "apiName": "wx.ConsoleError",
  "pid": "6",
  "parentApiName": "wx.getFileSystemManager.saveFile:args[0].fail:callback",
  "traceId": "2",
  "traceApiName": "wx.downloadFile",
  "args": ["save file error"],
  "ret": "errorLog",
  "isAsync": null,
  "time": 1589531227058
}

shouldHookConsoleError():boolean

是否监听console.error

apiCbkTimeout:number

判断successfail回调超时时间,默认 3 秒

eventCbkTimeout:number

事件监听的回调超时时间,默认 30 秒

dealType:string

监听处理类型,ProxydefineProperty。默认Proxy

Package Sidebar

Install

npm i proxy-monitor

Weekly Downloads

0

Version

1.12.5

License

MIT

Unpacked Size

550 kB

Total Files

46

Last publish

Collaborators

  • folger_fan