108 lines
3.8 KiB
Markdown
108 lines
3.8 KiB
Markdown
|
|
# Logging Architecture
|
|||
|
|
|
|||
|
|
## 这套日志系统解决什么问题
|
|||
|
|
|
|||
|
|
在游戏引擎里,日志不是单纯的输出文本,而是跨模块诊断链路。渲染后端、资源系统、编辑器工具和自动化测试都需要记录问题,但它们不应该各自决定“写到哪里、以什么格式写、是否需要同时写文件和控制台”。
|
|||
|
|
|
|||
|
|
`XCEngine::Debug::Logger` 采用中心分发 + sink 的结构,就是为了把这两类职责拆开:
|
|||
|
|
|
|||
|
|
- 业务层负责说明“发生了什么”。
|
|||
|
|
- `Logger` 负责说明“这条记录如何统一过滤和分发”。
|
|||
|
|
- sink 负责说明“最终落到哪里”。
|
|||
|
|
|
|||
|
|
## 为什么不是 everywhere `printf`
|
|||
|
|
|
|||
|
|
直接 `printf` 的问题是:
|
|||
|
|
|
|||
|
|
- 输出目的地写死在调用点里。
|
|||
|
|
- 无法统一按级别和分类过滤。
|
|||
|
|
- 很难同时输出到控制台、文件和编辑器 UI。
|
|||
|
|
- 工具层很难在不修改业务代码的前提下插入额外日志通道。
|
|||
|
|
|
|||
|
|
sink 模式的好处是,业务代码只需要依赖:
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
Logger::Get().Info(LogCategory::General, "...");
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
至于这条日志最终是进入控制台、文件还是编辑器面板,可以由启动阶段自由拼装。
|
|||
|
|
|
|||
|
|
## 当前架构中的角色分工
|
|||
|
|
|
|||
|
|
### `Logger`
|
|||
|
|
|
|||
|
|
- 维护全局最小级别。
|
|||
|
|
- 维护 category 开关。
|
|||
|
|
- 生成 `LogEntry`。
|
|||
|
|
- 把日志广播给已注册 sink。
|
|||
|
|
|
|||
|
|
### `ILogSink`
|
|||
|
|
|
|||
|
|
- 定义输出目标的最小契约。
|
|||
|
|
- 允许扩展出控制台、文件、内存缓存或远程诊断通道。
|
|||
|
|
|
|||
|
|
### `ConsoleLogSink`
|
|||
|
|
|
|||
|
|
- 面向“立即可见”的输出。
|
|||
|
|
- 最适合测试程序、命令行工具和开发期启动日志。
|
|||
|
|
|
|||
|
|
### `FileLogSink`
|
|||
|
|
|
|||
|
|
- 面向“事后复盘”的输出。
|
|||
|
|
- 最适合编辑器、长时间运行工具和自动化测试。
|
|||
|
|
|
|||
|
|
## 推荐接线方式
|
|||
|
|
|
|||
|
|
启动阶段推荐做三件事:
|
|||
|
|
|
|||
|
|
1. 显式 `Logger::Get().Initialize()`。
|
|||
|
|
2. 注册至少一个可见 sink,例如 `ConsoleLogSink`。
|
|||
|
|
3. 如果程序有会话级价值,再追加 `FileLogSink`。
|
|||
|
|
|
|||
|
|
示例:
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
using namespace XCEngine::Debug;
|
|||
|
|
|
|||
|
|
Logger::Get().Initialize();
|
|||
|
|
Logger::Get().AddSink(std::make_unique<ConsoleLogSink>());
|
|||
|
|
Logger::Get().AddSink(std::make_unique<FileLogSink>("editor.log"));
|
|||
|
|
Logger::Get().SetMinimumLevel(LogLevel::Debug);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
这也是当前编辑器和多个集成测试采用的基本模式。
|
|||
|
|
|
|||
|
|
## 和 Unity 的对照
|
|||
|
|
|
|||
|
|
可以把这套结构理解为:
|
|||
|
|
|
|||
|
|
- 比 Unity Console 更底层,因为它先解决日志分发,再决定是否显示在 UI。
|
|||
|
|
- 比单纯的 `Debug.Log` 更偏引擎层,因为它允许接多个输出目标。
|
|||
|
|
|
|||
|
|
但它又明显没有 Unity 那么完整:
|
|||
|
|
|
|||
|
|
- 还没有统一的上下文对象、堆栈追踪或富文本格式化。
|
|||
|
|
- `XE_ASSERT` 当前也只会记一条 `Fatal` 日志,并不会中断执行。
|
|||
|
|
|
|||
|
|
## 当前版本最重要的限制
|
|||
|
|
|
|||
|
|
- `Logger::SetMinimumLevel` 和 `SetCategoryEnabled` 当前没有加锁,最好在初始化阶段配置。
|
|||
|
|
- `Shutdown` 不应和其它线程的 `Log` 并发执行。
|
|||
|
|
- 便捷方法 `Info`、`Warning` 等默认不带源码位置信息;需要文件和行号时,应优先使用 `XE_LOG`。
|
|||
|
|
- `FileLogSink` 会每次写入后立刻刷新文件,诊断友好,但吞吐量不是最优。
|
|||
|
|
|
|||
|
|
## 推荐实践
|
|||
|
|
|
|||
|
|
- 引擎启动、设备创建、资源加载失败这类关键路径,用 `Info` / `Warning` / `Error` 做结构化记录。
|
|||
|
|
- 高频噪声日志放到 `Verbose` 或 `Debug`,并通过最小级别统一控制。
|
|||
|
|
- category 要按子系统区分,不要把所有信息都塞到 `General`。
|
|||
|
|
- 如果未来需要编辑器控制台过滤、远程日志或测试断言统计,优先加 sink,不要让业务代码直接分叉输出逻辑。
|
|||
|
|
|
|||
|
|
## 相关 API
|
|||
|
|
|
|||
|
|
- [Debug](../../XCEngine/Debug/Debug.md)
|
|||
|
|
- [Logger](../../XCEngine/Debug/Logger/Logger.md)
|
|||
|
|
- [ILogSink](../../XCEngine/Debug/ILogSink/ILogSink.md)
|
|||
|
|
- [ConsoleLogSink](../../XCEngine/Debug/ConsoleLogSink/ConsoleLogSink.md)
|
|||
|
|
- [FileLogSink](../../XCEngine/Debug/FileLogSink/FileLogSink.md)
|