3. 实现引导日志分级

实现引导日志分级 学习卡片

任务目标

在 Zephyr 引导程序中实现分级日志输出:根据编译期日志级别宏,控制 printk 输出的详细程度,使开发者可在调试与发布构建间切换,而无需修改业务代码。

实现方案

通过自定义日志宏 BOOT_LOG,将日志级别映射到 Zephyr 的 LOG_LEVEL 配置。核心思路:

  • 定义 LOG_LEVEL_ERR/WRN/INF/DBG 四个级别,数值越大越详细;
  • 在编译期用 IS_ENABLED 判断当前级别,决定 printk 是否参与编译;
  • 级别宏在 prj.conf 中通过 BOOT_LOG_LEVEL 配置,实现构建时切换。

完整代码

/* boot_log.h */
#ifndef BOOT_LOG_H
#define BOOT_LOG_H

#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>

#define LOG_LEVEL_ERR  1
#define LOG_LEVEL_WRN  2
#define LOG_LEVEL_INF  3
#define LOG_LEVEL_DBG  4

/* 默认级别:警告 */
#ifndef BOOT_LOG_LEVEL
#define BOOT_LOG_LEVEL LOG_LEVEL_WRN
#endif

/* 编译期级别比较 */
#define BOOT_LOG_AT_LEVEL(level) \
    (IS_ENABLED(CONFIG_BOOT_LOG_LEVEL_ENABLED) && \
     (BOOT_LOG_LEVEL >= (level)))

/* 分级输出宏 */
#define BOOT_LOG_ERR(...) \
    do { if (BOOT_LOG_AT_LEVEL(LOG_LEVEL_ERR)) { printk("[ERR] "); printk(__VA_ARGS__); printk("\n"); } } while (0)

#define BOOT_LOG_WRN(...) \
    do { if (BOOT_LOG_AT_LEVEL(LOG_LEVEL_WRN)) { printk("[WRN] "); printk(__VA_ARGS__); printk("\n"); } } while (0)

#define BOOT_LOG_INF(...) \
    do { if (BOOT_LOG_AT_LEVEL(LOG_LEVEL_INF)) { printk("[INF] "); printk(__VA_ARGS__); printk("\n"); } } while (0)

#define BOOT_LOG_DBG(...) \
    do { if (BOOT_LOG_AT_LEVEL(LOG_LEVEL_DBG)) { printk("[DBG] "); printk(__VA_ARGS__); printk("\n"); } } while (0)

#endif /* BOOT_LOG_H */
/* main.c */
#include <zephyr/kernel.h>
#include "boot_log.h"

void main(void)
{
    BOOT_LOG_ERR("boot failed: %d", -EIO);
    BOOT_LOG_WRN("retry count: %d", 3);
    BOOT_LOG_INF("booting from slot %d", 1);
    BOOT_LOG_DBG("flash addr: 0x%x", 0x10000);
}
/* prj.conf */
CONFIG_BOOT_LOG_LEVEL_ENABLED=y
/* 可选值: 1=ERR, 2=WRN, 3=INF, 4=DBG */
/* 通过 CMake 或命令行覆盖: -DBOOT_LOG_LEVEL=4 */

构建与运行

  1. 将上述三个文件放入 Zephyr 应用目录(如 boot_log_demo/);
  2. 编译(以 qemu_cortex_m3 为例):
west build -b qemu_cortex_m3 . -DBOOT_LOG_LEVEL=4
west build -t run

验证方法

观察串口输出,应看到全部四条日志(ERR/WRN/INF/DBG)。随后将 -DBOOT_LOG_LEVEL 改为 2 重新编译运行,此时仅输出 [ERR] 与 [WRN] 两条,[INF] 与 [DBG] 不再出现。若改为 1,则仅输出 [ERR]。通过不同级别下输出条目的增减,即可确认分级生效。

关键说明

  • 级别判断在编译期完成,未达级别的 printk 调用不会生成代码,零运行时开销;
  • CONFIG_BOOT_LOG_LEVEL_ENABLED 作为总开关,置 n 时所有日志被完全裁剪;
  • 实际引导工程中,可将 BOOT_LOG_LEVEL 与构建系统变量绑定,实现 release/debug 自动切换。