Zephyr RTOS 日志系统一次讲清
【免费下载链接】zephyrPrimary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.项目地址: https://gitcode.com/GitHub_Trending/ze/zephyr
本文只讲 Zephyr RTOS 日志怎么用。读完后,你能用 3 行 Kconfig 打开日志,按场景挑选合适的嵌入式日志后端,并在设备上不重烧固件就调整日志级别与模块过滤;同时掌握传感器驱动错误定位和 Zephyr 崩溃日志留存的写法。
日志系统在 Zephyr 中的位置
日志子系统是 Zephyr 里一个独立的子系统,而不是一组打印函数。它的实现集中在 subsys/logging/ 目录,整体走"前端 → 缓存 → 后端"的流水线:前端只记录消息元数据(级别、模块、时间戳),消息先进入环形缓存,后端负责格式化与输出。
这个结构带来三个实际特性:
- 模块化:每个源文件通过 LOG_MODULE_REGISTER 注册成独立模块,可单独设定编译期级别;
- 多后端:UART、蓝牙 LE、文件系统等后端可插拔,靠 Kconfig 切换;
- 运行时可控:系统跑起来之后,级别与过滤仍然可以在线调整。
追踪数据也走同一条管线,抓出来后可以用第三方工具做时间线分析:
最小可用配置
打开日志只需要 3 行配置,就能得到一条完整的输出链路:
CONFIG_LOG=y CONFIG_LOG_DEFAULT_LEVEL=3 CONFIG_LOG_BACKEND_UART=y#include <zephyr/logging/log.h> LOG_MODULE_REGISTER(my_module); int main(void) { LOG_INF("boot ok, temp=%d", 25); LOG_DBG("detail: %s", "raw data"); return 0; }每条日志对应 0~6 共 7 个级别:0=EMERG,1=ALERT,2=CRIT,3=ERR,4=WARNING,5=INFO,6=DEBUG。注意 CONFIG_LOG_DEFAULT_LEVEL 取 0~4(默认 3),它是一把"粗刻度":3 表示编译期默认放行到 ERR 档,4 才会放行到 DEBUG 档。
按场景选输出后端
后端只是输出阶段的可替换实现,选哪个取决于设备最终摆在什么位置。
| 输出通道 | 关键配置项 | 适用场景 |
|---|---|---|
| 控制台(UART 串口) | CONFIG_LOG_BACKEND_UART=y | 开发板带串口,开发阶段直接看输出 |
| 蓝牙 LE | CONFIG_LOG_BACKEND_BLE=y(依赖 BT) | 设备没有物理串口,用手机端 NUS 兼容应用接收 |
| 文件系统(持久存储) | CONFIG_BACKEND_FS=y对应的CONFIG_LOG_BACKEND_FS=y | 设备断电或现场死机,事后由上位机从存储分区回读 |
开发期留控制台最省事;设备部署到远处后换成 BLE 或文件系统后端,就不用每次拆机看串口。完整可编译的例子在 samples/subsys/logging/ 里,BLE 后端附带手机接收的说明。
不重新烧录,也能调日志
打开 CONFIG_LOG_RUNTIME_FILTERING 后,级别和按模块的过滤都能在系统运行中修改,无需重新编译。
#include <zephyr/logging/log_ctrl.h> /* 调高某模块级别:src_id 是模块的数字 ID(shell 可查) */ log_filter_set(NULL, 0, 42, LOG_LEVEL_DBG); /* 全局静默:级别设为 LOG_LEVEL_NONE */ log_filter_set(NULL, 0, 42, LOG_LEVEL_NONE);启用 shell 后,也可以在串口控制台直接用log filter set这类命令调级,连代码都不用动。
两个性能参数值得记住:
- CONFIG_LOG_PROCESS_THREAD(默认开):由一个专用线程负责格式化输出,日志调用点只往缓存里写,不在你的代码路径上阻塞;
- CONFIG_LOG_PROCESS_TRIGGER_THRESHOLD(默认 10):缓存里攒够 10 条才唤醒处理线程批量输出,减少上下文切换次数。
🔍 两个高频调试场景
场景一:传感器驱动错误定位。I2C 读失败时把返回值和寄存器地址打进日志,一条消息就能区分总线问题与器件问题:
static int sensor_read(struct device *dev, int *val) { int ret = i2c_reg_read_byte(dev, 0x20); if (ret < 0) { LOG_ERR("i2c read fail: reg=0x20, err=%d", ret); return ret; } *val = decode(ret); LOG_DBG("raw=0x%02x, val=%d", ret, *val); return 0; }要点:LOG_ERR 只带错误码和关键上下文,LOG_DBG 留给中间量,发布时关 DEBUG 档即可。samples/sensor/ms5837/ 里有现成的传感器日志用法可对照。
场景二:崩溃前留下最后一条信息。致命错误处理函数接管后,先强制把缓存里没输出的日志冲刷出去:
void my_fatal_handler(const struct z_arch_esf_t *esf) { log_panic(); /* 冲刷所有已缓存日志 */ LOG_ERR("crash: pc=0x%llx", (unsigned long long)esf->pc); sys_reboot(SYS_REBOOT_COLD); }要点:log_panic() 会立即排空缓冲区,系统挂死前发生的最后几条日志,重新上电后依然能看到。
⚠️ 常见坑与取舍
- 级别选错:编译期默认级别放到 DEBUG,发布固件也会带出全部调试信息,RAM 和带宽都白烧。发布版降到 ERR/WRN 档,调试时再临时调高。
- 日志被丢:默认延迟模式的消息缓存在 LOG_BUFFER_SIZE(默认 1024 字节),写满后旧消息被丢弃;关键路径要么调大它,要么改用同步模式。
- 日志拖慢主循环:同步模式(CONFIG_LOG_MODE_IMMEDIATE)的格式化与输出发生在调用上下文,主循环甚至高优先级中断都会被卡住;多核多线程系统建议保持默认延迟模式加处理线程。
- BLE 后端有前置条件:依赖蓝牙子系统,且要求处理线程栈不小于 2048 字节,不满足时配置解析直接报错。
- 刷屏淹没关键信息:循环里每轮都打日志会把通道占满,用 LOG_*_RATELIMIT 宏按调用点限流,默认间隔 5000 ms,可用 CONFIG_LOG_RATELIMIT_INTERVAL_MS 调整。
延伸阅读
- 核心实现:subsys/logging/log_core.c
- 公共 API 与宏定义:include/zephyr/logging/log.h
- 官方示例(控制台、BLE、多域日志):samples/subsys/logging/
- 追踪子系统文档:doc/services/tracing/
【免费下载链接】zephyrPrimary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.项目地址: https://gitcode.com/GitHub_Trending/ze/zephyr
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考