日志模块配置: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 延迟输出模式 生产环境推荐
小提示:如果你不确定该用哪个模式,先选CONFIG_LOG_MODE_IMMEDIATE。等调试差不多了,再切到DEFERRED模式。我一般调试阶段用IMMEDIATE,发布前改成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就够了,否则日志刷屏刷到你怀疑人生。

注意:日志级别是编译期决定的!你设了LOG_LEVEL_INF,那LOG_DBG的代码根本不会编译进去。这既是好事也是坏事——好处是零开销,坏处是你想临时看调试信息得重新编译。我曾经在调试一个偶发bug时,因为忘了改日志级别,反复编译了十几次才抓到关键信息。

日志输出格式配置:让日志更好读

日志内容有了,但怎么呈现也很重要。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

你看,差别还是挺大的。调试阶段追求信息量和实时性,发布阶段追求性能和代码体积。你想想看,产品卖出去之后,用户可不想看到满屏的调试信息。

好了,日志模块的配置就聊到这儿。下一节咱们会深入讲日志的运行时控制,包括动态调整日志级别、过滤模块等高级玩法。那些技巧,在线上排查问题时特别管用。