在嵌入式开发中,日志系统是调试和问题定位的重要工具。EasyLogger 作为一款轻量级嵌入式日志库,凭借其灵活的架构设计和易用性,在裸机、RTOS、Linux 等多种环境中得到广泛应用。本文将从架构设计、核心结构、使用指南到移植实战,全面解析 EasyLogger 的技术细节。
一、架构设计:优雅的前后端分离
EasyLogger 最核心的优势在于其前后端分离设计,这种架构让它能够轻松适配不同硬件平台和应用场景。
1.1 架构分层
整个系统分为三层,职责清晰:
┌─────────────────────────────────────┐
│ 应用层 (Application) │ ← 用户代码(调用 log_i() 等 API)
├─────────────────────────────────────┤
│ 核心层 (Core/Frontend) │ ← 平台无关(日志格式化、过滤、缓冲)
│ elog.c、elog_utils.c │
├─────────────────────────────────────┤
│ 移植层 (Port/Backend) │ ← 平台相关(硬件适配接口)
│ elog_port.c │
└─────────────────────────────────────┘
1.2 前后端职责划分
- 前端(核心层):负责“日志应该是什么样子”
- 日志格式化(时间戳、级别、标签等信息组装)
- 日志级别过滤(仅输出指定级别及以上的日志)
- 标签(TAG)管理(标识日志来源模块)
- 缓冲区管理(临时存储日志内容)
- 后端(移植层):负责“日志输出到哪里去”
- 实现硬件相关的输出接口(串口、文件、网络等)
- 提供时间获取、中断控制等平台相关功能
- 接口与配置:
- 前后端通过标准化函数接口(如
elog_port_output)通信 - 全局配置通过
elog_cfg.h实现(编译期可定制)
- 前后端通过标准化函数接口(如
1.3 设计优势
这种架构的灵活性体现在:
- 跨平台移植简单:移植到新平台只需修改后端(约5-6个接口函数),前端代码完全复用
- 输出方式灵活:可轻松切换输出目标(串口/文件/网络/LCD,甚至多目标同时输出)
- 便于测试:前端逻辑可在PC上用标准C库测试,后端可通过Mock实现单元测试
二、核心结构:模块划分与协作
EasyLogger 的代码结构清晰,各模块边界明确,通过合理的依赖关系实现功能协作。
2.1 核心模块划分
| 模块 | 包含文件 | 职责说明 | 边界 |
|---|---|---|---|
| 核心功能模块 | src/elog.c、src/elog_utils.c | 实现日志格式化、过滤、输出控制等核心逻辑;提供日志API(log_a/log_e等)。 | 不直接依赖硬件,通过移植接口与底层交互。 |
| 移植接口模块 | port/elog_port.c | 实现硬件相关接口(如串口输出、时间获取),适配特定芯片(如KF32A)。 | 仅依赖硬件驱动(如UART、RTC),向上提供标准化接口。 |
| 配置模块 | inc/elog_cfg.h | 定义日志输出级别、缓冲区大小、格式等配置参数。 | 被核心模块和移植模块引用,无对外依赖。 |
| 头文件模块 | inc/elog.h | 声明日志API、数据结构和宏定义(如日志级别、格式枚举)。 | 作为对外接口,被所有模块引用。 |
2.2 模块间依赖关系
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 应用层代码 │ │ 核心功能模块 │ │ 移植接口模块 │
│ (使用日志API) │─────>│ (elog.c/utils) │─────>│ (elog_port.c) │
└─────────────────┘ └─────────────────┘ └────────┬────────┘
▲ │
│ ▼
└───────────────────────────────────────────┐ 硬件驱动层 │
│ (UART/RTC等) │
└──────────────┘
▲ ▲
│ │
┌─────────────────┐ ┌─────────────────┐ │
│ 配置模块 │<─────│ 头文件模块 │<──────────────┘
│ (elog_cfg.h) │ │ (elog.h) │
└─────────────────┘ └─────────────────┘
2.3 日志输出流程
一条日志从产生到输出的完整路径:
应用代码调用日志API → 核心模块格式化日志 → 过滤(级别/标签) → 移植接口输出 → 硬件设备
(如log_i("xxx")) (添加时间/标签/级别) (符合条件则输出) (串口发送) (终端显示)
关键数据流说明:
- 应用层通过
log_x宏生成日志请求,传入标签、格式字符串和参数; - 核心模块在
elog_output函数中组装日志(级别、时间、标签等),存入缓冲区log_buf; - 核心模块调用
elog_port_output,将缓冲区数据传递给移植接口; - 移植接口通过硬件驱动(如
USART1_STREAM)将日志发送到终端。
三、使用指南:标签、API与最佳实践
掌握 EasyLogger 的使用方法,能让日志输出更规范、调试更高效。
3.1 标签(TAG)设置:标识日志来源
在 .c 文件开头定义 LOG_TAG,后续使用简化宏自动带上标签:
// 在 WIFI.c 文件中
#define LOG_TAG "WIFI" // 必须在 #include <elog.h> 之前定义
#include <elog.h>
void wifi_connect(void) {
log_i("开始连接WiFi"); // 输出: I/WIFI: 开始连接WiFi
log_e("连接失败"); // 输出: E/WIFI: 连接失败
}
注意事项:
- 标签作用域仅限当前
.c文件(预处理器隔离),不同文件可使用相同标签; - 不要在头文件中定义
LOG_TAG(会导致所有包含该头文件的.c共享同一标签); - 未定义
LOG_TAG时,默认使用“NO_TAG”。
3.2 日志API使用:按级别输出
EasyLogger 提供6种日志级别(从高到低),对应不同的调试场景:
| 级别 | 宏定义 | 说明 |
|---|---|---|
| 断言 | log_a(...) | 程序断言失败时输出(最高优先级) |
| 错误 | log_e(...) | 错误信息(如功能异常) |
| 警告 | log_w(...) | 警告信息(如参数不合理但不影响运行) |
| 信息 | log_i(...) | 一般信息(如初始化完成) |
| 调试 | log_d(...) | 调试信息(如中间变量值) |
| 详细 | log_v(...) | 详细日志(如函数调用过程) |
标准使用示例:
#define LOG_TAG "GPS"
#include <elog.h>
void gps_update(void) {
log_i("GPS模块启动"); // 信息日志
int signal = get_signal();
if (signal < 0) {
log_e("信号获取失败"); // 错误日志
} else if (signal < 30) {
log_w("信号弱(%d)", signal); // 警告日志
} else {
log_d("信号强度:%d", signal); // 调试日志
}
}
3.3 动态过滤:精准筛选日志
在调试时,可通过过滤功能只输出关注的日志,支持按级别、标签、关键词组合筛选。
过滤API汇总
| 过滤维度 | 函数 | 示例 |
|---|---|---|
| 级别 | elog_set_filter_lvl(level) | elog_set_filter_lvl(ELOG_LVL_INFO);(只输出INFO及以上) |
| 标签 | elog_set_filter_tag(tag) | elog_set_filter_tag("WIFI");(只输出WIFI标签) |
| 关键词 | elog_set_filter_kw(kw) | elog_set_filter_kw("timeout");(只输出含"timeout"的日志) |
| 组合过滤 | elog_set_filter(lvl, tag, kw) | elog_set_filter(ELOG_LVL_WARN, "GPS", "timeout"); |
高级功能:标签独立级别
可为不同标签设置独立输出级别,优先级高于全局级别:
elog_set_filter_lvl(ELOG_LVL_INFO); // 全局默认INFO
elog_set_filter_tag_lvl("GPS", ELOG_LVL_VERBOSE); // GPS输出详细日志
elog_set_filter_tag_lvl("CAN", ELOG_FILTER_LVL_SILENT); // CAN静默(不输出)
过滤优先级规则
- 标签独立级别(
elog_set_filter_tag_lvl)优先级最高; - 全局级别(
elog_set_filter_lvl)作为默认; - 标签过滤和关键词过滤为精确匹配。
四、移植实战:裸机平台适配
EasyLogger 移植到裸机平台(如STM32)仅需修改2个核心文件,步骤简单清晰。
4.1 移植接口文件:elog_port.c
需实现7个硬件相关接口函数,核心是日志输出和中断控制:
| 函数 | 说明 | 裸机实现要点 |
|---|---|---|
elog_port_init() | 移植初始化 | 初始化串口等输出外设(可空,若串口已在别处初始化) |
elog_port_deinit() | 移植反初始化 | 可为空 |
elog_port_output() | 日志输出接口 | 通过串口发送日志(如 printf 或直接操作UART寄存器) |
elog_port_output_lock() | 加锁 | 关中断防止日志输出被打断(如 __disable_irq()) |
elog_port_output_unlock() | 解锁 | 开中断恢复(如 __enable_irq()) |
elog_port_get_time() | 获取时间 | 返回时间字符串(可用SysTick计数或RTC,裸机可返回空) |
elog_port_get_p/t_info() | 进程/线程信息 | 裸机无进程/线程,返回空字符串 "" |
示例(STM32 裸机):
#include "elog.h"
#include <stdio.h>
// 初始化(串口已在main中初始化)
ElogErrCode elog_port_init(void) {
return ELOG_NO_ERR;
}
// 日志输出(通过串口1发送)
void elog_port_output(const char *log, size_t size) {
// 适配不支持 %.*s 的情况
char temp_buf[1024 + 1];
memcpy(temp_buf, log, size);
temp_buf[size] = '\\\\0';
fprintf(USART1_STREAM, "%s", temp_buf);
}
// 加锁(关中断)
void elog_port_output_lock(void) {
__disable_irq();
}
// 解锁(开中断)
void elog_port_output_unlock(void) {
__enable_irq();
}
// 获取时间(使用SysTick计数)
const char *elog_port_get_time(void) {
static char time_str[16];
sprintf(time_str, "%d", ticktick); // ticktick为全局计时变量
return time_str;
}
// 进程/线程信息(裸机返回空)
const char *elog_port_get_p_info(void) { return ""; }
const char *elog_port_get_t_info(void) { return ""; }
4.2 配置文件:elog_cfg.h
根据硬件资源(RAM/ROM)和需求修改配置:
/* 日志输出开关 */
#define ELOG_OUTPUT_ENABLE
/* 日志级别(裸机推荐ELOG_LVL_VERBOSE或ELOG_LVL_DEBUG) */
#define ELOG_OUTPUT_LVL ELOG_LVL_VERBOSE
/* 每行日志缓冲区大小(根据RAM调整,MCU资源少可设128-256) */
#define ELOG_LINE_BUF_SIZE 256
/* 换行符(嵌入式终端常用"\\\\r\\\\n") */
#define ELOG_NEWLINE_SIGN "\\\\\\\\r\\\\\\\\n"
/* 颜色开关(终端支持则开启) */
// #define ELOG_COLOR_ENABLE
/* 异步/缓冲输出(裸机一般关闭) */
// #define ELOG_ASYNC_OUTPUT_ENABLE
// #define ELOG_BUF_OUTPUT_ENABLE
4.3 工程配置与初始化
步骤1:添加源文件到工程
easylogger/src/elog.c // 核心功能
easylogger/src/elog_utils.c // 工具函数
elog_port.c // 你的移植文件
步骤2:初始化示例
#include "elog.h"
int main(void) {
// 初始化硬件(时钟、串口等)
System_Init();
UART_Init();
// 初始化EasyLogger
elog_init();
// 设置日志格式(按需配置)
elog_set_fmt(ELOG_LVL_ERROR, ELOG_FMT_LVL | ELOG_FMT_TAG | ELOG_FMT_TIME);
elog_set_fmt(ELOG_LVL_INFO, ELOG_FMT_LVL | ELOG_FMT_TAG | ELOG_FMT_TIME);
// 启动日志系统
elog_start();
elog_set_filter_lvl(ELOG_LVL_INFO); // 只输出 警告 及以上级别
// 测试日志输出
log_i("系统启动完成");
while(1) { }
}
五、总结
EasyLogger 凭借前后端分离的架构设计,实现了“一次开发,多平台适配”的灵活性。其核心优势包括:
- 跨平台兼容:支持裸机、RTOS、Linux等多种环境;
- 轻量高效:代码量小,资源占用低,适合嵌入式场景;
- 易用性强:通过标签和动态过滤简化调试流程;
- 可扩展性好:轻松扩展输出方式(串口/文件/网络等)。
无论是小型裸机项目还是复杂的嵌入式系统,EasyLogger 都能成为高效的日志调试助手。通过本文的讲解,相信你已掌握其核心原理和使用方法,不妨在实际项目中尝试应用,体验它带来的便捷!
Github链接:https://github.com/armink/EasyLogger
开源协议:MIT license

被折叠的 条评论
为什么被折叠?



