Zephyr RTOS 日志 5 分钟配好,调级免重编译
【免费下载链接】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 日志都取不出来。你重启了一遍又一遍,换板、换晶振,只能一个原因一个原因地猜。嵌入式调试的卡点往往不是代码没 bug,而是出 bug 时现场没有任何记录。Zephyr RTOS 的日志与追踪机制就是为这件事设计的:让系统里每个关键动作都留下痕迹,事后可以把事故时间线一步步回放。
梳理一条日志的完整旅程:级别、过滤、后端
你在代码里写一条 LOG_INF(...),它不会直接跑到串口上。第一道关是级别:日志子系统定义了从 EMERG(紧急)到 DBG(调试)共 7 级,高于当前门槛的消息直接丢弃。第二道关是模块过滤:LOG_MODULE_REGISTER 时就要声明本模块想要的级别,够不着级别的消息在编译期就被裁掉,不占一个字。两道关都过了,消息打包进消息池。默认走延迟模式:调用点立刻返回,字符串格式化这类耗时操作交给专门的日志处理线程,再交给后端输出。后端可以不止一个,各自独立过滤。
图中绿色方框是日志前端,负责把各执行域产生的消息收上来,后端做最终输出,出口可以是串口、USB 或主机端工具。完整选项清单见日志官方文档。
配置最短日志链路:开启、注册、打出第一条
在配置文件中打开全局开关,指定默认级别和输出通道:
# prj.conf CONFIG_LOG=y CONFIG_LOG_DEFAULT_LEVEL=5 # 5=INFO,输出 INFO 及以上 CONFIG_LOG_BACKEND_UART=y # 输出到控制台然后在代码里注册模块,打出第一条日志:
#include <zephyr/logging/log.h> LOG_MODULE_REGISTER(sensor, LOG_LEVEL_INF); static void read_once(void) { int raw = 42; LOG_INF("读取完成,原始值 %d", raw); }模块注册、按实例打日志、自定义前端的完整写法,在logger 示例里都有,多文件工程可以直接照抄它的 LOG_MODULE_DECLARE 用法。
对比三种输出通道,挑一个适合设备的
| 后端 | 典型场景 | 关键 Kconfig | 主要代价 |
|---|---|---|---|
| UART 控制台 | 开发台有调试探头 | CONFIG_LOG_BACKEND_UART | 格式化与串口带宽占真实时间 |
| 蓝牙 | 量产设备无串口、需远程回传 | CONFIG_LOG_BACKEND_BLE | 依赖蓝牙协议栈,内存预算变高 |
| RAM | 抓崩溃现场、事后回读 | CONFIG_LOG_BACKEND_RAM + CONFIG_LOG_BACKEND_RAM_BUFFER_SIZE | 缓冲有限,掉电即丢失 |
选型逻辑其实很简单:开发阶段首选 UART,反馈回路最短;量产设备没有串口就用蓝牙回传;要留住崩溃前的记录,就再挂一个 RAM 后端——设备即使死机,环形缓冲里还留着最后几十条。
调整日志级别与模块过滤,不用重编译
编译期级别是底线,设备跑起来之后还要随时调音量。运行时过滤的入口是 log_filter_set(),第二参数是模块名(传 NULL 表示全部),第三参数是目标级别,前提是打开 CONFIG_LOG_RUNTIME_FILTERING=y。
log_filter_set(NULL, "sensor", LOG_LEVEL_DBG); /* 单开 sensor 到 DEBUG */ log_filter_set(NULL, NULL, LOG_LEVEL_NONE); /* 全体静音 */两个细节值得注意。一是运行时过滤按后端独立生效:关掉 UART 通道不影响 RAM 通道,串口侧可以静音降噪,RAM 侧继续留存现场。二是想弄清过滤如何生效,可以看 subsys/logging/ 源码:log_mgmt.c 管理过滤状态,backends/ 下是各通道的输出实现。
用日志定位驱动读失败与崩溃前空白
I2C 传感器:读出来永远是错误
现象:读温度传感器总是返回 -EIO,换板也一样,最初像硬件问题。
怀疑:接线、引脚复用被排除后,嫌疑落在驱动里的寄存器地址。
定位动作:打开驱动读取路径的 LOG_DBG,让每一步都打印返回值和总线上读回的原始字节,日志直接指向写出的地址字节。
结论:7 位与 8 位寻址弄混了,地址高位多写了一位。改完再看同一条 LOG_DBG,从报错变成正常值,修复当场确认。
死机前没有任何记录
现象:设备偶发死机,死机前串口一片安静,只有看门狗复位,线索为零。
怀疑:要么高优先级线程卡死,要么日志缓冲满被丢——消息产生了,但没出去。
定位动作:保留延迟模式并加挂 RAM 后端,同时打开 CONFIG_LOG_MODE_OVERFLOW,让缓冲满时留下"有消息被丢弃"的标记。下次死机后从 shell 回读 RAM 环形缓冲,拿到最后两条:工作队列处理函数卡在等信号量上,日志恰好丢在等待点。
结论:卡死源于另一个线程持有资源不释放。修复后 RAM 后端在产线继续发挥作用——崩溃现场不再是黑盒。
如果问题更接近"哪个函数太慢",可以转向追踪子系统,用主机端工具把执行流程调出来,直接看调用时间线:
核对四个高频坑位再上线
- ⚠️ 延迟模式缓冲满就丢:默认消息池不大,设备一忙日志悄悄消失。加大 CONFIG_LOG_BUFFER_SIZE,或打开 CONFIG_LOG_MODE_OVERFLOW 留下丢弃标记。
- ⚠️ 编译期级别只能提不能降:模块注册时的级别是上限,全局覆盖只会更松不会更紧。想砍掉某模块的调试输出,在注册处把级别设准。
- ⚠️ 延迟处理线程也吃内存和栈:多数配置无感,但紧张的项目要检查 CONFIG_LOG_PROCESS_STACK_SIZE,别让日志线程自己先崩。
- ⚠️ 后端代价不对称:蓝牙回传拖入整个协议栈,RAM 后端掉电即失,UART 占真实带宽。先按最坏情况估算日志量,再选通道。
日志回答"当时发生了什么",追踪回答"当时跑得多快",两者配合,多数嵌入式调试疑难都能变成可读的时间线。先把上面三个 Kconfig 选项配上,让第一条日志出现在屏幕上,再按需打开运行时过滤、RAM 后端和限频输出。
- 核心源码:subsys/logging/,log_core.c 管消息流转,log_mgmt.c 管过滤,backends/ 是各输出通道
- 示例目录:samples/subsys/logging/,蓝牙回传、字典压缩、多域等完整实现
- 官方文档:doc/services/logging/index.rst,Kconfig 到 API 细节齐全
你量产设备用的是蓝牙回传还是 RAM 留存?欢迎在评论区聊聊踩过的坑。
【免费下载链接】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),仅供参考