日志与断言体系
自动关联目录:日志与断言体系
1. 定位
日志与断言是"留给半年后自己的诊断信息"。写的时候便宜,缺的时候极贵。
一句话定位:check 与 ensure 的选择是这套体系里唯一需要认真想的事——选错一次就是线上崩溃或线上静默。
2. 日志
UE_LOG(LogTemp, Warning, TEXT("Health = %f"), Health);| 宏 | 说明 |
|---|---|
UE_LOG(Category, Verbosity, Format, ...) | 分级日志 |
UE_LOG 的 Verbosity | Fatal / Error / Warning / Display / Log / Verbose / VeryVerbose |
自定义分类:
DECLARE_LOG_CATEGORY_EXTERN(LogMyGame, Log, All); // .h
DEFINE_LOG_CATEGORY(LogMyGame); // .cpp用独立分类而不是 LogTemp——可以按分类过滤,调试效率差很多。
| 要点 | 说明 |
|---|---|
| 分类按系统划分 | LogAbilitySystem、LogNet、项目自有分类 |
| 用 Verbose 放细节 | 默认不开,需要时 Log LogMyGame Verbose |
| 热路径别写日志 | 字符串格式化本身有成本 |
| 日志要能定位 | 带上关键 ID/状态,不要只写"出错了" |
3. 断言:三个宏的选择
| 宏 | 行为 | 适用 |
|---|---|---|
check(expr) | 失败即中断(Shipping 里被剔除) | 绝不该发生的前提 |
verify(expr) | 与 check 类似,但表达式始终求值 | 表达式有副作用时 |
ensure(expr) | 失败不中断,首次触发报错并生成调用栈 | 可恢复的异常 |
ensureAlways(expr) | 每次都上报 | 需要统计频率时 |
checkf / ensureMsgf | 带格式化信息 | 需要上下文时 |
核心判断标准:这个条件不成立时,继续运行会不会造成更严重的问题(数据损坏、安全漏洞)?
- 会 →
check - 不会 →
ensure
最常见的误用:用 check 处理业务上的异常(如"找不到某个配置"),结果线上一个边界情况就让整个进程崩掉。
4. 崩溃与栈的关系
ensure 的价值在于保留现场:它不中断,只上报并附带调用栈,所以你能拿到当时的完整状态(见 崩溃与 Dump)。
用 ensure 换来的诊断信息,往往比 check 换来的一次崩溃更有价值。
5. 代价与权衡
| 设计 | 收益 | 代价 | 什么时候不该用 |
|---|---|---|---|
| 独立日志分类 | 可过滤 | 要声明 | 临时调试可用 LogTemp |
| Verbose 级别 | 细节不污染 | 需要时打开 | 关键错误不该只放 Verbose |
check | 尽早暴露 | 一次异常就崩 | 可恢复的异常不该用 |
ensure | 保留现场 | 错误被放过,可能继续错下去 | 致命前提仍应 check |
| 热路径日志 | 便于排查 | 有成本 | 每帧调用的地方不要写 |
| 上线保留 ensure | 线上能拿到诊断 | 少量开销 | 极高频路径要评估 |
6. 踩坑与排查
| 坑 | 现象 | 怎么验证 |
|---|---|---|
用 check 处理可恢复错误 | 线上崩 | 判断"继续运行会不会更糟" |
全用 LogTemp | 无法过滤 | 建分类 |
| 热路径写日志 | 性能掉 | 检查每帧调用处 |
| 只写"出错了" | 无法定位 | 带关键 ID 与状态 |
| Debug 绘制留在正式代码 | Shipping 无效果但成本还在 | 见 Debug |
| 断言信息没有上下文 | 看不出为什么 | 用 checkf / ensureMsgf |
| 待机时日志刷屏 | 淹没真正的问题 | 分级 + 采样 |
7. 排查顺序
1. 逻辑不对?→ 先加日志(分类 + 关键状态)
2. 偶发问题?→ ensure 保留现场 + [Visual Logger](/cb2e1433.html) 回放
3. 崩溃?→ [崩溃与 Dump](/939ee7a1.html)
4. 日志太多?→ 调整级别与采样
5. 修完 → 保留一条断言防回归参考
许可协议:CC BY
作者:Davids
本文链接:https://hustjjd.github.io/c66a6d85.html
更新于:2026年10月10日