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的日志等级是分层的:编译时等级运行时等级。这个参数决定了编译时能保留哪些等级的日志。

我的习惯:开发阶段,我通常把等级设为LOG_LEVEL_DBG,确保所有调试信息都能编译进去。发布前再改成LOG_LEVEL_INF或LOG_LEVEL_ERR,减少固件体积。

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,效率高得多。

我曾经踩过的坑:在中断上下文里直接调用LOG_ERR。Zephyr的日志后端(比如UART)可能会休眠,导致死锁。解决办法是用LOG_ERR的异步版本,或者把日志丢到工作队列里处理。

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_INFLOG_HEXDUMP_DBGLOG_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那么简单。