# Editor模块 Console面板错误绑定 fallback sink 导致运行时日志不显示 ## 1. 问题定义 当前 editor 底部 `Console` 面板在 PlayMode 下看不到 C# `Debug.Log` 输出。 已确认: 1. `Tick` 正在正常推进,脚本字段计数持续变化 2. `Debug.Log` 已经成功从 C# 进入 native logger 3. `editor/bin/Debug/editor.log` 中可以看到脚本日志 4. 但 editor 底部 `Console` 面板仍然为空,或看不到对应运行时日志 这说明问题不在脚本运行时本身,而在 editor 控制台面板读取日志数据的链路。 --- ## 2. 复现现象 复现方式: 1. 给场景对象挂上 `project/Assets/Scripts/TickLogProbe.cs` 2. 点击 editor 的 `Play` 3. 观察脚本上的计数字段持续递增 4. 观察底部 `Console` 面板,没有出现 `[Project TickLogProbe] Awake/Start/Update/...` 同时检查: - `editor/bin/Debug/editor.log` 可以看到类似日志已经真实写入: - `[INFO] [Scripting] [Project TickLogProbe] Awake` - `[INFO] [Scripting] [Project TickLogProbe] Start` - `[INFO] [Scripting] [Project TickLogProbe] Update 1` --- ## 3. 排查结论 问题不在以下层级: 1. 不在 `Tick` 系统 2. 不在 C# `Debug.Log` 3. 不在 Mono internal call 注册 4. 不在 native `Logger` 5. 不在 `FileLogSink` 这些链路都已经工作正常。 真正出问题的是: - editor `ConsolePanel` 读到的不是 logger 中真实注册的 `EditorConsoleSink` - 它读到的是一个错误的 fallback 实例 --- ## 4. 根本原因 根因位于: - `editor/src/Core/EditorConsoleSink.cpp` 当前实现: 1. `ConfigureEditorLogging()` 启动时会把一个真正的 `EditorConsoleSink` 注册到 `Logger` 2. 后续 `ConsolePanel::OnAttach()` 会调用 `EditorConsoleSink::GetInstance()` 3. `GetInstance()` 内部存在一个 `static EditorConsoleSink fallbackInstance` 4. 但 `EditorConsoleSink` 的构造函数会无条件执行 `s_instance = this` 这会导致一个严重副作用: 1. logger 里原本已经注册好的真实 `EditorConsoleSink` 先成为 `s_instance` 2. 当 `ConsolePanel` 第一次调用 `GetInstance()` 时,`fallbackInstance` 被构造 3. `fallbackInstance` 构造时又把 `s_instance` 覆盖成它自己 4. 此后 `ConsolePanel` 通过 `GetInstance()` 拿到的是 fallback sink 5. 但 logger 继续写入的仍然是最初注册到 `Logger` 的那一个真实 sink 最终结果就是: 1. 文件日志正常 2. logger 正常 3. 脚本日志正常 4. Console 面板读取的是另一份空数据 --- ## 5. 关键代码位置 相关位置: - `editor/src/Core/EditorLoggingSetup.h` - `editor/src/Core/EditorConsoleSink.cpp` - `editor/src/panels/ConsolePanel.cpp` - `editor/src/Application.cpp` 关键调用顺序: 1. `Application::Initialize()` 2. `ConfigureEditorLogging()` 3. `Logger` 注册真实 `EditorConsoleSink` 4. `AttachEditorLayer()` 5. `ConsolePanel::OnAttach()` 6. `EditorConsoleSink::GetInstance()` 7. fallback 实例构造并错误覆盖 `s_instance` --- ## 6. 影响范围 这个问题的影响不只是 `TickLogProbe` 看不到日志,而是整个 editor runtime 日志可视化都不可信: 1. 任何 C# `Debug.Log` 2. 任何 `Scripting` 分类日志 3. 未来 PlayMode 运行时诊断 4. 运行时错误追踪 5. 面向脚本模块的调试体验 从开发效率上看,这个问题会直接误导判断: 1. 容易误以为脚本没执行 2. 容易误以为 `Debug.Log` 没打通 3. 容易误以为 Tick 没跑 4. 实际上只是 Console 面板读错了 sink --- ## 7. 修复方向 建议修复方向: 1. `EditorConsoleSink::GetInstance()` 不应在查询过程中构造会修改全局状态的 fallback 对象 2. `EditorConsoleSink` 构造函数不应无条件覆盖 `s_instance` 3. 应保证 `ConsolePanel` 永远读取 logger 中真实注册的那一个 sink 4. 如果确实需要 fallback,也应是只读占位对象,不能污染全局 `s_instance` 更稳妥的方向包括: 1. 明确区分“真实注册实例”和“空对象返回值” 2. 让 `ConfigureEditorLogging()` 的注册实例成为唯一权威实例 3. 增加 editor 级回归测试,覆盖“logger 已写入但 ConsolePanel 读取为空”的场景 --- ## 8. 验收标准 修复后至少应满足: 1. `Debug.Log` 能实时显示在 editor 底部 `Console` 面板 2. `editor.log` 与 `Console` 面板看到的是同一批日志 3. `ConsolePanel` 读取到的 `EditorConsoleSink` 与 logger 注册实例一致 4. PlayMode 期间 `Awake/Start/FixedUpdate/Update/LateUpdate` 日志都可见 5. 不再出现“文件里有日志但面板里没有”的分裂状态 --- ## 9. 优先级 高。 原因: 1. 当前脚本运行时其实已经可用 2. 但最基础的可视化调试入口失效 3. 会直接阻塞后续对脚本生命周期、PlayMode 行为、运行时异常的验证效率 --- ## 10. 备注 本问题已经确认不是以下原因导致: 1. `Tick` 没运行 2. `Script` 没挂载成功 3. `Debug.Log` internal call 没注册 4. logger category 被禁用 5. `editor.log` 没写入 这是一个纯 editor 层的 Console sink 实例绑定错误问题。