3. 日志API详解:LOG_MODULE_REGISTER、LOG_INF、LOG_WRN、LOG_ERR、LOG_DBG、LOG_HEXDUMP
好,咱们直接进入正题。日志API是Zephyr调试的“嘴”和“耳朵”,你总得告诉系统你想看什么,以及怎么看。这一章,我把最核心的几个宏掰开揉碎讲给你听。
3.1 注册模块:LOG_MODULE_REGISTER
每个使用日志的源文件,第一件事就是注册。说白了,就是告诉Zephyr:“嘿,我这儿有个模块,叫xxx,以后它的日志归我管。”
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(my_module, CONFIG_MY_MODULE_LOG_LEVEL);
这里有个细节——第二个参数是日志等级。你可能会想:“我直接写个LOG_LEVEL_DBG不就行了?”嗯,我刚开始也这么干,结果发现编译出来的固件日志全关了。为啥?因为Zephyr的日志等级是分层的:编译时等级和运行时等级。这个参数决定了编译时能保留哪些等级的日志。
3.2 日志输出四兄弟:LOG_INF、LOG_WRN、LOG_ERR、LOG_DBG
这四个宏,用法几乎一样,区别在于严重程度。我列个表,你一看就明白:
| 宏 | 等级 | 典型场景 |
|---|---|---|
| LOG_ERR | 错误 | 系统崩溃、外设初始化失败 |
| LOG_WRN | 警告 | 内存不足、时序超时 |
| LOG_INF | 信息 | 启动完成、连接成功 |
| LOG_DBG | 调试 | 变量值、函数调用流程 |
用法很简单,跟printf差不多:
int ret = sensor_read(&data);
if (ret < 0) {
LOG_ERR("传感器读取失败,错误码:%d", ret);
return ret;
}
LOG_INF("传感器数据:温度=%.2f°C,湿度=%.1f%%", data.temp, data.humidity);
LOG_DBG("当前缓冲区使用量:%d字节", buf_used);
你想想看,如果所有日志都用LOG_INF,那错误信息就淹没在普通信息里了。我建议你养成习惯:错误用ERR,流程用INF,细节用DBG。这样线上排查问题时,直接过滤出ERR和WRN,效率高得多。
3.3 二进制数据打印:LOG_HEXDUMP
这个宏,说实话,是我调试通信协议时的救命稻草。它能以十六进制和ASCII双栏形式打印一段内存数据,特别适合看报文、寄存器值、或者内存布局。
uint8_t packet[] = {0xAA, 0x55, 0x01, 0x02, 0x03, 0x04, 0xEF};
LOG_HEXDUMP_INF(packet, sizeof(packet), "收到数据包");
输出效果类似这样:
[00:00:00.123] <inf> my_module: 收到数据包
[00:00:00.123] AA 55 01 02 03 04 EF .U.....
注意,LOG_HEXDUMP也有等级后缀:LOG_HEXDUMP_INF、LOG_HEXDUMP_DBG、LOG_HEXDUMP_ERR。我个人习惯用LOG_HEXDUMP_DBG,因为报文数据通常只在调试时需要,正式发布时关掉。
if (len < 256) { LOG_HEXDUMP_DBG(buf, len, "小数据包"); }
3.4 日志等级的动态控制
Zephyr支持在运行时动态调整模块的日志等级。你可以在shell里敲:
uart:~$ log enable my_module 4
这里的4对应LOG_LEVEL_DBG。这样就不用重新编译固件了。我在现场调试时经常用这招——先让系统跑着,发现问题了再动态打开详细日志,定位完再关掉。
log_backend_uart,记得在prj.conf里打开CONFIG_LOG_RUNTIME_FILTERING=y。否则你敲了命令也没反应。
3.5 总结一下
这一章的核心就三件事:
- 注册模块——每个文件只做一次,别漏了
- 选对等级——ERR、WRN、INF、DBG,各司其职
- 善用HEXDUMP——二进制数据一目了然
嗯,这些API看着简单,但用好了能让调试效率翻倍。下一章我会讲日志后端的配置,包括怎么把日志输出到文件、网络或者RTT。到时候你就知道,Zephyr的日志系统远不止printk那么简单。