From b461b291f5604a30012aaab3f6209f41d0638cb5 Mon Sep 17 00:00:00 2001 From: lanterngamescn Date: Fri, 26 Jun 2026 07:09:14 +0800 Subject: [PATCH] =?UTF-8?q?M5(T-M5-06):=20=E5=8F=AF=E8=A7=82=E6=B5=8B?= =?UTF-8?q?=E2=80=94=E2=80=94BridgeTracer=20=E5=85=A8=E9=93=BE=E8=B7=AF=20?= =?UTF-8?q?traceId=20+=20ErrorCenter=E2=86=92APM=20sink?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Message 加非协议 traceId 字段(不进 toJson/toArray,对 H5 透明) - BridgeController 在 OUTBOUND/INBOUND/HANDLER/RESPONSE/DISPATCH/RETURN/DROP 各阶段打点,一条桥消息往返可经 traceId 还原 - entry 新增 ErrorReporter.installErrorSink(),打通 ErrorCenter Report→APM/Crash 上报通道(结构化字段就位,接入真实 SDK 仅改 reportToApm) Co-Authored-By: Claude Opus 4.8 (1M context) --- entry/src/main/ets/di/ErrorReporter.ets | 33 +++++++++++++++++++ .../main/ets/entryability/EntryAbility.ets | 3 ++ .../src/main/ets/core/BridgeController.ets | 22 +++++++++++-- feature_bridge/src/main/ets/core/Message.ets | 5 +++ 4 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 entry/src/main/ets/di/ErrorReporter.ets diff --git a/entry/src/main/ets/di/ErrorReporter.ets b/entry/src/main/ets/di/ErrorReporter.ets new file mode 100644 index 0000000..cde74e6 --- /dev/null +++ b/entry/src/main/ets/di/ErrorReporter.ets @@ -0,0 +1,33 @@ +/** + * 错误上报接入点(T-M5-06 可观测)。entry 层向 common 的 ErrorCenter 注入 sink, + * 把 Report 策略的错误转发到 APM/Crash 平台。 + * + * common 层不依赖 UI / 第三方上报 SDK,故由本文件在启动时 setSink;接入真实平台 + * (如华为 APM / Bugly)只改这里的 reportToApm(),对各能力 Provider 透明。 + */ +import { ErrorCenter, ErrorStrategy } from 'common'; +import { AppError } from 'contracts'; +import { hilog } from '@kit.PerformanceAnalysisKit'; + +const DOMAIN = 0x9527; +const TAG = 'ErrorReporter'; + +/** 转发到 APM/Crash 平台。当前为预留实现(结构化记录),接入 SDK 时在此替换。 */ +function reportToApm(error: AppError): void { + // TODO(M6+): 接入华为 APM / Crash SDK,上报 { kind, code, message, cause }。 + // 现阶段:结构化打到 hilog,保证上报链路已贯通、字段就位。 + const code: string = error.code !== undefined ? String(error.code) : '-'; + hilog.error(DOMAIN, TAG, 'APM report | kind=%{public}s code=%{public}s msg=%{public}s', + error.kind, code, error.message); +} + +/** 在应用启动时调用,安装错误 sink。 */ +export function installErrorSink(): void { + ErrorCenter.setSink((error: AppError, strategy: ErrorStrategy): void => { + if (strategy === ErrorStrategy.Report) { + reportToApm(error); + } + // Notify/Block 的用户可见处置由 UI 层(容器/路由)按场景实现; + // Silent 已由 ErrorCenter 记日志,此处不重复。 + }); +} diff --git a/entry/src/main/ets/entryability/EntryAbility.ets b/entry/src/main/ets/entryability/EntryAbility.ets index b13220b..fddd5dc 100644 --- a/entry/src/main/ets/entryability/EntryAbility.ets +++ b/entry/src/main/ets/entryability/EntryAbility.ets @@ -4,6 +4,7 @@ import { window } from '@kit.ArkUI'; import { EventBus } from 'common'; import { WeChatApi } from 'feature_capabilities'; import { AppEvents } from '../routes/AppRoutes'; +import { installErrorSink } from '../di/ErrorReporter'; const DOMAIN = 0x0000; @@ -15,6 +16,8 @@ export default class EntryAbility extends UIAbility { hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err)); } hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); + // 可观测(T-M5-06):安装错误上报 sink,打通 ErrorCenter → APM/Crash 通道 + installErrorSink(); // 微信回调(冷启动经 want 拉回):交 SDK 解析 → 路由到 Share/Login 回调 WeChatApi.getInstance().handleWant(want); } diff --git a/feature_bridge/src/main/ets/core/BridgeController.ets b/feature_bridge/src/main/ets/core/BridgeController.ets index 5ae9b03..ad8118c 100644 --- a/feature_bridge/src/main/ets/core/BridgeController.ets +++ b/feature_bridge/src/main/ets/core/BridgeController.ets @@ -11,6 +11,7 @@ import { IWebController } from '../web/IWebController'; import { HandlerRegistry, BridgeHandler } from './HandlerRegistry'; import { Message } from './Message'; import { MessageCodec } from './MessageCodec'; +import { BridgeTracer } from 'common'; const YY_SCHEMA: string = 'yy://'; const YY_RETURN: string = 'yy://return/'; @@ -98,6 +99,8 @@ export class BridgeController { private doSend(handlerName: string | undefined, data: string, cb?: ResponseCallback): void { const m = new Message(); + m.traceId = BridgeTracer.next(); + BridgeTracer.step(m.traceId, 'OUTBOUND', handlerName ?? '(default)'); if (data.length > 0) { m.data = data; // 对齐 Android !TextUtils.isEmpty(data) } @@ -144,6 +147,7 @@ export class BridgeController { const responseId = m.responseId; if (responseId !== undefined && responseId.length > 0) { // H5 对"原生 callHandler"的回执 + BridgeTracer.step('return', 'RETURN', responseId); const fn = this.responseCallbacks.get(responseId); if (fn !== undefined) { fn(m.responseData ?? ''); @@ -152,15 +156,21 @@ export class BridgeController { } else { // H5 主动 callHandler → 分发到能力层 handler const callbackId = m.callbackId; + const traceId: string = BridgeTracer.next(); + const name = m.handlerName; + BridgeTracer.step(traceId, 'INBOUND', name ?? '(default)'); const respFn: ResponseCallback = (callbackId !== undefined && callbackId.length > 0) ? (d: string) => { - this.queue(Message.response(callbackId, d)); + BridgeTracer.step(traceId, 'RESPONSE', `cb=${callbackId}`); + const rm = Message.response(callbackId, d); + rm.traceId = traceId; // 回执下发沿用入站 traceId,串起一条往返 + this.queue(rm); } : (_d: string) => { }; - const name = m.handlerName; const handler: BridgeHandler = (name !== undefined && this.registry.has(name)) ? this.registry.get(name)! : this.registry.default(); + BridgeTracer.step(traceId, 'HANDLER', name ?? '(default)'); handler(m.data ?? '', respFn); } } @@ -173,6 +183,7 @@ export class BridgeController { const fn = this.getFunctionFromReturnUrl(url); const data = this.getDataFromReturnUrl(url); if (fn !== undefined) { + BridgeTracer.step('return', 'RETURN', fn); const cb = this.responseCallbacks.get(fn); if (cb !== undefined) { cb(data ?? ''); @@ -192,8 +203,15 @@ export class BridgeController { private dispatch(m: Message): void { // 非激活:抑制出站(native→H5 不下发到非激活 Web)。激活时机由 WebSlotManager 控制顺序。 if (!this.active) { + if (m.traceId !== undefined) { + BridgeTracer.step(m.traceId, 'DROP', 'inactive'); + } return; } + if (m.traceId !== undefined) { + const label: string = m.handlerName ?? (m.responseId !== undefined ? 'response' : '(default)'); + BridgeTracer.step(m.traceId, 'DISPATCH', label); + } // lzyzsd 二次转义(复刻 \ 与 ");再补转义单引号——lzyzsd 原版遗漏,而出站脚本用 // 单引号包裹 _handleMessageFromNative('...'),data 含 ' 会提前闭合字符串。补转义后 // H5 JSON.parse 得到的仍是原始 data,对 H5 完全透明(边界行为不变)。 diff --git a/feature_bridge/src/main/ets/core/Message.ets b/feature_bridge/src/main/ets/core/Message.ets index 7aafed9..63679d9 100644 --- a/feature_bridge/src/main/ets/core/Message.ets +++ b/feature_bridge/src/main/ets/core/Message.ets @@ -9,6 +9,11 @@ export class Message { callbackId?: string; responseId?: string; responseData?: string; + /** + * 全链路追踪 ID(T-M5-06)。**非桥协议字段**:MessageCodec.toJson/toArray 只挑上面 5 个 + * 协议字段,traceId 既不下发给 H5、也不参与回传解析,对 H5 完全透明(不破坏零改动铁律)。 + */ + traceId?: string; /** 构造一条回执消息(对端 callbackId → responseId)。 */ static response(responseId: string, responseData: string): Message {