日志与断言体系

自动关联目录:日志与断言体系

1. 定位

日志与断言是"留给半年后自己的诊断信息"。写的时候便宜,缺的时候极贵。

一句话定位:check 与 ensure 的选择是这套体系里唯一需要认真想的事——选错一次就是线上崩溃或线上静默。

2. 日志

UE_LOG(LogTemp, Warning, TEXT("Health = %f"), Health);
宏说明
UE_LOG(Category, Verbosity, Format, ...)分级日志
UE_LOG 的 VerbosityFatal / 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. 修完 → 保留一条断言防回归

参考