
1. 移动端调试的真实困境为什么AI看不见你的H5日志做过H5开发的人都有一个共同的痛在PC浏览器上调试得好好的页面一放到手机里就各种问题。按钮点不动、接口报错、页面白屏而你手上只有一台手机和一个完全不知道内部发生了什么的应用。Chrome DevTools的远程调试虽然能用但配置繁琐需要USB连接、需要开启开发者模式、需要目标WebView支持调试协议而且一旦涉及到跨端框架比如某些App内嵌的WebView远程调试经常连不上。传统的做法是在页面里引入vConsole一个轻量级的移动端调试面板。它能在手机屏幕上直接显示console日志、网络请求、DOM结构等信息相当于把DevTools的核心功能搬到了手机端。这个方案确实解决了很多问题但它有一个根本性的局限只有人能看到AI看不到。这就引出了我们今天要聊的核心话题如何让AI直接读取H5页面的日志和网络请求从而实现真正的AI辅助debug。关键词里的vConsole、MCP、H5、debug、WebSocket其实指向的就是这条技术链路。MCP是Model Context Protocol的缩写它本质上是一套让AI模型能够调用外部工具、读取外部数据的标准协议。把vConsole采集到的日志和请求数据通过WebSocket实时传输到一个MCP Server再让AI通过MCP协议读取这些数据就实现了AI直接看见H5运行状态这件事。这套方案适合谁适合所有在做H5开发、跨端开发、移动端WebView调试的前端工程师。不管你用的是原生H5、Vue、React还是各种小程序转H5的框架只要你的页面跑在浏览器环境里这套思路就能用。哪怕你之前完全没接触过MCP只要你会写JavaScript、了解WebSocket的基本用法就能跟着这篇文章把整套链路搭起来。我先把整体架构说清楚让你心里有个全景图。整个系统分三层第一层是注入到H5页面里的采集脚本它负责拦截console方法和网络请求把数据通过WebSocket发出去第二层是一个WebSocket服务端它接收页面发来的数据并缓存第三层是MCP Server它把缓存的数据暴露成AI可以调用的工具接口。AI通过MCP协议调用这些工具就能读取到实时的日志和请求信息。听起来有点绕但实际写起来代码量并不大核心逻辑加起来可能就两三百行。2. 拆解vConsole的数据采集机制与WebSocket传输链路2.1 vConsole到底采集了什么怎么采集的要理解整套方案首先得搞清楚vConsole的工作原理。vConsole本质上是一个前端SDK它在页面加载时执行做了几件关键的事情。第一它重写了console对象上的log、warn、error、info等方法在调用原始方法的同时把参数格式化后存到自己的日志队列里。第二它通过Proxy或者直接替换的方式拦截XMLHttpRequest和fetch记录请求的URL、方法、请求头、请求体、响应状态码、响应头和响应体。第三它还监听了window.onerror和unhandledrejection事件捕获未处理的异常和Promise拒绝。这些数据被vConsole收集后默认是渲染到它自己创建的DOM面板里。但vConsole也提供了插件机制和事件系统允许外部代码监听这些数据。我们要做的就是利用这个机制在vConsole收集数据的同时把数据转发到WebSocket连接上。这里有一个关键的技术选择是直接修改vConsole源码还是通过它的插件API来扩展我的建议是走插件路线。vConsole的插件系统虽然文档不算特别完善但足够稳定而且升级vConsole版本时不会因为源码改动导致冲突。具体做法是调用vConsole.VConsolePlugin创建一个插件实例在插件的onReady回调里拿到vConsole的核心事件总线然后监听console和network相关的事件。不过实际测试下来vConsole的事件系统对网络请求的暴露不够完整特别是响应体的内容有时候拿不到。所以更稳妥的方案是console日志走vConsole的事件监听网络请求则自己单独拦截一层。这样虽然代码多了一点但数据完整性有保障。2.2 为什么选WebSocket而不是HTTP轮询数据传输层选择WebSocket而不是HTTP轮询这个决策背后有几个实际考量。HTTP轮询的问题是延迟高、无效请求多。假设你每秒轮询一次那日志从产生到被AI读取至少有1秒延迟而且大部分轮询请求返回的是空数据浪费资源。WebSocket是长连接数据产生后可以立即推送延迟在毫秒级别。另一个考量是双向通信的需求。虽然主要数据流是从页面到服务端但有时候AI可能需要主动向页面发送指令比如清空当前日志或者执行一段代码。WebSocket天然支持双向通信HTTP轮询要实现这个就得再加一套接口。还有一个容易被忽略的点移动端网络环境复杂WebSocket的断线重连机制需要认真设计。我在实际项目里遇到过手机切到后台再切回来、WebSocket连接断开但页面不知道的情况。解决方案是加心跳机制客户端每隔一段时间发送一个ping消息服务端回复pong如果连续几次没收到pong就主动重连。心跳间隔建议设在15到30秒之间太短了耗电太长了断线发现不及时。// WebSocket心跳与重连的核心逻辑 class LogSocket { constructor(url) { this.url url; this.reconnectDelay 1000; this.maxReconnectDelay 30000; this.heartbeatInterval 20000; this.connect(); } connect() { this.ws new WebSocket(this.url); this.ws.onopen () { this.reconnectDelay 1000; this.startHeartbeat(); }; this.ws.onmessage (e) { if (e.data pong) { this.lastPong Date.now(); } }; this.ws.onclose () { this.stopHeartbeat(); setTimeout(() this.connect(), this.reconnectDelay); this.reconnectDelay Math.min(this.reconnectDelay * 2, this.maxReconnectDelay); }; } startHeartbeat() { this.heartbeatTimer setInterval(() { if (this.ws.readyState WebSocket.OPEN) { this.ws.send(ping); } }, this.heartbeatInterval); } stopHeartbeat() { clearInterval(this.heartbeatTimer); } send(data) { if (this.ws.readyState WebSocket.OPEN) { this.ws.send(JSON.stringify(data)); } } }这段代码里的指数退避重连策略很关键。第一次断线1秒后重连第二次2秒第三次4秒一直翻倍到30秒封顶。这样既保证了快速恢复又避免了服务端挂掉时客户端疯狂重连把服务端打垮。2.3 数据格式设计让AI能读懂的关键数据从页面发到服务端格式设计直接决定了AI能不能有效理解。我的经验是不要直接把原始数据扔过去而是要做一层结构化处理。每条日志消息应该包含这几个字段类型log/warn/error/network、时间戳、页面URL、设备信息、具体内容。网络请求的数据结构要更细致一些。除了基本的URL、method、status还要包含请求耗时、请求体大小、响应体大小。响应体如果太大比如超过10KB不要全量传输截取前几KB并标记截断。这是因为AI的上下文窗口有限塞太多无关数据反而影响分析效果。// 网络请求数据结构示例 { type: network, timestamp: 1700000000000, pageUrl: https://example.com/page, request: { url: /api/user/info, method: GET, headers: { Content-Type: application/json }, body: null }, response: { status: 200, headers: { Content-Type: application/json }, body: {code:0,data:{name:test}}, truncated: false }, timing: { start: 1700000000000, end: 1700000000230, duration: 230 } }时间戳用毫秒级Unix时间戳方便排序和计算耗时。页面URL要带上因为一个WebSocket连接可能同时接收多个页面的数据比如你在调试一个多页应用。设备信息包括userAgent和屏幕尺寸有些布局问题跟设备强相关。3. MCP Server的设计把日志数据变成AI可调用的工具3.1 MCP协议的核心概念与最小实现MCP是Anthropic推出的一个开放协议目的是让AI模型能够以标准化的方式调用外部工具和读取外部资源。你可以把它理解成AI世界的USB接口——不管什么工具只要实现了MCP协议AI就能用统一的方式去调用它。MCP的核心概念有三个Tools、Resources和Prompts。Tools是AI可以主动调用的函数比如获取最新日志、搜索包含关键词的请求。Resources是AI可以读取的数据源比如当前页面的所有网络请求列表。Prompts是预定义的提示模板这个在我们的场景里用得比较少。实现一个MCP Server最直接的方式是用官方提供的SDK。TypeScript SDK和Python SDK都比较成熟。考虑到我们的WebSocket服务端大概率用Node.js写用TypeScript SDK会更顺滑不用跨语言通信。一个最小的MCP Server大概长这样import { Server } from modelcontextprotocol/sdk/server/index.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; const server new Server({ name: h5-debug-server, version: 1.0.0 }, { capabilities: { tools: {} } }); server.setRequestHandler(tools/list, async () ({ tools: [ { name: get_recent_logs, description: 获取最近的H5页面日志, inputSchema: { type: object, properties: { count: { type: number, description: 获取条数默认50 }, level: { type: string, enum: [all, error, warn], description: 日志级别过滤 } } } } ] })); server.setRequestHandler(tools/call, async (request) { if (request.params.name get_recent_logs) { const logs logStore.getRecent(request.params.arguments); return { content: [{ type: text, text: JSON.stringify(logs, null, 2) }] }; } }); const transport new StdioServerTransport(); await server.connect(transport);这段代码定义了一个名为get_recent_logs的工具AI可以调用它并传入count和level参数。服务端从logStore里取出对应的日志序列化成JSON返回。3.2 工具设计AI需要哪些调试能力工具设计是整个方案里最需要动脑子的部分。设计得好AI能高效定位问题设计得不好AI要么拿不到需要的数据要么被大量无关数据淹没。我总结了几个在实际调试中最常用的工具get_recent_logs获取最近的console日志支持按级别过滤。这个工具解决的是页面报了什么错的问题。get_network_requests获取网络请求列表支持按URL关键词、状态码、请求方法过滤。这个工具解决的是接口通不通、返回了什么的问题。get_failed_requests专门获取失败的请求状态码非2xx或请求超时。这个工具是get_network_requests的快捷方式因为排查问题时最常看的就是失败请求。search_logs在所有日志里搜索包含特定关键词的条目。当你知道错误信息里有某个关键词但不确定完整内容时这个工具很有用。get_page_info获取当前连接的页面信息包括URL、设备、连接时间。多页面调试时用来确认数据来源。clear_logs清空日志缓存。在复现一个bug之前先清空然后操作页面这样拿到的日志就是干净的操作序列。这些工具的参数设计要遵循一个原则默认值要合理让AI不传参数也能拿到有用的数据。比如get_recent_logs不传count时默认返回50条不传level时默认返回所有级别。AI有时候会忘记传参数合理的默认值能避免工具调用失败。3.3 数据缓存策略内存队列与过期清理WebSocket服务端收到的数据需要有地方存。最简单的方案是内存里维护一个固定长度的队列比如保留最近1000条日志和500条网络请求。超出长度时淘汰最旧的数据。为什么不用数据库因为调试场景下数据是临时的页面刷新后旧数据就没意义了。而且写数据库会增加延迟内存队列的读写是微秒级的完全不会成为瓶颈。但内存队列有一个问题如果AI在排查一个复杂问题需要查看较早的日志而队列已经淘汰了那些数据怎么办我的做法是提供一个快照机制。当AI调用某个工具时如果发现需要的数据已经被淘汰就返回一个提示建议AI先调用freeze_snapshot工具冻结当前数据。冻结后数据不再被淘汰直到AI主动调用release_snapshot释放。class LogStore { constructor(maxLogs 1000, maxRequests 500) { this.logs []; this.requests []; this.maxLogs maxLogs; this.maxRequests maxRequests; this.frozen false; } addLog(log) { this.logs.push(log); if (!this.frozen this.logs.length this.maxLogs) { this.logs.shift(); } } addRequest(req) { this.requests.push(req); if (!this.frozen this.requests.length this.maxRequests) { this.requests.shift(); } } getRecent(count 50, level all) { let result this.logs; if (level ! all) { result result.filter(l l.level level); } return result.slice(-count); } }这个实现里frozen标志控制是否淘汰数据。冻结时队列可以无限增长但要注意内存占用所以冻结状态下也要设置一个硬上限比如10000条超过时返回警告。4. 从零搭建完整链路的实操步骤与关键配置4.1 采集脚本的注入方式与兼容性处理采集脚本要注入到H5页面里有几种方式。最直接的是在HTML模板里加一个script标签但这种方式在调试第三方页面或者已经上线的页面时不可行。更灵活的方式是通过构建工具注入比如Webpack的DefinePlugin或者Vite的插件机制在开发模式下自动注入。如果你用的是跨端框架比如某些App的WebView可能需要在原生层做注入。Android可以通过WebView的evaluateJavascript方法注入iOS可以通过WKWebView的evaluateJavaScript。这种方式的好处是不依赖页面本身的构建流程任何页面都能注入。注入脚本时要注意几个兼容性问题。第一如果页面本身已经引入了vConsole不要重复引入检测window.vConsole是否存在即可。第二脚本要尽早执行最好在head里同步加载否则可能错过页面初始化阶段的日志。第三要考虑CSP内容安全策略的限制如果页面设置了严格的CSP内联脚本可能被阻止这时需要把脚本放到同源的js文件里加载。// 采集脚本的核心初始化逻辑 (function() { if (window.__H5_DEBUG_INJECTED__) return; window.__H5_DEBUG_INJECTED__ true; const socket new LogSocket(ws://localhost:8765); const originalConsole {}; [log, warn, error, info].forEach(level { originalConsole[level] console[level]; console[level] function(...args) { originalConsole[level].apply(console, args); socket.send({ type: log, level: level, timestamp: Date.now(), pageUrl: location.href, args: args.map(formatArg) }); }; }); function formatArg(arg) { if (arg instanceof Error) { return { type: error, message: arg.message, stack: arg.stack }; } if (typeof arg object) { try { return JSON.parse(JSON.stringify(arg)); } catch (e) { return String(arg); } } return arg; } })();这段代码里有个细节值得注意formatArg函数对Error对象做了特殊处理提取message和stack。因为console.error经常用来打印错误对象如果直接JSON.stringify会丢失stack信息。另外对普通对象做了深拷贝尝试失败时降级为String避免循环引用导致报错。4.2 WebSocket服务端的搭建与消息路由WebSocket服务端用Node.js的ws库来搭是最轻量的选择。核心逻辑是监听连接、接收消息、解析消息类型、分发到对应的存储队列。const WebSocket require(ws); const wss new WebSocket.Server({ port: 8765 }); const logStore new LogStore(); wss.on(connection, (ws, req) { const clientId generateId(); console.log(Client connected: ${clientId}); ws.on(message, (data) { const text data.toString(); if (text ping) { ws.send(pong); return; } try { const msg JSON.parse(text); if (msg.type log) { logStore.addLog({ ...msg, clientId }); } else if (msg.type network) { logStore.addRequest({ ...msg, clientId }); } } catch (e) { console.error(Failed to parse message:, e); } }); ws.on(close, () { console.log(Client disconnected: ${clientId}); }); });消息路由的关键是类型判断。除了log和network还可以扩展其他类型比如performance性能数据、storage本地存储变化等。每新增一种类型就在LogStore里加一个对应的队列。这里有个实际踩过的坑WebSocket消息大小限制。默认情况下ws库对消息大小没有严格限制但浏览器端对发送的消息大小有限制不同浏览器不一样一般在几MB到几十MB之间。如果某个请求的响应体特别大比如下载了一个大文件直接发送可能导致连接断开。所以要在客户端做截断超过阈值我一般设50KB就只发前50KB并标记truncated。4.3 MCP Server与WebSocket服务端的进程通信MCP Server和WebSocket服务端是两个独立的进程它们之间需要通信。最简单的方式是让它们跑在同一个Node.js进程里MCP Server直接访问LogStore的内存数据。但MCP协议通常通过stdio通信而WebSocket服务端需要监听端口两者在同一个进程里并不冲突。如果非要拆成两个进程可以用IPC或者本地HTTP接口通信。但我的建议是不要拆因为拆开后数据同步会引入延迟和复杂性而调试场景对实时性要求很高。把MCP Server和WebSocket服务端放在同一个进程里的架构是这样的import { Server } from modelcontextprotocol/sdk/server/index.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import { WebSocketServer } from ws; const logStore new LogStore(); // 启动WebSocket服务端 const wss new WebSocketServer({ port: 8765 }); wss.on(connection, (ws) { ws.on(message, (data) { // 处理数据写入logStore }); }); // 启动MCP Server const mcpServer new Server({ name: h5-debug, version: 1.0.0 }, { capabilities: { tools: {} } }); mcpServer.setRequestHandler(tools/call, async (request) { // 从logStore读取数据返回 }); const transport new StdioServerTransport(); await mcpServer.connect(transport);这个架构下AI通过stdio与MCP Server通信H5页面通过WebSocket与同一个进程通信数据在内存里共享没有额外的序列化和网络开销。4.4 在AI客户端里配置MCP ServerMCP Server写好后需要在AI客户端里配置才能使用。不同的AI客户端配置方式不同但核心都是告诉客户端有一个MCP Server通过什么命令启动叫什么名字。以常见的配置文件为例通常是一个JSON文件里面有一个mcpServers字段{ mcpServers: { h5-debug: { command: node, args: [/path/to/mcp-server.js], env: { WS_PORT: 8765 } } } }配置好后重启AI客户端它就会启动这个MCP Server进程并通过stdio与之通信。你可以在AI的对话里问它现在有哪些可用的工具如果配置正确它应该能列出我们定义的那些工具。这里有个常见的坑MCP Server启动失败但客户端不报错。原因是stdio通信下Server的启动错误可能被吞掉。排查方法是先在终端里手动运行node /path/to/mcp-server.js看有没有报错。如果有报错先解决报错再配置到客户端里。5. 实战调试场景AI如何用这些数据定位问题5.1 场景一接口报错但控制台没有明显信息这是最常见的调试场景。页面某个功能不工作但console里没有报错或者只有一些无关紧要的警告。传统做法是打开Network面板一个个看请求效率很低。有了这套系统后你可以直接问AI帮我看看最近有没有失败的请求。AI会调用get_failed_requests工具拿到所有非2xx的请求。然后你可以进一步问这个500错误的请求响应体是什么AI会从缓存里取出对应的响应体展示给你。我实际遇到过一个案例一个表单提交后页面没反应console没有任何错误。AI通过get_failed_requests发现提交接口返回了200但响应体里code字段是40001表示参数校验失败。而前端代码只判断了HTTP状态码没有判断业务状态码所以静默失败了。这个问题如果靠人眼看Network面板可能要翻好几个请求才能发现。5.2 场景二页面白屏需要快速定位错误源头白屏问题通常伴随一个未捕获的JS错误。AI可以调用get_recent_logs并过滤level为error快速拿到错误信息和堆栈。但堆栈信息在移动端往往被压缩过可读性很差。这时候可以结合search_logs工具搜索错误堆栈里的关键词看看错误发生前有哪些日志输出。比如错误堆栈里有renderList这个函数名就搜索renderList看看这个函数执行前后打印了什么日志。通过日志的时间序列能还原出错误发生时的执行路径。提示在生产环境调试时建议在采集脚本里加一个sourceMap解析的步骤。如果构建时生成了sourceMap可以在服务端把压缩后的堆栈还原成源码位置AI拿到的堆栈就是可读的。5.3 场景三性能问题需要分析请求耗时分布页面加载慢但不确定是哪个环节慢。AI可以调用get_network_requests拿到所有请求然后按duration排序找出最耗时的几个请求。进一步分析这些请求的timing字段看是DNS慢、连接慢还是响应慢。我一般会让AI做一个汇总分析帮我统计一下所有请求的总耗时、平均耗时以及耗时最长的三个请求。AI拿到数据后能直接给出统计结果比人一个个看快得多。如果发现某个接口特别慢还可以让AI对比同一个接口多次请求的耗时看是偶发还是稳定慢。这需要get_network_requests支持按URL分组统计我在工具实现里加了一个groupBy参数AI可以指定按URL分组。5.4 场景四多页面切换时的状态追踪在单页应用里页面切换不会刷新浏览器但URL会变。如果采集脚本只在页面加载时初始化一次切换页面后的日志会混在一起。解决方案是在采集的数据里带上当前页面URLAI分析时可以按URL过滤。更进一步可以监听history的pushState和replaceState事件在页面切换时发送一个标记消息。这样AI就能知道哪些日志属于哪个页面阶段。我在实际项目里加了这个机制后排查从A页面跳到B页面后数据丢失这类问题效率提升很明显。6. 踩坑记录与稳定性优化6.1 WebSocket连接在移动端的保活问题移动端浏览器对后台页面的WebSocket连接管理很激进。页面切到后台几秒后WebSocket可能被系统挂起或断开。等用户切回前台时连接已经断了但页面代码可能没有及时感知。解决方案是结合visibilitychange事件。当页面从后台回到前台时主动检查WebSocket的readyState如果不是OPEN状态就立即重连。同时在页面进入后台时可以主动关闭连接以节省资源回到前台时再重连。document.addEventListener(visibilitychange, () { if (document.visibilityState visible) { if (socket.ws.readyState ! WebSocket.OPEN) { socket.connect(); } } });这个逻辑看起来简单但实际测试中发现一个问题快速切换前后台时可能触发多次重连导致创建多个WebSocket连接。所以要在connect方法里加一个锁如果正在连接中就不要重复发起。6.2 大量日志导致的性能下降在调试一个循环里的日志时比如一个for循环里console.log了1000次采集脚本会发送1000条WebSocket消息。这不仅会阻塞主线程还可能导致服务端处理不过来。我的优化方案是在客户端做批量发送。维护一个待发送队列每隔100毫秒或者队列长度达到50条时一次性打包发送。这样把1000次单独发送变成20次批量发送性能提升很明显。class BatchSender { constructor(socket, interval 100, maxSize 50) { this.socket socket; this.queue []; this.interval interval; this.maxSize maxSize; this.timer setInterval(() this.flush(), interval); } push(item) { this.queue.push(item); if (this.queue.length this.maxSize) { this.flush(); } } flush() { if (this.queue.length 0) return; const batch this.queue.splice(0); this.socket.send({ type: batch, items: batch }); } }服务端收到batch类型的消息后遍历items逐个处理。这样既减少了网络往返次数又避免了单条消息过大。6.3 敏感数据泄露的风险控制调试数据里可能包含用户token、密码、手机号等敏感信息。如果这些数据被发送到服务端并被AI读取存在泄露风险。虽然是在本地环境但养成好习惯很重要。我在采集脚本里加了一个脱敏层对常见的敏感字段做处理。比如请求头里的Authorization字段只保留前几位和后几位中间用星号替代。请求体里的password、token、idCard等字段直接替换成[REDACTED]。const SENSITIVE_KEYS [password, token, authorization, idcard, phone]; function sanitize(obj) { if (typeof obj ! object || obj null) return obj; const result Array.isArray(obj) ? [] : {}; for (const key in obj) { if (SENSITIVE_KEYS.some(k key.toLowerCase().includes(k))) { result[key] [REDACTED]; } else if (typeof obj[key] object) { result[key] sanitize(obj[key]); } else { result[key] obj[key]; } } return result; }这个脱敏逻辑要在数据发送前执行确保敏感信息不会离开页面。虽然会增加一点CPU开销但相比安全风险这点开销完全值得。6.4 MCP工具调用的超时与错误处理AI调用MCP工具时如果工具执行时间过长可能会导致AI客户端超时。我们的工具大部分是内存读取速度很快但search_logs在日志量很大时可能变慢。优化方案是给搜索加索引。在LogStore里维护一个倒排索引记录每个关键词出现在哪些日志的索引位置。搜索时先查索引再取具体日志。这样即使有10万条日志搜索也能在毫秒级完成。另外所有工具都要有错误处理。如果AI传了非法参数比如count传了负数工具应该返回一个友好的错误信息而不是抛异常。错误信息要具体告诉AI哪个参数有问题、应该传什么类型的值。这样AI能自我纠正并重新调用。7. 扩展思路从H5调试到更通用的AI可观测性这套方案的思路其实不局限于H5调试。任何产生日志和事件的前端系统都可以用类似的方式让AI看见。比如小程序的调试、Electron应用的调试、甚至Node.js服务端的调试核心逻辑都是采集数据、传输数据、暴露给AI。MCP协议的价值在于它标准化了AI与外部工具的交互方式。以前要让AI读取外部数据得针对每个AI平台写不同的插件。现在只要实现一个MCP Server所有支持MCP的AI客户端都能用。这个生态还在快速发展未来可能会有更多标准化的调试工具出现。我在实际使用中最大的体会是AI辅助debug的效率提升关键不在于AI有多聪明而在于你给它提供的数据有多完整、多结构化。数据质量决定了AI分析的上限。所以花时间设计好数据采集和工具接口比反复调整AI的提示词更有效。最后分享一个实用技巧在让AI分析日志之前先让它调用clear_logs清空缓存然后你手动操作页面复现问题再让AI读取日志。这样AI拿到的日志就是干净的操作序列没有之前操作的干扰分析准确率会高很多。这个习惯我坚持用了几个月排查效率至少提升了一倍。