从“手机抓不到日志”到“一套远程调试闭环”:我为什么写了 vconsole-relay-plugin
平时做 H5,你一定遇到过这种场景:
- 用户说“页面卡住了/白屏了/按钮点了没反应”,但你在本地怎么都复现不出来
- 线上环境、灰度环境、客户内网……你连 Chrome DevTools 都不一定能连上
- 真机调试不是不行,但每次都要插线、配代理、开远程调试,成本很高
- 最要命的是:你缺的往往不是“更强的调试工具”,而是“能把信息稳定带回来的通道”
我就是在这些“抓不到日志”的时刻,开始动手写这个插件的。
这篇文章就从开发者视角聊聊:
- 我们到底缺了什么
- 为什么选择基于 vConsole 扩展
- 这个插件在项目里怎么跑起来
- 设计上踩过哪些坑、怎么取舍
希望能给你一个可复用的思路:把调试变成“可交付的能力”,而不是“临时的技巧”。
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 链接占位;你发布到掘金后也可以换成自己的图床链接):
效果截图
2. 为什么选 vConsole:因为它“足够工程化”
很多人对 vConsole 的第一印象是“线上临时开个控制台”,但它其实有两个对工程非常友好的点:
- 它已经把常见信息源收集好了(Console、Network、Storage……)
- 它支持插件机制:可以加一个新 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
使用流程也很符合直觉:
- PC 页面打开 Viewer,复制会话 ID
- H5 页面打开 vConsole 的「设置」Tab,选同一个会话并连接
- 在 H5 页面触发日志/请求,PC 页面实时看到数据
7. 作为开发者,我希望它带来什么
写这个插件最开始只是为了解决“我抓不到日志”,但做成项目后,我更希望它能带来两件事:
- 让调试能力可交付:一个包 + 一个服务 + 一个 viewer,团队里谁都能用
- 让线上问题更可控:即使你连不上真机调试,也能拿到足够多的信息做判断
如果你也在做 H5、也被“线上问题看不到”折磨过,希望这套思路能对你有一点帮助。
后面如果大家对“接入方式”“协议设计”“数据结构兼容(vConsole payload)”感兴趣,我也可以再写一篇更偏实现细节的。
附:包信息
- 包名:
vconsole-relay-plugin - 主入口:
createSettingsPlugin(vConsoleInstance) - 子路径:
vconsole-relay-plugin/forwarder