6.1 日志系统 Dlog
6.1 日志系统 Dlog
📚 本节导读
学习时长: 约 60 分钟
难度级别: ⭐⭐⭐☆☆
前置知识: C 语言宏定义、变参函数、第4章内核组件
🎯 学习目标
- 理解 Dlog 日志系统的设计理念与核心架构
- 掌握四种日志级别(ERROR / WARNING / INFO / DEBUG)及其使用场景
- 理解编译级别与全局输出级别的区别与协同作用
- 掌握 LOG_E / LOG_W / LOG_I / LOG_D / LOG_RAW / LOG_HEX 等日志宏
- 掌握后端(dlog_backend)机制,包括控制台后端和文件系统后端
- 了解异步输出、ISR 安全、运行时过滤、彩色输出等高级特性
- 掌握 Syslog 兼容层的 API 用法
- 能够根据实际需求配置和使用 Dlog
一、概述
Dlog 是 OneOS 内置的日志系统,为嵌入式开发提供了一套功能完备、灵活高效的日志输出框架。其核心设计特点包括:
- 多级别日志输出:支持 ERROR、WARNING、INFO、DEBUG 四种级别,与标准 syslog 优先级兼容
- 多后端支持:内置控制台后端(串口输出)和文件系统后端(文件记录),支持自定义后端扩展
- 异步输出模式:日志先写入缓冲区,由专用任务异步输出,减少对调用任务的阻塞
- ISR 安全:日志输出 API 可在中断服务例程中安全使用
- 运行时过滤:支持 tag 过滤、关键词过滤、tag 级别过滤等多种运行时过滤方式
- 彩色输出:不同级别日志使用不同颜色,便于快速识别
- 编译时裁剪:通过编译级别控制,未使用的日志级别在编译时即被裁剪,不占用代码空间
- Syslog 兼容层:提供标准 POSIX syslog API,方便移植已有代码
Dlog 的源码位于 components/dlog/ 目录,核心头文件为 components/dlog/include/dlog.h。
二、日志级别
2.1 级别定义
Dlog 定义了四种日志级别,数值与标准 syslog 优先级兼容(源码 dlog.h 第38-41行):
#define DLOG_ERROR 3 /* Error conditions - 错误条件 */
#define DLOG_WARNING 4 /* Warning conditions - 警告条件 */
#define DLOG_INFO 6 /* Informational - 信息消息 */
#define DLOG_DEBUG 7 /* Debug-level messages - 调试消息 */各级别使用场景:
| 级别 | 数值 | 使用场景 | 示例 |
|---|---|---|---|
DLOG_ERROR | 3 | 系统运行中的错误条件,可能导致功能异常 | 内存分配失败、外设初始化失败 |
DLOG_WARNING | 4 | 潜在问题,不影响当前运行但需关注 | 参数超出推荐范围、重试操作 |
DLOG_INFO | 6 | 正常运行中的关键信息 | 系统启动完成、任务创建成功、网络连接状态 |
DLOG_DEBUG | 7 | 调试信息,用于开发阶段定位问题 | 函数入口/出口、变量值、状态转换 |
注意:级别数值 3、4、6、7 并非连续,这是为了与 syslog 的
LOG_ERR(3)、LOG_WARNING(4)、LOG_INFO(6)、LOG_DEBUG(7) 保持一致。
2.2 两个独立的控制维度
Dlog 使用两个独立的维度来控制日志输出:
(1)编译级别(DLOG_COMPILE_LEVEL)
编译时由 Kconfig 配置,决定哪些级别的日志宏在编译时被保留。级别低于编译级别的日志宏会被展开为空语句,不占用代码空间和 CPU 时间。
// 例如:DLOG_COMPILE_LEVEL = DLOG_INFO(6)
// 则 LOG_E 和 LOG_W 和 LOG_I 会生成实际代码
// 而 LOG_D 会被展开为空(2)全局输出级别(DLOG_GLOBAL_PRINT_LEVEL)
运行时通过 dlog_global_lvl_set() 设置,决定当前哪些级别的日志被实际输出。即使日志宏在编译时被保留,如果其级别低于全局输出级别,也不会输出。
// 运行时设置
dlog_global_lvl_set(DLOG_WARNING); // 只输出 WARNING 及以上级别
dlog_global_lvl_set(DLOG_DEBUG); // 输出所有级别两个维度协同工作,形成"编译时做减法,运行时做开关"的灵活控制策略。
三、核心日志宏详解
3.1 基本日志宏
Dlog 提供了四个基本日志宏,分别对应四种日志级别。每个宏都接受一个 tag 参数,用于标识日志来源模块。
(1)LOG_E - 错误级别日志
#define LOG_E(tag, fmt, ...) dlog_output(DLOG_ERROR, tag, OS_TRUE, fmt, ##__VA_ARGS__)用于输出错误条件。当 DLOG_ERROR > DLOG_COMPILE_LEVEL 时,该宏在编译时被裁剪。
(2)LOG_W - 警告级别日志
#define LOG_W(tag, fmt, ...) dlog_output(DLOG_WARNING, tag, OS_TRUE, fmt, ##__VA_ARGS__)用于输出警告信息。当 DLOG_WARNING > DLOG_COMPILE_LEVEL 时裁剪。
(3)LOG_I - 信息级别日志
#define LOG_I(tag, fmt, ...) dlog_output(DLOG_INFO, tag, OS_TRUE, fmt, ##__VA_ARGS__)用于输出信息消息。当 DLOG_INFO > DLOG_COMPILE_LEVEL 时裁剪。
(4)LOG_D - 调试级别日志
#define LOG_D(tag, fmt, ...) dlog_output(DLOG_DEBUG, tag, OS_TRUE, fmt, ##__VA_ARGS__)用于输出调试信息。当 DLOG_DEBUG > DLOG_COMPILE_LEVEL 时裁剪。
3.2 函数名和行号
通过 DLOG_WITH_FUNC_LINE 宏控制是否在日志中自动附加函数名和行号。
当启用 DLOG_WITH_FUNC_LINE 时(源码 dlog.h 第43-71行),日志宏会自动在输出末尾追加 [函数名][行号]:
#define LOG_E(tag, fmt, ...) \
dlog_output(DLOG_ERROR, tag, OS_TRUE, fmt " [%s][%d]", ##__VA_ARGS__, __FUNCTION__, __LINE__)当关闭 DLOG_WITH_FUNC_LINE 时(源码 dlog.h 第74-100行),日志宏仅输出用户指定的内容:
#define LOG_E(tag, fmt, ...) dlog_output(DLOG_ERROR, tag, OS_TRUE, fmt, ##__VA_ARGS__)3.3 特殊输出宏
(1)LOG_RAW - 原始输出
#define LOG_RAW(fmt, ...) dlog_raw(fmt, ##__VA_ARGS__)不带任何格式修饰(无级别、无 tag、无时间戳),直接将内容输出到所有后端。适用于输出纯文本或特殊格式内容。
(2)LOG_HEX - 十六进制 dump
#define LOG_HEX(tag, width, buf, size) dlog_hexdump(tag, width, buf, size)以十六进制格式输出内存缓冲区内容。width 参数指定每行显示的字节数(通常为 16 或 8)。
四、日志后端(dlog_backend)
4.1 后端结构体
Dlog 采用后端(backend)机制,将日志输出与具体输出设备解耦。后端结构体定义如下(源码 dlog.h 第112-128行):
struct dlog_backend
{
os_list_node_t list_node; /* 链表节点,用于后端链表管理 */
char name[OS_NAME_MAX + 1]; /* 后端名称 */
os_bool_t support_color; /* 是否支持彩色输出 */
os_bool_t support_isr; /* 是否支持 ISR 中调用 */
void (*init)(struct dlog_backend *backend); /* 初始化后端 */
void (*deinit)(struct dlog_backend *backend); /* 反初始化后端 */
void (*output)(struct dlog_backend *backend, char *log, os_size_t len); /* 输出日志 */
void (*flush)(struct dlog_backend *backend); /* 刷新缓冲区 */
};
typedef struct dlog_backend dlog_backend_t;4.2 内置后端类型
(1)控制台后端(console_backend)
- 配置项:
DLOG_BACKEND_USING_CONSOLE(默认开启) - 功能:将日志通过控制台(串口)输出
- 特点:简单直接,适合于开发调试阶段
(2)文件系统后端(filesystem_backend)
- 配置项:
DLOG_BACKEND_USING_FILESYSTEM(默认关闭) - 功能:将日志记录到文件系统,支持日志文件的持久化存储
- 特点:自动选择异步输出模式,支持日志文件滚动(按大小和数量限制)
- 相关配置:文件路径(
DLOG_FILE_DIR)、文件名(DLOG_FILE_NAME)、文件大小(DLOG_FILE_SIZE)、文件数量(DLOG_FILE_NUM)、文件缓存(DLOG_FILE_ENABLE_CACHE)
4.3 后端注册与注销
os_err_t dlog_backend_register(dlog_backend_t *backend); /* 注册后端 */
os_err_t dlog_backend_unregister(dlog_backend_t *backend); /* 注销后端 */注册后的后端会被添加到全局后端链表中,所有日志输出会同时发送到所有已注册的后端。
五、核心运行时 API
5.1 系统初始化
os_err_t dlog_init(void);初始化日志系统,通常在系统启动时调用。该函数会初始化所有已注册的后端。
5.2 底层输出函数
void dlog_output(uint16_t level, const char *tag, os_bool_t newline, const char *format, ...);日志系统的核心输出函数,所有日志宏最终都调用此函数。参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
level | uint16_t | 日志级别,如 DLOG_ERROR、DLOG_INFO |
tag | const char * | 日志标签,用于标识模块来源 |
newline | os_bool_t | 是否在末尾追加换行符 |
format | const char * | 格式化字符串,与 printf 兼容 |
... | - | 可变参数 |
void dlog_raw(const char *format, ...);原始输出,不带级别、tag、时间戳等格式修饰。
void dlog_hexdump(const char *tag, os_size_t width, uint8_t *buf, os_size_t size);十六进制 dump 输出,用于查看内存数据。
5.3 全局级别控制
void dlog_global_lvl_set(uint16_t level); /* 设置全局输出级别 */
uint16_t dlog_global_lvl_get(void); /* 获取当前全局输出级别 */运行时动态调整日志输出级别,控制哪些级别的日志被实际输出。
5.4 缓冲区刷新
void dlog_flush(void);刷新所有后端的日志缓冲区。在异步输出模式下,日志可能暂存在缓冲区中,调用此函数可强制立即输出。
六、Kconfig 配置详解
Dlog 的配置项位于 components/dlog/Kconfig,在 menu "Dlog" 下。
6.1 基础配置
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
OS_USING_DLOG | bool | y | 启用 Dlog 日志系统 |
DLOG_GLOBAL_PRINT_LEVEL | int | 4(WARNING) | 全局输出级别(运行时) |
DLOG_COMPILE_LEVEL | int | 7(DEBUG) | 编译级别(编译时) |
6.2 ISR 安全
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
DLOG_USING_ISR_LOG | bool | y | 启用 ISR 安全日志,允许在中断中调用日志 API |
6.3 运行时过滤
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
DLOG_USING_FILTER | bool | y | 启用运行时过滤,支持 tag 过滤、关键词过滤、tag 级别过滤 |
启用后可通过 Shell 命令动态控制过滤规则:
dlog_gtag_ctrl- 全局 tag 控制(仅输出指定 tag 的日志)dlog_gkw_ctrl- 全局关键词控制(仅输出包含指定关键词的日志)dlog_tlvl_ctrl- tag 级别控制(针对特定 tag 设置独立的输出级别)
6.4 异步输出
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
DLOG_USING_ASYNC_OUTPUT | bool | y | 启用异步输出模式 |
DLOG_ASYNC_OUTPUT_BUF_SIZE | int | 2048 | 异步输出缓冲区大小(字节) |
DLOG_ASYNC_OUTPUT_TASK_STACK_SIZE | int | 2048 | 异步输出任务栈大小(字节) |
DLOG_ASYNC_OUTPUT_TASK_PRIORITY | int | 20(32级) | 异步输出任务优先级 |
6.5 Syslog 兼容
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
DLOG_USING_SYSLOG | bool | n | 启用 Syslog 兼容层 |
6.6 日志格式
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
DLOG_OUTPUT_FLOAT | bool | n | 启用浮点数支持(需 libc,会增加栈开销) |
DLOG_WITH_FUNC_LINE | bool | y | 在日志中附加函数名和行号 |
DLOG_USING_COLOR | bool | y | 启用彩色日志输出 |
DLOG_OUTPUT_TIME_INFO | bool | y | 启用时间信息输出 |
DLOG_TIME_USING_TIMESTAMP | bool | n | 使用时间戳格式(需 libc,依赖 DLOG_OUTPUT_TIME_INFO) |
6.7 文件系统后端
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
DLOG_BACKEND_USING_FILESYSTEM | bool | n | 启用文件系统后端 |
DLOG_FILESYSTEM_FAL_PARTITION_NAME | string | "filesystem" | FAL 分区名称 |
DLOG_FILE_DIR | string | "/dlog/" | 日志文件目录 |
DLOG_FILE_NAME | string | "log.txt" | 日志文件名 |
DLOG_FILE_SIZE | int | 20480 | 单个日志文件大小(字节) |
DLOG_FILE_NUM | int | 5 | 日志文件最大数量 |
DLOG_FILE_ENABLE_CACHE | bool | y | 启用文件缓存(减少 I/O) |
DLOG_FILE_CACHE_BUF_SIZE | int | 1024 | 文件缓存缓冲区大小 |
七、Syslog 兼容层
Dlog 提供了 POSIX 标准 syslog 兼容层,方便移植使用 syslog 的已有代码。源码位于 components/dlog/include/syslog/syslog.h。
7.1 优先级定义
#define LOG_EMERG 0 /* 系统不可用 */
#define LOG_ALERT 1 /* 必须立即采取措施 */
#define LOG_CRIT 2 /* 临界条件 */
#define LOG_ERR 3 /* 错误条件 */
#define LOG_WARNING 4 /* 警告条件 */
#define LOG_NOTICE 5 /* 正常但重要的情况 */
#define LOG_INFO 6 /* 信息消息 */
#define LOG_DEBUG 7 /* 调试级别消息 */7.2 Facility 代码
#define LOG_KERN (0 << 3) /* 内核消息 */
#define LOG_USER (1 << 3) /* 用户级消息 */
#define LOG_MAIL (2 << 3) /* 邮件系统 */
#define LOG_DAEMON (3 << 3) /* 系统守护进程 */
#define LOG_AUTH (4 << 3) /* 安全/授权消息 */
#define LOG_LOCAL0 (16 << 3) /* 本地使用 0 */
#define LOG_LOCAL1 (17 << 3) /* 本地使用 1 */
#define LOG_LOCAL2 (18 << 3) /* 本地使用 2 */
#define LOG_LOCAL3 (19 << 3) /* 本地使用 3 */
#define LOG_LOCAL4 (20 << 3) /* 本地使用 4 */
#define LOG_LOCAL5 (21 << 3) /* 本地使用 5 */
#define LOG_LOCAL6 (22 << 3) /* 本地使用 6 */
#define LOG_LOCAL7 (23 << 3) /* 本地使用 7 */7.3 API
void openlog(const char *ident, int option, int facility); /* 打开日志连接 */
void syslog(int priority, const char *format, ...); /* 输出日志 */
void closelog(void); /* 关闭日志连接 */openlog:设置日志标识符(ident)、选项(如LOG_PID、LOG_CONS、LOG_NDELAY)和 facilitysyslog:按指定的优先级输出日志closelog:关闭日志连接
八、使用示例
8.1 基本日志输出
#include <dlog.h>
void basic_log_example(void)
{
LOG_E("main", "System initialization failed, error code: %d", -1);
LOG_W("main", "Battery level low: %d%%", 15);
LOG_I("main", "System started successfully, version: %s", "v3.3.1");
LOG_D("main", "Task stack usage: %d/%d bytes", 512, 2048);
}输出示例:
[E/main] System initialization failed, error code: -1 [basic_log_example][42]
[W/main] Battery level low: 15% [basic_log_example][43]
[I/main] System started successfully, version: v3.3.1 [basic_log_example][44]
[D/main] Task stack usage: 512/2048 bytes [basic_log_example][45]8.2 带 tag 的模块日志
#include <dlog.h>
/* 网络模块 */
void network_module(void)
{
LOG_I("net", "Network interface eth0 up");
LOG_D("net", "IP address: 192.168.1.100, netmask: 255.255.255.0");
LOG_W("net", "Packet retransmission count: %d", 3);
LOG_E("net", "Connection timeout, server: %s", "10.0.0.1");
}
/* 传感器模块 */
void sensor_module(void)
{
LOG_I("sensor", "Temperature sensor initialized");
LOG_D("sensor", "Current temperature: %.1f C", 25.5);
LOG_W("sensor", "Temperature exceeds threshold: %.1f C", 85.0);
}8.3 十六进制 dump 示例
#include <dlog.h>
void hex_dump_example(void)
{
uint8_t packet[] = {
0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x4F, 0x6E,
0x65, 0x4F, 0x53, 0x00, 0x01, 0x02, 0x03, 0xFF
};
LOG_I("comm", "Received packet:");
LOG_HEX("comm", 16, packet, sizeof(packet));
}8.4 全局级别运行时控制
#include <dlog.h>
void level_control_example(void)
{
/* 开发阶段:输出所有日志 */
dlog_global_lvl_set(DLOG_DEBUG);
LOG_D("test", "This debug message will be output");
LOG_I("test", "This info message will be output");
/* 发布阶段:仅输出警告和错误 */
dlog_global_lvl_set(DLOG_WARNING);
LOG_D("test", "This debug message will NOT be output");
LOG_I("test", "This info message will NOT be output");
LOG_W("test", "This warning message will be output");
LOG_E("test", "This error message will be output");
}8.5 自定义后端注册示例
#include <dlog.h>
/* 自定义后端:输出到自定义存储 */
static void my_output(struct dlog_backend *backend, char *log, os_size_t len)
{
/* 将日志写入自定义存储设备 */
my_storage_write(log, len);
}
static void my_init(struct dlog_backend *backend)
{
/* 初始化自定义存储设备 */
my_storage_init();
}
static void my_flush(struct dlog_backend *backend)
{
/* 刷新存储缓冲区 */
my_storage_flush();
}
static dlog_backend_t my_backend =
{
.name = "my_storage",
.support_color = OS_FALSE,
.support_isr = OS_FALSE,
.init = my_init,
.deinit = OS_NULL,
.output = my_output,
.flush = my_flush,
};
void register_custom_backend(void)
{
dlog_backend_register(&my_backend);
LOG_I("custom", "Custom backend registered: %s", my_backend.name);
}8.6 Syslog 兼容层使用示例
#include <syslog/syslog.h>
void syslog_example(void)
{
/* 打开日志连接 */
openlog("my_app", LOG_PID | LOG_CONS, LOG_USER);
/* 使用 syslog 输出日志 */
syslog(LOG_ERR, "Failed to open device: %s", "/dev/uart0");
syslog(LOG_WARNING, "Disk usage exceeds 80%%");
syslog(LOG_INFO, "Application started successfully");
syslog(LOG_DEBUG, "Debug: variable x = %d", 42);
/* 关闭日志连接 */
closelog();
}📝 本节小结
本节详细介绍了 OneOS Dlog 日志系统,核心要点如下:
- 四种日志级别:ERROR(3)、WARNING(4)、INFO(6)、DEBUG(7),与 syslog 兼容
- 两级控制:编译级别(编译时裁剪)和全局输出级别(运行时开关),灵活控制日志输出
- 六大日志宏:LOG_E / LOG_W / LOG_I / LOG_D 用于分级输出,LOG_RAW 用于原始输出,LOG_HEX 用于十六进制 dump
- 后端机制:通过
dlog_backend_t结构体抽象输出设备,支持控制台后端和文件系统后端,可自定义扩展 - 高级特性:异步输出、ISR 安全、运行时过滤(tag/关键词/级别)、彩色输出、时间戳
- Syslog 兼容:提供 POSIX 标准 syslog API,支持优先级和 facility 代码