BridgeTrace 调试链路:一次调用如何被记录下来

📅 2026/6/25 14:42:35
BridgeTrace 调试链路:一次调用如何被记录下来
BridgeTrace 调试链路一次调用如何被记录下来这是「ASCF 架构升级」系列的第 5 篇对应提交95def05feat(bridge): enrich bridge trace with ability metadata。上一篇讲了AbilityMeta—— 能力的说明书。这一篇讲每次桥调用「走完一圈」的完整账单是怎么被记录下来的。一、JSBridge 调试为什么不能只看最终结果调一次桥的「最终结果」就两种H5 上的回调被window.__ascfOnResponse调用了拿到一个 JSONH5 上的回调超时了什么都没收到这两种结果不够用 —— 因为「没收到」可能发生在十几个地方H5 自己 JS 报错send都没调出去H5 调了send但window.ascfBridge没注入成功调的是undefinedArkTS 端WebBridgeChannel.send被调到了但JSON.parse失败JSON 解析成功但请求缺id或action请求结构没问题但 action 在注册表里找不到action 在表里但meta.enabled falsehandler 在执行过程中抛异常handler 返回了但runJavaScript回传 H5 时 webview 已销毁H5 上window.__ascfOnResponse被覆盖或没定义没有一份完整 trace 时调这种 bug 的过程就像看一个只剩谜底没题面的猜谜你看到 H5 屏幕上没反应但不知道是上面九条里的哪一条。二、一次桥接调用至少应该记下什么trace 不只是「日志」 —— 它是一次桥调用的完整生命账单。理想情况下应该包含这些字段字段含义为什么要记id请求唯一 id关联请求与响应H5 那头也按它对账action调用的能力名知道是谁出问题requestJson完整请求 JSON还原入参复现问题responseJson完整响应 JSON看返回了什么startTime进入桥的时刻ms算耗时和 H5 那边的发送时间对账endTime响应回出的时刻ms同上costendTime - startTimems性能分析直接读这个statuspending / done / errorUI 状态条用code响应码0 / 400 / 403 / 404 / 500失败分类message响应文案失败原因人话版successcode 0UI 二分用namespaceAbilityMeta.namespace分组 / 调试面板descriptionAbilityMeta.description调试时知道这是干嘛的permissionAbilityMeta.permission权限相关 bug 时一眼能看mockAbilityMeta.mock模拟实现要标出来enabledAbilityMeta.enabled调用时验证「是不是被禁用挡的」errorStackhandler 抛错时的栈真正的 root cause项目里这些字段都已经在BridgeLogEntry上了对应文件entry/src/main/ets/bridge/BridgeLog.ets。它就是项目里那个「BridgeTrace」的本体 —— 只是名字沿用了原来的 BridgeLog。三、Bridge Trace 各自解决了什么问题回到上一节那「九种失败的可能性」看看 trace 怎么把它们一一定位「H5 有没有发出请求」看 BridgeLog 列表里有没有这条 id —— 没有说明 H5 这头send没被调到问题在 H5。「ArkTS 有没有收到」看BridgeLogEntry.requestJson—— 它原样保留了 H5 发来的 JSON 字符串。能在 trace 里看到这串 JSON就证明 ArkTS 已经收到。「分发到哪个能力了」看namespace / action / description—— 它直接告诉你「这次调用被分发到了device/getDeviceInfo—— 获取设备信息」。如果 namespace 是unknown、description 是未知能力那就是 action 名根本没注册。「失败是为什么」看code400—— 请求结构不对缺 id 或 action403—— action 找到了但enabled false被开关挡了404—— action 不存在500—— handler 在执行过程中抛了异常再配合message看人话原因。如果 code 是 500还能看errorStack拿到真实栈。「这能力到底有没有跑慢」看cost。正常 mock 能力个位数 ms。如果出现几百 ms要么 H5 那头 setTimeout 抢资源、要么 handler 里做了不该做的同步 IO。这就是为什么 trace 上的每一个字段都不是「凑数」 —— 它对应着一个「调试时会被反复问到」的问题。四、一条 trace 是怎么被写下来的写入路径在NativeAbilityRegistry.dispatch内部完成。挑「成功」路径走一遍dispatch(req:BridgeRequest):BridgeResponse{constloggetBridgeLog();// 1) BAD_REQUEST 兜底一般 Channel 已经拦了...// 2) 查能力 写「请求」段先把 meta 一并写进 BridgeLogEntryconstregthis.abilities.get(req.action);constmetareg!undefined?reg.meta:undefined;constentry:BridgeLogEntrylog.onRequest(req,JSON.stringify(req),meta);if(regundefined){// UNKNOWN_ACTION 路径……}if(!reg.meta.enabled){// ABILITY_DISABLED 路径……}try{constdatareg.handler(req);constrespBridgeResponses.ok(req.id,data);log.onResponse(entry,resp,JSON.stringify(resp));// 3) 写「响应」段returnresp;}catch(e){// INTERNAL_ERROR 路径拿到 error.message 和 error.stack// 一起回写到 trace}}注意两件事1.onRequest时就把meta一起记进去。不是等响应回来再补。因为「这个能力是谁」是一个静态属性请求一进门立刻知道。等响应回来再写就晚了 —— 中间任何环节崩溃trace 上至少能看到 meta。2.onResponse时同时计算cost endTime - startTime。两个时间戳都在 BridgeLogEntry 上diff 自然就出来了。五条路径在 trace 上的差异路径namespacedescriptionenabledcodeerrorStackBAD_REQUESTunknown未知能力false400‘’UNKNOWN_ACTIONunknown未知能力false404‘’ABILITY_DISABLED真实值真实值false403‘’INTERNAL_ERROR真实值真实值true500非空OK真实值真实值true0‘’把这张表记住调试时看 trace 一眼就能定位是哪一种失败。五、用 UNKNOWN_ACTION 验证一遍 trace 系统这一节有一个重要前提要先说清本项目里的 H5 跑在鸿蒙 WebView 里window.ascfBridge是 ArkTS 通过javaScriptProxy注入的对象。把 ascf_bridge_demo.html 用桌面 Chrome 直接打开 ——里面不会有ascfBridge这个全局——ascfBridge.send(...)会立刻报Cannot read properties of undefined。所以「DevTools 控制台调一条 UNKNOWN_ACTION 验证」具体怎么做有三种现实选择选择 A临时给 H5 加一个验证按钮最快在ascf_bridge_demo.html任意一组现有按钮旁加一行buttononclickwindow.ascfBridge.send(JSON.stringify({id:t-unknown-Date.now(),version:1.0,action:foobar}))测试 UNKNOWN_ACTION/button跑起 demo进「JSBridge 调试实验室」点这个按钮。下方桥接日志区会出现一条[失败] unknown/foobar id t-unknown-XXX Xms → {id:t-unknown-XXX,version:1.0,action:foobar} ← {id:t-unknown-XXX,code:404,message:未知 action: foobar}打开调试器看BridgeLogEntry字段应该是namespace: unknown description: 未知能力 permission: unknown mock: false enabled: false ← 注意因为根本没注册所以 enabled 维持默认 false code: 404 message: 未知 action: foobar success: false cost: 几个 ms errorStack: 验完把这个临时按钮删掉就行。选择 BHarmonyOS WebView 远程调试鸿蒙的 WebView 支持远程调试 —— ArkTS 这边把 webview 开启调试电脑 Chrome 打开chrome://inspect/#devices能挂上去。挂上之后就能在 Chrome DevTools 控制台里直接敲window.ascfBridge.send(JSON.stringify({id:t1,version:1.0,action:foobar}))效果和「选择 A」一样。配置略繁临时验一次通常不值得但常驻调试时这套环境很方便。选择 CArkTS 端打断点最稳如果只是想确认 trace 系统在五条路径上工作正常不必触发 UNKNOWN_ACTION正常路径点 H5 任何一个已有按钮断点打在NativeAbilityRegistry.dispatch的log.onResponse(...)看 Variables 面板里entry.namespace / cost / code / success是否都对。ABILITY_DISABLED临时把NativeAbilityBiz.registerTo里某个 meta 的enabled: true改成false点对应按钮断点打在「if (!reg.meta.enabled)」分支看返回的 resp 是否 403。INTERNAL_ERROR临时在NativeAbilityImp.readDeviceInfo()第一行throw new Error(mock-boom)点设备信息按钮断点打在 catch 段看errorStack是否被填进了 entry。走完三类后UNKNOWN_ACTION 是唯一需要外部触发的 —— 这时再回选择 A 临时加个按钮就行。六、Bridge Trace 对新人调试的价值调桥这件事反复出问题的是「中间环节」不是 H5 没发、不是 ArkTS 没收而是「发出来了但中间某步出错」。新人最大的痛苦就是用 console.log 调但日志都在 H5 那边、ArkTS 那边、两边各看一半用断点调但断点打错地方、没命中用 try/catch 加 alert搞了半天发现 alert 都没出现trace 解决的核心问题是**「把所有相关信息聚到一处」**一条调用一条 trace。不用拼接日志、不用找上下文 —— 同一个 id 下面请求、响应、耗时、meta、错误栈全部在一行里。失败分类是结构化的。不靠看 message 字符串猜看 code namespace enabled 三个字段就能 5 秒定位是哪一种。能在生产用。trace 是ObservedV2TraceUI 上就能直接呈现。开发模式下打开调试面板QA 不用懂代码也能截屏给开发看。不依赖 console。ArkTS 的 console 不一定都能直观看到HiLog 里翻日志成本不低trace 是真正的内存数据挂在getBridgeLog()单例上任何地方都能读。数据为未来的功能保留入口。想做调试面板读entries。想做异常告警监听status error。想做性能监控看cost。trace 数据是开放的未来加任何观测能力都不需要回头补字段。新人最容易踩的两个坑以为「我看不到 UI 显示就是没记 trace」。错。trace 是数据UI 是渲染。即便 WebRuntimePage 上的桥接日志区不显示 costtrace 数据里cost字段也是有值的去断点里看 entry 对象本身。以为「pending状态就是 trace 出问题了」。不是。pending只是 trace 默认值。本项目里因为 dispatch 是同步的pending 几乎是一瞬UI 上肉眼基本看不到。如果你看到一直停在 pending那确实是 handler 卡住了 —— 这反而是 trace 在告诉你「这次调用永远没回」。我应该能讲出来的 5 个问题调一次桥失败可能发生在哪些环节BridgeTrace 是怎么帮你区分这些环节的BridgeLogEntry的 17 个字段里哪几个是onRequest时写的、哪几个是onResponse时写的为什么 meta 要在onRequest时就写进去UNKNOWN_ACTION、ABILITY_DISABLED、INTERNAL_ERROR 这三种失败的 trace 长什么样在 namespace / enabled / errorStack 字段上能看出什么差别为什么本项目的 H5 不能直接用桌面 Chrome 打开 控制台敲ascfBridge.send要验 UNKNOWN_ACTION新人有哪几种现实做法BridgeTrace 这套数据为「调试面板」「异常告警」「性能监控」「README 自动生成」分别预留了什么入口