# 从“手机抓不到日志”到“一套远程调试闭环”:我为什么写了 vconsole-relay-plugin

20 阅读7分钟

从“手机抓不到日志”到“一套远程调试闭环”:我为什么写了 vconsole-relay-plugin

平时做 H5,你一定遇到过这种场景:

  • 用户说“页面卡住了/白屏了/按钮点了没反应”,但你在本地怎么都复现不出来
  • 线上环境、灰度环境、客户内网……你连 Chrome DevTools 都不一定能连上
  • 真机调试不是不行,但每次都要插线、配代理、开远程调试,成本很高
  • 最要命的是:你缺的往往不是“更强的调试工具”,而是“能把信息稳定带回来的通道”

我就是在这些“抓不到日志”的时刻,开始动手写这个插件的。

这篇文章就从开发者视角聊聊:

  1. 我们到底缺了什么
  2. 为什么选择基于 vConsole 扩展
  3. 这个插件在项目里怎么跑起来
  4. 设计上踩过哪些坑、怎么取舍

希望能给你一个可复用的思路:把调试变成“可交付的能力”,而不是“临时的技巧”。


1. 背景:H5 的“信息回传”一直是痛点

移动端 H5 的问题通常不是“不会修”,而是“看不到”:

  • 看不到控制台:线上不可能让用户帮你截图 Console
  • 看不到网络细节:请求发没发、耗时多少、返回什么,在真机上很难完整拿到
  • 看不到存储状态:localStorage、cookie、sessionStorage 经常是排查关键

理想的状态是:我在 PC 上开一个页面,把手机端发生的事情实时看到,就像在本地开 DevTools 一样。

于是就有了这个项目的目标:H5 Debug Relay —— 面向 H5 场景的远程调试方案。

