日志模块配置:Kconfig配置、日志模块使能、日志级别设置、日志输出格式配置
好,咱们今天聊聊日志模块的配置。说实话,很多开发者刚开始接触Zephyr时,觉得日志不就是printf加个开关嘛?嗯,其实远没那么简单。Zephyr的日志系统设计得非常灵活,但也正因为灵活,配置起来有不少门道。我最早接手一个项目时,就因为日志配置不当,导致调试信息要么太多刷屏,要么太少啥也看不到,折腾了好几天才摸清楚套路。
Kconfig配置:日志系统的总开关
Zephyr的配置体系,核心就是Kconfig。日志模块也不例外。你想想看,整个日志系统要不要编译进来,全靠几个关键的Kconfig选项控制。
最基本的,你得先打开这个:
CONFIG_LOG=y
这行一加,日志框架就启用了。但光有这个还不够,你还需要决定日志后端。什么是后端?说白了就是日志往哪儿输出。最常用的就是串口:
CONFIG_LOG_BACKEND_UART=y
我个人习惯,在项目初期会把所有后端相关的选项都打开,方便调试。但到了产品阶段,一定要精简,只保留必要的后端。我曾经在一个资源紧张的MCU上,因为开了太多日志后端,导致RAM爆了,程序直接跑飞。嗯,这个坑我踩过。
还有一些常用的Kconfig选项,我列个表给你看:
| Kconfig选项 | 作用 | 建议 |
|---|---|---|
| CONFIG_LOG | 启用日志系统 | 必选 |
| CONFIG_LOG_BACKEND_UART | 串口后端 | 调试时必选 |
| CONFIG_LOG_BACKEND_RTT | SEGGER RTT后端 | 高性能调试推荐 |
| CONFIG_LOG_BACKEND_NET | 网络后端 | 远程调试用 |
| CONFIG_LOG_MODE_IMMEDIATE | 立即输出模式 | 调试时用,性能差 |
| CONFIG_LOG_MODE_DEFERRED | 延迟输出模式 | 生产环境推荐 |
日志模块使能:模块级别的精细控制
Kconfig打开了全局开关,但每个模块要不要打日志,还得单独使能。Zephyr的做法是,每个模块都有自己的日志使能宏。
比如你有一个蓝牙模块,想让它输出日志:
#define LOG_MODULE_NAME bt
#define LOG_LEVEL LOG_LEVEL_DBG
#include <logging/log.h>
LOG_MODULE_REGISTER(bt);
这里的关键是LOG_MODULE_REGISTER。你注册了,这个模块的日志才生效。没注册?那这个模块里所有的LOG_xxx调用都会被编译器优化掉,一点代码都不会生成。
为什么会这样?因为Zephyr的日志系统是编译期决定的。说白了,你在代码里写的日志调用,最终会不会变成实际的代码,完全取决于你有没有注册模块、日志级别设了多少。这一点和很多其他RTOS不一样,它们往往是运行时判断,会浪费CPU周期。
我记得有一次,一个同事抱怨说他的日志死活不输出,代码里明明写了LOG_INF。我一看,他忘了加LOG_MODULE_REGISTER。嗯,这种小错误,新手很容易犯。
日志级别设置:从DEBUG到ERROR的取舍
日志级别,这是最需要你花心思的地方。Zephyr定义了四个级别:
- LOG_LEVEL_ERR (0) — 只有错误信息
- LOG_LEVEL_WRN (1) — 错误+警告
- LOG_LEVEL_INF (2) — 错误+警告+信息
- LOG_LEVEL_DBG (3) — 全部输出,包括调试信息
你可以在模块级别设置:
#define LOG_LEVEL LOG_LEVEL_DBG
也可以在Kconfig层面全局设置:
CONFIG_LOG_DEFAULT_LEVEL=3
我个人建议,开发阶段每个模块都设成LOG_LEVEL_DBG,反正MCU性能够用。但到了集成测试阶段,就要开始收敛了。比如蓝牙协议栈这种高频调用的模块,设成LOG_LEVEL_INF就够了,否则日志刷屏刷到你怀疑人生。
日志输出格式配置:让日志更好读
日志内容有了,但怎么呈现也很重要。Zephyr允许你自定义输出格式。默认格式是这样的:
[00:00:01.234] <inf> bt: Bluetooth initialized
包含时间戳、日志级别、模块名、消息。但你可以通过Kconfig调整:
CONFIG_LOG_TIMESTAMP_64BIT=y
CONFIG_LOG_FUNC_NAME=y
CONFIG_LOG_BACKEND_UART_SHOW_COLOR=y
我个人特别喜欢打开颜色输出。不同级别的日志用不同颜色显示,一眼就能看出哪些是错误、哪些是警告。尤其是在终端里看大量日志时,颜色能帮你快速定位问题。
还有一个很实用的配置是显示函数名:
CONFIG_LOG_FUNC_NAME=y
打开后,日志会带上调用函数的名称。这在追踪代码执行路径时特别有用。不过要注意,这会增加代码体积,生产环境建议关掉。
嗯,这里还要提一个坑。如果你用了RTT后端,颜色输出可能不支持。我当初在J-Link RTT Viewer里折腾了半天颜色,最后发现它根本不解析ANSI转义序列。所以,选后端的时候,也要考虑它的显示能力。
实战建议:一套我常用的配置模板
说了这么多,给你一套我项目里常用的配置吧。调试阶段用这个:
CONFIG_LOG=y
CONFIG_LOG_BACKEND_UART=y
CONFIG_LOG_MODE_IMMEDIATE=y
CONFIG_LOG_DEFAULT_LEVEL=3
CONFIG_LOG_FUNC_NAME=y
CONFIG_LOG_BACKEND_UART_SHOW_COLOR=y
发布阶段改成这样:
CONFIG_LOG=y
CONFIG_LOG_BACKEND_UART=y
CONFIG_LOG_MODE_DEFERRED=y
CONFIG_LOG_DEFAULT_LEVEL=1
# CONFIG_LOG_FUNC_NAME is not set
# CONFIG_LOG_BACKEND_UART_SHOW_COLOR is not set
你看,差别还是挺大的。调试阶段追求信息量和实时性,发布阶段追求性能和代码体积。你想想看,产品卖出去之后,用户可不想看到满屏的调试信息。
好了,日志模块的配置就聊到这儿。下一节咱们会深入讲日志的运行时控制,包括动态调整日志级别、过滤模块等高级玩法。那些技巧,在线上排查问题时特别管用。