7.20. LOG

7.20.1. 概述

SDK 的打印系统通过 printf/putchar/puts/put_buf 等标准接口,将日志通过串口实时输出。打印功能由多个编译宏协同控制,支持系统打印用户打印两套独立开关。

主要特性:

  • 多级编译宏精确控制打印开关

  • 系统打印(printf)与用户打印(user_printf)独立管理

  • 支持分模块、分级控制打印输出

  • 支持时间戳输出


7.20.2. 打印配置与接口

作用

CONFIG_DEBUG_ENABLE

打印总开关,注释掉则系统打印和用户打印同时关闭

CONFIG_SYS_DEBUG_DISABLE

单独关闭系统打印,不影响用户打印

CONFIG_USER_DEBUG_ENABLE

单独控制用户打印,默认关闭

7.20.2.1. 打印总开关

CONFIG_DEBUG_ENABLE定义在 app_config.h 中,是整个打印系统的总开关。注释掉后,系统打印和用户打印同时关闭

// app_config.h
#define CONFIG_DEBUG_ENABLE     // 打印总开关,注释掉则关闭系统打印和用户打印

关闭原理: printf/putchar/puts 等函数在库(.a)中有默认实现。debug.cdebug_user.c 中通过条件编译将这些函数重新定义为空函数。链接时 .c 文件中的函数定义会覆盖库中的弱符号实现,从而达到关闭打印的效果。

7.20.2.2. 系统打印

CONFIG_SYS_DEBUG_DISABLE单独控制系统打印,不影响用户打印。定义后即使 CONFIG_DEBUG_ENABLE 已开启,系统打印也会被关闭。

#define CONFIG_SYS_DEBUG_DISABLE     // 关闭系统打印

接口:

printf("hello %d\n", val);               // 格式化输出
puts("hello");                           // 输出字符串并换行
putchar('A');                            // 输出单个字符
put_buf(buf, len);                       // 输出二进制数据
put_u8hex(0xAB);                         // 十六进制输出

7.20.2.3. 用户打印

CONFIG_USER_DEBUG_ENABLE单独控制用户打印,不影响系统打印。

// app_config.h
#define CONFIG_USER_DEBUG_ENABLE   // 开启用户打印

接口:

user_printf("user: %d\n", val);
user_puts("user hello\n");
user_putchar('U');
user_put_buf(buf, len);

例程路径:apps/common/example/system/printf/main.c

7.20.2.4. 系统打印与用户打印的互斥问题

系统打印(printf)和用户打印(user_printf)底层没有做互斥保护。当两者同时开启时,可能出现以下问题:

  • 打印内容丢失

  • 字符交叉错乱

  • 输出不完整

建议方案:使用 CONFIG_SYS_DEBUG_DISABLE + CONFIG_USER_DEBUG_ENABLE 组合,仅保留一路打印。


7.20.3. 时间戳控制

日志输出中默认附带系统时间戳,格式为 [HH:MM:SS.ms],代表距离系统上电的时间。若不需要时间戳,修改 apps/common/config/log_config/lib_system_config.c 中的全局变量:

// 打印是否时间打印信息
const int config_printf_time = 1;   // 改为 0 关闭时间戳
  • 1:每条日志前输出 [00:00:00.246] 格式的时间戳

  • 0:不输出时间戳


7.20.4. 分级日志

debug.h 提供带 TAG的6 级日志过滤机制,支持分模块、分级别独立控制。

7.20.4.1. 日志级别

含义

对应宏

冗余调试

log_verb()

调试信息

log_debug()

基本信息

log_info()

警告

log_warn()

错误

log_error()

字符输出

log_char()

7.20.4.2. 分级日志开关

7.20.4.2.1. 总开关

LIB_DEBUG 是分级日志的总开关。与 CONFIG_DEBUG_LIB(x) 宏配合使用。

// app_config.h
#if defined CONFIG_RELEASE_ENABLE || TCFG_DEBUG_DLOG_ENABLE
#define LIB_DEBUG    1
#else
#define LIB_DEBUG    0
#endif
#define CONFIG_DEBUG_LIB(x)         (x & LIB_DEBUG)
  • LIB_DEBUG 设为 0 时,CONFIG_DEBUG_LIB() 始终返回 0,所有层级的分级日志全部关闭

  • printf 的打印不受 LIB_DEBUG 影响。

7.20.4.2.2. 分级开关

