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:
+58
-3
@@ -172,11 +172,42 @@ Safari "开发" 菜单会把每个**已加载页面**的 `WKWebView` 列为**独
|
||||
|
||||
### Q3:能看到条目但 Console 是空的
|
||||
|
||||
可能原因:
|
||||
**首要原因:H5 自带的调试总开关出厂是关的** ⭐
|
||||
|
||||
`gamehall.zip` → `js/01_SubGame/00_SubGame_Config.js:1-12`:
|
||||
|
||||
```js
|
||||
Game_Config.Debugger={
|
||||
isDebugger : false, // debugger模式下会将所有收发的包输出到控制台(正式发布改为false)
|
||||
...
|
||||
```
|
||||
|
||||
整包 220 处 `console.log`,82 处已被 `//` 注释掉,剩下的**高频日志全部挡在这个开关后面**:
|
||||
|
||||
| 位置 | 内容 |
|
||||
|------|------|
|
||||
| `js/00_Surface/09_Net.js:47` | `发送数据:...` |
|
||||
| `js/00_Surface/12_Logic.js:210` | `接收数据:...` |
|
||||
| `js/00_Surface/12_Logic.js:463 / 1317` | 包体输出 |
|
||||
|
||||
没被挡的只剩 `12_Logic.js:784/829/870/921` 的 `Connect:...`,那几行在大厅启动建 websocket 时就打完了,等你挂上 Inspector 早已过去。
|
||||
|
||||
**打开方式(不改 H5 一个字节,符合原则 A)** —— 在 Web Inspector 控制台运行:
|
||||
|
||||
```js
|
||||
Game_Config.Debugger.isDebugger = true
|
||||
```
|
||||
|
||||
这是运行时改内存里的对象属性,不碰 `gamehall.zip`、不碰任何文件;杀进程重启即恢复 `false`。
|
||||
|
||||
> ⚠️ **不要去改 `Resources/gamehall.zip` 里的这个 `false`** —— 那是 H5 修改,违反原则 A,且会跟着渠道包发出去。
|
||||
|
||||
其它可能原因:
|
||||
|
||||
- H5 端确实没有 `console.*` 调用(业务在沉默运行,没问题就不输出)
|
||||
- Web Inspector 打开前的旧日志没被记录 → 在 Web Inspector 已经打开的状态下**触发一次** H5 操作(点按钮、切 tab)再看
|
||||
- Console 顶部过滤器把日志级别过滤掉了 → 确认 All Levels 都选中
|
||||
- 连错 WebView(3 个实例 title 都是 `gameabc`)→ 敲 `typeof Game_Config`,返回 `"undefined"` 说明不是大厅那个,按 §5.2 用 hover 高亮法重选
|
||||
- H5 端确实没有 `console.*` 调用(业务在沉默运行,没问题就不输出)
|
||||
|
||||
### Q4:Sources 里看不到 .js 源码
|
||||
|
||||
@@ -200,9 +231,32 @@ Safari Web Inspector 是开发期主力,但有它够不到的场景:
|
||||
|
||||
- **真机 + 用户已在生产环境**:Release 包没有 inspectable;需要的是事后日志,参考原生侧的崩溃 / 错误上报(项目目前无 crash 监控,依赖用户反馈)
|
||||
- **断网 / 弱网下的 H5 表现**:用 Mac 上的 Network Link Conditioner,或模拟器 Features → Network Link Conditioner
|
||||
- **生产环境的 H5 console 输出**:原生侧已经装了 `H5ErrorRelay`(`Source/Bridge/H5ErrorRelay.swift`),会把 `console.error / window.onerror / unhandledrejection` 转发到原生 `print`,Debug build 可在 Xcode 控制台看到。要扩展到 `console.log` 全量转发,可参考其实现照葫芦画瓢
|
||||
- **不想开 Safari、只在 Xcode 控制台看 H5 输出**:原生侧的 `H5ErrorRelay`(`Source/Bridge/H5ErrorRelay.swift`)已覆盖,见下面 §8.1
|
||||
- **断点调试原生 Swift 与 H5 同时跑**:Xcode + Safari Web Inspector 同时打开,两边各自下断点互不影响
|
||||
|
||||
### 8.1 H5 console → Xcode 控制台(`H5ErrorRelay`)
|
||||
|
||||
`Source/Bridge/H5ErrorRelay.swift` 用 `WKUserScript(.atDocumentStart)` hook 住 console 与全局异常,通过 `webkit.messageHandlers.h5error` 转发到原生。**不改 H5 一行**(原则 A)。
|
||||
|
||||
| JS 侧 | Xcode 输出前缀 | 落 `Documents/diag.log` | 构建配置 |
|
||||
|-------|---------------|------------------------|---------|
|
||||
| `console.log` | `[H5 console.log]` | ❌ | **仅 Debug** |
|
||||
| `console.info` | `[H5 console.info]` | ❌ | **仅 Debug** |
|
||||
| `console.warn` | `[H5 console.warn]` | ✅ | Debug + Release |
|
||||
| `console.error` | `[H5 console.error]` | ✅ | Debug + Release |
|
||||
| `window.onerror` | `[H5 onerror]` | ✅ | Debug + Release |
|
||||
| `unhandledrejection` | `[H5 unhandledrejection]` | ✅ | Debug + Release |
|
||||
|
||||
设计要点:
|
||||
|
||||
- **`log` / `info` 只在 Debug 注入**:Release 包不该为每条业务日志付一次 JS→Native IPC,也不该把 H5 内部输出暴露给外部审阅。`#if DEBUG` 在编译期就把这段 JS 从 `javaScriptSource` 里摘掉
|
||||
- **`log` / `info` 走裸 `print` 而非 `diagLog`**:`diagLog` 会同步落盘到 `Documents/diag.log`,而该 sink 无轮转、无大小上限(`BridgeBus.swift:47`)。一旦 §7 Q3 里的 `isDebugger` 被打开,收发包 firehose 灌进去会把设备磁盘吃光
|
||||
- **对象参数走 `JSON.stringify`**:大厅业务大量使用 `console.log(msg)` / `console.log(res)` 打整包,直接 `String(obj)` 只会得到无用的 `[object Object]`。循环引用时 stringify 抛错 → 回退 `String()` → 再抛则输出 `[unstringifiable]`
|
||||
- **原始 console 调用被透传**(`origLog.apply(console, arguments)`),所以转发不影响 Safari Web Inspector 里照常看到日志,两条路可以同时用
|
||||
- **仅 `BridgedWebView`(大厅 + 子游戏)注入**,`OverlayViewController` 是第三方外链,不挂
|
||||
|
||||
⚠️ 依然受 §7 Q3 的 `Game_Config.Debugger.isDebugger` 制约:收发包日志被 H5 自己的开关挡着,Xcode 控制台同样看不到。需要时在 Safari 控制台运行 `Game_Config.Debugger.isDebugger = true`。
|
||||
|
||||
---
|
||||
|
||||
## 9. 修订记录
|
||||
@@ -210,3 +264,4 @@ Safari Web Inspector 是开发期主力,但有它够不到的场景:
|
||||
| 日期 | 内容 |
|
||||
|------|------|
|
||||
| 2026-06-24 | 文档建立。落地 `BridgedWebView` / `OverlayViewController` 两处 `isInspectable` 补丁。 |
|
||||
| 2026-08-08 | 查明「Console 空」的首要原因是 H5 自带 `Game_Config.Debugger.isDebugger = false`,补进 §7 Q3。`H5ErrorRelay` 扩展 `console.log` / `console.info` 转发(Debug only)+ 对象参数 JSON 序列化,新增 §8.1。 |
|
||||
|
||||
@@ -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`
|
||||
// 上报到原生 print;Debug 构建下额外转发 `console.log` / `console.info`。
|
||||
//
|
||||
// Phase 9.2(Design §11.2)。msext 时代仅靠用户/运营手工反馈;本项目作为
|
||||
// 原则 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 而不是 diagLog:H5 把 `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/catch,hook 自身永远不应抛错(避免污染业务流程)。
|
||||
private static let javaScriptSource = """
|
||||
///
|
||||
/// `console.log` / `console.info` 只在 Debug 构建注入:Release 包不该为每条业务
|
||||
/// 日志付一次 JS→Native 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 proxy(weak target 切断 retain cycle)
|
||||
|
||||
Reference in New Issue
Block a user