日志库
概述
Ameba SDK 提供了统一的标签式日志系统,可用于 ROM、Bootloader 及应用固件各阶段。日志输出通过两层独立机制进行控制:
编译期过滤(
COMPIL_LOG_LEVEL):超过该级别的日志调用在预处理阶段即被移除,不产生任何代码或数据开销。运行时过滤(
rtk_log_level_set()):控制已编译进固件的日志实际输出,不影响二进制大小。
日志级别
日志级别由枚举 rtk_log_level_t 定义:
typedef enum {
RTK_LOG_NONE, /* 0 */
RTK_LOG_ALWAYS, /* 1 */
RTK_LOG_ERROR, /* 2 */
RTK_LOG_WARN, /* 3 */
RTK_LOG_INFO, /* 4 */
RTK_LOG_DEBUG /* 5 */
} rtk_log_level_t;
级别 |
数值 |
字母 |
说明 |
|---|---|---|---|
RTK_LOG_NONE |
0 |
— |
禁用所有输出。 |
RTK_LOG_ALWAYS |
1 |
|
必须始终显示的关键信息,但不代表警告或错误。 |
RTK_LOG_ERROR |
2 |
|
错误信息,如 API 使用不当导致的错误。 |
RTK_LOG_WARN |
3 |
|
不合适的配置但仍可运行,或可能导致错误的情形。 |
RTK_LOG_INFO |
4 |
|
正常且必要的提示信息(默认级别)。 |
RTK_LOG_DEBUG |
5 |
|
出错或故障时的诊断信息,如指针指向和内存内容。 |
如需更改编译期或运行时的日志级别,请参阅 日志级别配置 章节。
日志格式
输出格式
日志宏的输出格式分为以下两种:
带标签:
[TAG-X] 消息内容
不带标签(传入
NOTAG):消息内容
其中 X 为级别字母( A 、 E 、 W 、 I 或 D )。所有宏均不自动追加换行符,
需在格式字符串末尾显式加入 \n 。 NOTAG 适用于在循环中打印连续行等不需要重复前缀的场景。
标签命名规则
每个模块通过标签字符串标识自身。命名规则如下:
将标签定义为
const char *指针,通常位于文件作用域。同一文件或同一组件内的所有文件共享一个标签作为唯一标识符。
标签字符串必须全大写,长度不超过 9 个字符,且只能包含数字、字母或下划线,不允许使用横杠。
标签变量命名以
TAG_开头(例如TAG_WIFI)。
static const char *TAG_APP = "APP";
日志输出宏
Ameba SDK 提供两类日志输出宏以适应不同的应用场景。
1. 标准输出宏 (RTK_LOGx)
这类宏使用 DiagPrintf ,支持除浮点数外的常见格式说明符。日志级别已编码在宏名称中,适合在常规任务场景中使用:
RTK_LOGA(tag, format, ...) /* RTK_LOG_ALWAYS */
RTK_LOGE(tag, format, ...) /* RTK_LOG_ERROR */
RTK_LOGW(tag, format, ...) /* RTK_LOG_WARN */
RTK_LOGI(tag, format, ...) /* RTK_LOG_INFO */
RTK_LOGD(tag, format, ...) /* RTK_LOG_DEBUG */
tag:模块标签字符串,或传入
NOTAG(省略前缀)。format:
printf风格的格式字符串。
2. 轻量级输出宏 (RTK_LOGS)
RTK_LOGS 底层调用 DiagPrintfNano ,它的栈占用更小,支持格式有限,适合在中断处理函数 (ISR) 或栈空间受限的任务中使用:
RTK_LOGS(tag, level, format, ...)
tag:模块标签字符串,或传入
NOTAG(省略前缀)。level:
rtk_log_level_t枚举值之一。format:
printf风格的格式字符串。
使用示例
#include "log.h"
static const char *TAG_APP = "APP";
void app_init(void)
{
int err = 1;
/* 推荐:nano 后端,栈占用更小 */
RTK_LOGS(TAG_APP, RTK_LOG_INFO, "version %d\n", 1);
RTK_LOGS(TAG_APP, RTK_LOG_ERROR, "open failed, ret=%d\n", err);
/* 打印标题行,循环内容使用 NOTAG 省略重复前缀 */
RTK_LOGI(TAG_APP, "command list start\n");
for (int i = 0; i < count; i++) {
RTK_LOGI(NOTAG, " [%d] %s\n", i, cmd_table[i].name);
}
RTK_LOGI(TAG_APP, "command list end\n");
}
预期输出:
[APP-I] version 1
[APP-E] open failed, ret=1
[APP-I] command list start
[0] help
[1] reset
[APP-I] command list end
日志级别配置
编译时日志级别设置
宏 COMPIL_LOG_LEVEL 是全局定义,决定哪些日志级别会被编译进固件。
超过该级别的调用在预处理阶段即被移除,不占用任何 Flash 或 RAM 空间。
默认的编译日志级别设定为 RTK_LOG_INFO (DEBUG 默认被裁剪,在 log.h 中配置)。
若要更改单个文件或者整个组件的编译级别,请参阅下文的覆盖方法。
单文件覆盖
在包含头文件 log.h 前定义 COMPIL_LOG_LEVEL ,可覆盖默认值:
#define COMPIL_LOG_LEVEL RTK_LOG_ERROR
#include "log.h"
static const char *TAG_FOO = "FOO";
/* 该文件中只有 ALWAYS 和 ERROR 级别的调用会被保留。 */
RTK_LOGE(TAG_FOO, "error code: %d\n", err);
RTK_LOGD(TAG_FOO, "debug value: %08x\n", val); /* 编译期被裁剪 */
组件级覆盖
若要更改某个组件(多个文件)的编译级别,在该组件的 CMakeLists.txt 中使用
private_definitions 追加定义:
ameba_list_append(private_definitions COMPIL_LOG_LEVEL=RTK_LOG_DEBUG)
这等效于为该组件的所有源文件传入 -DCOMPIL_LOG_LEVEL=RTK_LOG_DEBUG 编译参数。
备注
COMPIL_LOG_LEVEL 是编译期上限。未编译进固件的日志,即使通过
rtk_log_level_set() 设置了对应级别,都无法被打印出来。运行时级别只能进一步
抑制输出,无法恢复编译期已裁剪的日志。
运行时日志级别设置
运行时可按标签或全局方式调整日志级别,不影响二进制大小。
rtk_log_level_set
条目 |
说明 |
|---|---|
功能介绍 |
为指定模块标签设置日志输出级别,或更新全局默认级别。 |
参数 |
|
返回值 |
成功返回 |
rtk_log_level_get
条目 |
说明 |
|---|---|
功能介绍 |
查询某模块标签当前生效的日志级别。 |
参数 |
|
返回值 |
若在缓存中找到该标签,返回其已注册的级别;否则返回全局默认级别
( |
rtk_log_array_clear
条目 |
说明 |
|---|---|
功能介绍 |
清除缓存中所有按标签设置的级别条目,并将计数器重置为零。 |
参数 |
无 |
返回值 |
无 |
rtk_log_array_print
条目 |
说明 |
|---|---|
功能介绍 |
打印当前缓存中存储的所有标签/级别条目。 |
参数 |
|
返回值 |
成功返回 |
全局级别与模块级别
运行时日志级别分为两个独立维度:
全局默认级别(
rtk_log_default_level):通过rtk_log_level_set("*", level)设置,作用于未在缓存中单独注册的所有模块。模块级别:通过
rtk_log_level_set(tag, level)针对特定标签设置,存储于标签缓存中。 已注册模块级别的标签在查询时直接返回其缓存值,全局默认级别对其不产生影响。
两者相互独立,互不覆盖。修改全局默认级别不会影响已注册模块级别的标签; 修改某模块级别也不会影响全局默认值或其他模块的设置。
标签缓存管理
标签缓存最多容纳 LOG_TAG_CACHE_ARRAY_SIZE (4)个条目,用于存储各模块的日志级别。
缓存已满时若写入新标签,将按环形策略覆盖索引 count % 4 处的槽位。
通配符 "*" 不占用缓存槽,其操作仅更新全局默认级别变量。
使用示例
/* 全局抑制 DEBUG 输出 */
rtk_log_level_set("*", RTK_LOG_INFO);
/* 仅对 WiFi 模块启用 DEBUG */
rtk_log_level_set("WIFI", RTK_LOG_DEBUG);
/* 查询生效的级别 */
rtk_log_level_t lvl = rtk_log_level_get("WIFI"); /* RTK_LOG_DEBUG */
/* 查看缓存内容 */
rtk_log_array_print(rtk_log_tag_array);
/* 重置所有按标签的设置 */
rtk_log_array_clear();
AT 命令
运行时日志级别也可通过 AT 命令进行控制,完整说明请参阅 AT+LOG 。
打印后端
每次日志宏调用在到达实际打印后端之前,需经过两级过滤,如下图所示。
两级过滤说明:
编译期过滤(
COMPIL_LOG_LEVEL):级别超过该值的调用由预处理器移除,不生成任何代码。运行时过滤(
rtk_log_level_get):将标签的当前生效级别与调用级别比较。若该标签已通过rtk_log_level_set()单独注册,则使用其对应级别;否则使用全局默认值 (rtk_log_default_level)。
两种打印后端的区别:
DiagPrintfNano:栈占用更小,适合中断和小栈任务,格式说明符支持有限。DiagPrintf:支持除浮点数以外的格式说明符,适合通用场景。
性能开销
两种宏在栈空间占用和格式支持方面存在差异:
RTK_LOGS调用链:RTK_LOGS→rtk_log_write_nano()→DiagPrintfNano/DiagVprintfNano。栈占用约 136 B,格式说明符支持有限。RTK_LOGx调用链:RTK_LOGx→rtk_log_write()→DiagPrintf/DiagVprintf。栈占用约 252 B,支持除浮点数以外的格式说明符。
宏 |
底层函数 |
栈占用(典型值) |
格式支持 |
|---|---|---|---|
RTK_LOGS |
DiagVprintfNano |
~136 B |
有限格式说明符 |
RTK_LOGx |
DiagVprintf |
~252 B |
除浮点数外 |
选型建议:
中断处理函数或栈空间受限的任务中,使用
RTK_LOGS。需要打印长整型(
%lld、%lu等)时,使用RTK_LOGx。浮点数格式说明符(
%f、%g等)两类宏均不支持。
内存 Dump
以下三个辅助函数用于将内存区域以十六进制格式输出到日志。
rtk_log_memory_dump_word
条目 |
说明 |
|---|---|
功能介绍 |
以 32 位字为单位转储内存区域,每行显示 8 个字。 |
参数 |
|
返回值 |
无 |
输出示例:
[200447b4] 20015e08 00000000 20000674 000055d9 0c002763 20000749 0c0067f4 00000000
rtk_log_memory_dump_byte
条目 |
说明 |
|---|---|
功能介绍 |
以字节为单位转储内存区域,每行显示 8 个字节。 |
参数 |
|
返回值 |
无 |
输出示例:
[200447b4] 08 5e 01 20 00 00 00 00
rtk_log_memory_dump2char
条目 |
说明 |
|---|---|
功能介绍 |
以十六进制与可打印 ASCII 组合格式转储内存区域,每行显示 16 字节。 |
参数 |
|
返回值 |
无 |
输出示例:
[0xe005263] 7c 03 f0 7f 03 23 74 cf e7 07 25 cd e7 a1 69 30 ||....#t...%...i0|
API 参考
API / 宏 |
说明 |
|---|---|
|
日志输出(nano 后端, 推荐使用) |
|
ALWAYS 级别日志 |
|
ERROR 级别日志 |
|
WARN 级别日志 |
|
INFO 级别日志 |
|
DEBUG 级别日志 |
|
设置标签运行时级别或 全局默认级别 |
|
获取标签运行时级别 |
|
清除标签级别缓存 |
|
打印标签级别缓存 |
|
内存 Dump:32 位字 |
|
内存 Dump:字节 |
|
内存 Dump:字节 + ASCII |
|
初始化日志互斥锁 (仅 RTL8730E) |