12、日志与调试:日志级别设计、日志格式规范、条件编译调试代码、日志性能影响
日志这东西,说简单也简单,就是printf嘛。但说复杂,它真能决定一个嵌入式项目是死是活。
我见过太多项目,上线后出问题,连个日志都没有,全靠工程师拿示波器戳。还有的项目,日志倒是打了一堆,但全是「error 1」、「error 2」这种,看了等于没看。
今天咱们就把日志这件事聊透。从级别设计,到格式规范,再到条件编译和性能影响,一步到位。
12.1 日志级别设计:别让日志变成噪音
日志级别,说白了就是给信息分个三六九等。为什么需要这个?你想想看,一个跑着的系统,每秒可能产生上千条日志。如果全打出来,你根本找不到重点。
我个人习惯用这五个级别,够用且不啰嗦:
| 级别 | 名称 | 含义 | 典型场景 |
|---|---|---|---|
| 0 | FATAL | 致命错误 | 系统即将崩溃,必须立即处理 |
| 1 | ERROR | 错误 | 功能异常,但系统还能跑 |
| 2 | WARN | 警告 | 潜在风险,需要关注 |
| 3 | INFO | 信息 | 关键流程、状态变化 |
| 4 | DEBUG | 调试 | 开发阶段用的详细输出 |
我曾经在一个电机控制项目里,把所有日志都打开了。结果电机一转,串口被日志塞满,控制指令都发不出去了。嗯,从那以后,我学会了「日志分级」这个基本功。
12.2 日志格式规范:让日志会说话
光有级别还不够,格式也得统一。我见过最离谱的日志,就一行字:「温度过高」。谁打的?什么时候打的?哪个模块?一概不知。
我建议的日志格式长这样:
[时间戳] [级别] [模块名] [文件名:行号] 消息内容
举个例子:
[2025-03-15 14:23:45.123] [ERROR] [BMS] [bms_temp.c:128] 电池温度异常:85.3°C,超过阈值80.0°C
这条日志一看就明白:
- 什么时候发生的(精确到毫秒)
- 什么级别(ERROR)
- 哪个模块(BMS,电池管理系统)
- 哪个文件哪一行(方便定位代码)
- 具体什么问题(温度超标,还有具体数值)
这才是能用的日志。不是给人看的,是给「查问题的人」看的。
12.3 条件编译调试代码:把调试代码和正式代码分开
嵌入式开发里,调试代码和正式代码混在一起是大忌。你总不能在发布版本里还留着printf吧?
我的做法是用条件编译,干净利落:
#define LOG_LEVEL 3 // 0=FATAL, 1=ERROR, 2=WARN, 3=INFO, 4=DEBUG
#define LOG_FATAL(fmt, ...) \
do { if (LOG_LEVEL >= 0) printf("[FATAL] " fmt "\r\n", ##__VA_ARGS__); } while(0)
#define LOG_ERROR(fmt, ...) \
do { if (LOG_LEVEL >= 1) printf("[ERROR] " fmt "\r\n", ##__VA_ARGS__); } while(0)
#define LOG_WARN(fmt, ...) \
do { if (LOG_LEVEL >= 2) printf("[WARN] " fmt "\r\n", ##__VA_ARGS__); } while(0)
#define LOG_INFO(fmt, ...) \
do { if (LOG_LEVEL >= 3) printf("[INFO] " fmt "\r\n", ##__VA_ARGS__); } while(0)
#define LOG_DEBUG(fmt, ...) \
do { if (LOG_LEVEL >= 4) printf("[DEBUG] " fmt "\r\n", ##__VA_ARGS__); } while(0)
使用时就像这样:
void adc_read(void) {
uint32_t value = HAL_ADC_GetValue(&hadc1);
LOG_DEBUG("ADC原始值: %lu", value);
if (value > 4095) {
LOG_ERROR("ADC值越界: %lu", value);
}
LOG_INFO("ADC转换完成,值: %lu", value);
}
发布版本时,把 LOG_LEVEL 改成 2 或 1,DEBUG 和 INFO 级别的日志就自动消失了。编译器会优化掉这些代码,不会留下任何痕迹。
12.4 日志性能影响:别让日志拖垮系统
日志不是免费的。每打一条日志,CPU 就要执行一次格式化、一次字符串拷贝、一次外设写入。在实时性要求高的系统里,这可能是致命的。
我遇到过最夸张的情况:一个中断服务函数里打了 DEBUG 日志,结果中断执行时间从 5us 变成了 200us。整个系统的实时性全毁了。
所以,关于日志性能,我有几条铁律:
- 中断里不要打日志 —— 除非是 FATAL 级别,而且你准备让系统停下来
- 高频循环里慎用 INFO 级别 —— 1kHz 的控制循环,每圈打一条日志,串口根本扛不住
- 用异步日志 —— 把日志丢到队列里,让后台任务慢慢写,别阻塞主流程
- 字符串格式化很贵 —— 能用整数就别用浮点,能用 %d 就别用 %s
下面这张图,是我自己总结的日志性能影响评估:
核心原则: 日志是调试工具,不是业务逻辑。别让日志反过来控制你的系统行为。如果关掉日志系统就跑不动了,说明你的代码本身就有问题。
最后说一句,日志系统设计好了,调试效率能翻倍。设计不好,那就是给自己挖坑。我这些年踩过的坑,总结下来就一句话:日志要「够用就好」,别贪多。
公众号:蓝海资料掘金营,微信deep3321