H5ErrorRelay 增加 console.log/info 转发,查明 Console 空的根因

排查「Safari Web Inspector 里 H5 控制台没输出」时定位到首要原因不在原生侧:
gamehall.zip 的 js/01_SubGame/00_SubGame_Config.js 里 Game_Config.Debugger
.isDebugger 出厂为 false,把「发送数据 / 接收数据」等高频日志全挡住了;未被
挡住的 Connect:... 又在建 websocket 时就打完,挂上 Inspector 时早已过去。
调试时在 Safari 控制台运行时改 Game_Config.Debugger.isDebugger = true 即可,
不动 H5 任何文件(原则 A)。

顺带补齐原生转发能力:
- 新增 console.log / console.info 转发,仅 Debug 构建注入(#if DEBUG 编译期
  摘掉那段 JS)。Release 包不为每条业务日志付 JS→Native IPC,也不把 H5 内部
  输出暴露给外部审阅;已验证 Release 二进制中该 hook 字符串命中 0 次
- log/info 走裸 print 而非 diagLog:diagLog 会落盘到 Documents/diag.log,而该
  sink 无轮转无上限(BridgeBus.swift:47),isDebugger 打开后 firehose 会吃光磁盘
- 参数格式化统一走 fmt(),对象改用 JSON.stringify —— 大厅业务大量 console.log(msg)
  打整包,原先 String(obj) 只得到无用的 [object Object];循环引用逐级回退
- 原始 console 调用照常透传,不影响 Safari 侧同时查看

无契约影响:H5 可观察行为不变,Release 行为完全不变。
docs/H5-Debug-Guide.md 同步补 §7 Q3 根因 + 新增 §8.1 转发能力表。
This commit is contained in:
joywayer
2026-08-08 14:05:37 +08:00
parent 1203957924
commit f4acccdd6c
2 changed files with 126 additions and 17 deletions
+68 -14
View File
@@ -2,8 +2,9 @@
// H5ErrorRelay.swift
// ylgamehall
//
// H5 WebView `console.error` / `window.onerror` /
// `unhandledrejection` `webkit.messageHandlers.h5error` print
// H5 / WebView `console.error` / `console.warn` /
// `window.onerror` / `unhandledrejection` `webkit.messageHandlers.h5error`
// printDebug `console.log` / `console.info`
//
// Phase 9.2Design §11.2msext /
// B H5 Xcode console
@@ -45,6 +46,14 @@ public final class H5ErrorRelay: NSObject {
guard let dict = body as? [String: Any],
let kind = dict["kind"] as? String else { return }
switch kind {
case "console.log", "console.info":
// Debug javaScriptSource #if DEBUG
// print diagLogH5 `Game_Config.Debugger.isDebugger`
// console.log diagLog
// `Documents/diag.log` BridgeBus.swift:47
// firehose Xcode console
let args = (dict["args"] as? [String]) ?? []
print("[H5 \(kind)] " + args.joined(separator: " "))
case "console.error":
let args = (dict["args"] as? [String]) ?? []
diagLog("[H5 console.error] " + args.joined(separator: " "))
@@ -69,31 +78,55 @@ public final class H5ErrorRelay: NSObject {
}
}
/// JS hook `webkit.messageHandlers.h5error.postMessage(payload)`
/// JS hook / `webkit.messageHandlers.h5error.postMessage(payload)`
/// send try/catchhook
private static let javaScriptSource = """
///
/// `console.log` / `console.info` Debug Release
/// JSNative IPC H5
private static var javaScriptSource: String {
#if DEBUG
let verboseHooks = verboseConsoleHookJS
#else
let verboseHooks = ""
#endif
return """
(function(){
function send(payload){
try { window.webkit.messageHandlers.h5error.postMessage(payload); } catch(e){}
}
// 参数格式化:直接 String(obj) 会得到无用的 "[object Object]",而大厅业务
// 大量使用 console.log(msg) / console.log(res) 打整包,故对象走 JSON.stringify。
// 循环引用时 stringify 抛错,回退 String();再抛就放弃,绝不让 hook 影响业务。
function fmt(v){
try {
if (v === null) { return 'null'; }
if (v === undefined) { return 'undefined'; }
var t = typeof v;
if (t === 'string') { return v; }
if (t === 'number' || t === 'boolean' || t === 'function') { return String(v); }
if (v instanceof Error) { return v.stack || (v.name + ': ' + v.message); }
var s = JSON.stringify(v);
return (s === undefined) ? String(v) : s;
} catch(e) {
try { return String(v); } catch(e2) { return '[unstringifiable]'; }
}
}
function collect(a){
var out = [];
for (var i=0; i<a.length; i++) { out.push(fmt(a[i])); }
return out;
}
var origErr = console.error;
console.error = function(){
try {
var args = [];
for (var i=0; i<arguments.length; i++) { args.push(String(arguments[i])); }
send({ kind:'console.error', args: args });
} catch(e){}
try { send({ kind:'console.error', args: collect(arguments) }); } catch(e){}
if (origErr) { origErr.apply(console, arguments); }
};
var origWarn = console.warn;
console.warn = function(){
try {
var args = [];
for (var i=0; i<arguments.length; i++) { args.push(String(arguments[i])); }
send({ kind:'console.warn', args: args });
} catch(e){}
try { send({ kind:'console.warn', args: collect(arguments) }); } catch(e){}
if (origWarn) { origWarn.apply(console, arguments); }
};
\(verboseHooks)
window.addEventListener('error', function(ev){
send({
kind:'onerror',
@@ -114,6 +147,27 @@ public final class H5ErrorRelay: NSObject {
});
})();
"""
}
/// Debug `console.log` / `console.info`
///
/// H5 `Game_Config.Debugger.isDebugger`
/// `gamehall.zip` `js/01_SubGame/00_SubGame_Config.js` `false`
/// `` / ``
/// Safari Web Inspector `Game_Config.Debugger.isDebugger = true`
/// H5 A
private static let verboseConsoleHookJS = """
var origLog = console.log;
console.log = function(){
try { send({ kind:'console.log', args: collect(arguments) }); } catch(e){}
if (origLog) { origLog.apply(console, arguments); }
};
var origInfo = console.info;
console.info = function(){
try { send({ kind:'console.info', args: collect(arguments) }); } catch(e){}
if (origInfo) { origInfo.apply(console, arguments); }
};
"""
}
// MARK: - WKScriptMessageHandler proxyweak target retain cycle