npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

proxy-monitor

v1.12.5

Published

利用defineProperty或ES6 Proxy监听SDK(主要为小程序)调用情况

Downloads

78

Readme

背景

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

举个例子:

某个小程序上调用某个 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