它的基本思路在项目里写得很清楚:

  • H5 侧接入 vConsole Relay 插件,把 Log / Network / Storage 等数据通过中转服务转发到 PC 端 Viewer 统一查看
    (见项目根目录 README:github.com/fwlee0625-p…

为了让你先有个直观感受,这里先放几张效果图(先用 GitHub Raw 链接占位;你发布到掘金后也可以换成自己的图床链接):

效果截图

概览(Overview)

控制台(Console)

网络(Network)

应用(Storage Snapshot)


2. 为什么选 vConsole:因为它“足够工程化”

很多人对 vConsole 的第一印象是“线上临时开个控制台”,但它其实有两个对工程非常友好的点:

  1. 它已经把常见信息源收集好了(Console、Network、Storage……)
  2. 它支持插件机制:可以加一个新 Tab、绑定 UI、接入自己的逻辑

这意味着我们不需要从零开始“接管 console.log / XHR / fetch”,而是站在 vConsole 的肩膀上,把“信息回传”做成一套稳定方案。

不过这里也得先把话说清楚:vConsole 本身是带 UI 的(页面上会有调试入口/悬浮按钮),对绝大多数业务来说,这种形态不可能“长期在线上常驻”。所以我对它的定位一直是:

  • vConsole 负责“采集与标准化输出”(日志、网络、存储等)
  • 我们的 relay 方案负责“把这些信息桥接到 PC”,把移动端操作不方便、页面不直观的问题一次性解决掉
  • 真正落地时通常会做 开关控制(只在 dev/test/灰度开启,或通过特定参数/鉴权开启),避免对正常用户造成干扰

所以 vconsole-relay-plugin 的定位也很明确:

  • 它不是替代 vConsole
  • 它是 vConsole 的一个“远程转发插件”,专门解决“把信息带回 PC”这件事

3. 目标:做一个“闭环”的远程调试体验

我的目标不是“能发日志就行”,而是尽量接近真实调试流程:

  • PC 端先开 Viewer,拿到一个会话 ID(session)
  • H5 端在 vConsole 里选择相同会话,建立连接
  • 一旦连接成功,就开始把 Log / Network 实时推送到服务端,再转发到 PC
  • PC 不仅能看实时数据,还能拉取一些“快照”(比如存储/ cookie),便于排查环境问题

所以项目整体分三块:

  • H5 端插件:负责配置服务地址、选择会话、连接后推送数据
  • 中转服务:WebSocket 转发 + 内存存储(按设备保留最新 1000 条)
  • PC 端 Viewer:按 vConsole 的数据结构做兼容展示(控制台 / 网络 / 应用 / 概览)

这一套闭环让“远程调试”变成一个可用的日常工具,而不是临时救火。


4. 插件做了什么:一个“设置”Tab + 两条数据管道

4.1 一个设置 Tab,让连接这件事“可视化”

插件的主入口是 createSettingsPlugin(vConsole)

import VConsole from "vconsole";
import { createSettingsPlugin } from "vconsole-relay-plugin";

const vConsole = new VConsole();
createSettingsPlugin(vConsole);

它会新增一个「设置」Tab,负责:

  • 输入服务器 Host / Port(默认 ws://localhost:4500/ws
  • 订阅在线会话列表(服务端推 sessions)
  • 选择 session 并连接(连接后自动开始推送)
  • 管理 deviceId / deviceName(便于 PC 端识别设备)

这块核心代码在 settings.js:github.com/fwlee0625-p…

4.2 两条数据管道:Log + Network

连接建立后,插件会去“钩住” vConsole 内部的 log/network model:

  • log:patch logModel.addLog
  • network:patch networkModel.updateRequest

把数据整理成可 JSON 化的 payload,通过 forwarder 入队,再批量 flush 到 WebSocket。

你可以在 forwarder.js:github.com/fwlee0625-p… 看到这个队列逻辑:它做了两件事:

  • 限流/分批:一次最多发 50 条,避免消息太大或过于频繁
  • 队列上限:最多保留 500 条,避免离线或断线时撑爆内存

5. 关键取舍:为什么要这样设计

这类“信息回传”的方案,最容易在工程里翻车的点,我踩过几个坑,也做了对应的取舍。

5.1 不直接在模块顶层 import vConsole

一开始我写的是 import VConsole from "vconsole",后来发现这会导致:

  • 在 Node/SSR 环境“仅仅 import 这个包”,就可能因为缺少 XMLHttpRequest 之类的浏览器 API 而报错

所以我把实现改成:只依赖传入的 vConsole 实例,并且在调用时校验环境:

  • 不是浏览器环境就直接抛错
  • vConsole 实例不合法也明确报错

这样做的好处是:包更“干净”,不会在错误的环境里提前炸掉。

5.2 不自己重新实现一套采集逻辑

自己 patch console.log、hook XHR/fetch 当然也行,但维护成本会很高:

  • 不同浏览器、不同运行时、不同框架,边界非常多

vConsole 在这方面已经做了大量兼容,我们只需要把它的“产出数据”回传即可。

5.3 只做内存存储,不做持久化

服务端目前是内存存储(按设备最多保留 1000 条最新数据),原因很简单:

  • 目标是远程调试,不是日志平台
  • 轻量部署、低侵入才容易落地

如果后续要做“问题追溯”,再加持久化(比如写文件/ES)会更合适。


6. 怎么跑起来:本地 3 分钟搭一套闭环

项目用 pnpm workspace 管理,启动非常直接(详见根目录 README):

pnpm install
pnpm dev:server
pnpm dev:pc
pnpm dev:h5

使用流程也很符合直觉:

  1. PC 页面打开 Viewer,复制会话 ID
  2. H5 页面打开 vConsole 的「设置」Tab,选同一个会话并连接
  3. 在 H5 页面触发日志/请求,PC 页面实时看到数据

7. 作为开发者,我希望它带来什么

写这个插件最开始只是为了解决“我抓不到日志”,但做成项目后,我更希望它能带来两件事:

  • 让调试能力可交付:一个包 + 一个服务 + 一个 viewer,团队里谁都能用
  • 让线上问题更可控:即使你连不上真机调试,也能拿到足够多的信息做判断

如果你也在做 H5、也被“线上问题看不到”折磨过,希望这套思路能对你有一点帮助。

后面如果大家对“接入方式”“协议设计”“数据结构兼容(vConsole payload)”感兴趣,我也可以再写一篇更偏实现细节的。


附:包信息

  • 包名:vconsole-relay-plugin
  • 主入口:createSettingsPlugin(vConsoleInstance)
  • 子路径:vconsole-relay-plugin/forwarder