模块在 apps/common/config/log_config/*_config.c 下的配置文件中声明了 log_tag_const_* 常量,通过控制对应级别的常量值为TRUE/FALSE来实现分模块、分级开关打印。

// lib_system_config.c 或 app_config.c
const char log_tag_const_v_APP AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIB(FALSE);  // Verbose 关闭
const char log_tag_const_d_APP AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIB(FALSE);  // Debug 关闭
const char log_tag_const_i_APP AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIB(TRUE);   // Info 开启
const char log_tag_const_w_APP AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIB(TRUE);   // Warn 开启
const char log_tag_const_e_APP AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIB(TRUE);   // Error 开启

7.20.4.3. 添加分级打印

开发者需要在自己实现的模块中添加分级打印,可以参考如下代码。

  • apps/common/config/log_config/*_config.c中选择合适的文件添加控制常量,比如:

// apps/common/config/log_config/*_config.c
const char log_tag_const_v_TEST AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIB(TRUE);
const char log_tag_const_d_TEST AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIB(TRUE);
const char log_tag_const_i_TEST AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIB(TRUE);
const char log_tag_const_w_TEST AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIB(TRUE);
const char log_tag_const_e_TEST AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIB(TRUE);
const char log_tag_const_c_TEST AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIB(TRUE);
  • 在模块.c文件中添加分级打印支持,比如:

// test.c
#define LOG_TAG_CONST       TEST      // 常量 TAG
#define LOG_TAG             "[TEST]"  // 字符串 TAG
#define LOG_ERROR_ENABLE              // 使能 Error 级别
#define LOG_DEBUG_ENABLE              // 使能 Debug 级别
#define LOG_INFO_ENABLE               // 使能 Info 级别
#include "debug.h"

void log_test(void)
{
    u8 buf[16];
    log_verb("I am verb Message.");
    log_info("I am info Message.");
    log_debug("I am debug Message.");
    log_warn("I am warn Message.");
    log_error("I am error Message.");
    log_char('@');
    log_info_hexdump(buf, sizeof(buf));
}

打印输出:

[Verb]: [TEST]I am verb Message.
[Info]: [TEST]I am info Message.
[Debug]: [TEST]I am debug Message.

(warn): <Warn>: [TEST]I am warn Message.


(error): <Error>: [TEST]I am error Message.

@
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

7.20.5. 常见问题

  • 系统每分钟定时打印任务栈信息,如何关闭?

    答:在 app_config.h 屏蔽 RTOS_STACK_CHECK_ENABLE

  • 如何动态开关打印?

    答:可以通过 config_debug_enable 全局变量控制,设置 config_debug_enable 值为 0 关闭打印,为 1 打开打印。

    注意: 使用打印保存到 TF 卡功能时,动态控制打印会有丢失数据问题。可使用 sd_log_flush() 强制将打印 buf 的缓存写入 SD 卡中,之后再使用 config_debug_enable = 0 关闭打印。

  • 如何把打印输出到 TF 卡里面保存成文件?

    答:在 debug_user.c 中开启宏定义 CONFIG_SDCARD_DEBUG_ENABLE

    注意: 打印保存到 SD 卡中使用缓存 buf,buf 每满 10 KB 写入一次到 SD 卡中(避免频繁写 SD 卡),不满则不写入。如需查看最后打印,可以使用 sd_log_flush() 强制将打印 buf 的缓存写入 SD 卡中;或者在使用动态开关打印时,关打印前强制将缓存写入 SD 卡中。

  • 如何把打印输出到云端服务器保存?

    答:

    1. debug_user.c 中开启宏定义 CONFIG_NETWORK_DEBUG_ENABLE

    2. 打开云串口调试工具,获取 KeyCode 值,并将生成的 KeyCode 值写到 debug_user.cLOG_KEYCODE 中,如:

    #define LOG_KEYCODE   "KRPNOCNSTGUMSRNL"  // 客户随机填写12-16个字符,用于日志查询
    
    1. 开启 wifi,使用 STA 模式,具体可以参考 wifi 配置;

    2. 定时调用 void upload_log_trig(void); 函数,具体定时中断可以参考 timer 配置;

    3. 在云串口调试工具中查看日志,关于云串口调试工具的使用可以参考 云串口调试工具.pdf

  • 如何使用 USB 虚拟串口作为打印口?

    答:

    1. debug_user.c 中开启宏定义 CONFIG_USB_DEBUG_ENABLE

    2. app_config.h 中增加如下宏定义

    #define TCFG_PC_ENABLE 1
    #define USB_PC_NO_APP_MODE 2
    #define TCFG_USB_SLAVE_ENABLE 1
    #define TCFG_UDISK_ENABLE 0
    #define USB_DEVICE_CLASS_CONFIG  (CDC_CLASS)
    

    注意: 在使用打印输出到 TF 卡、打印输出到云端服务器保存,以及 USB 虚拟串口作为打印口时,需要在 app_config.h 中开启宏 CONFIG_DEBUG_ENABLE