EasyLogger 详解:从架构设计到实战应用

在嵌入式开发中,日志系统是调试和问题定位的重要工具。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.csrc/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"))  (添加时间/标签/级别)  (符合条件则输出)   (串口发送)   (终端显示)

关键数据流说明:

  1. 应用层通过 log_x 宏生成日志请求,传入标签、格式字符串和参数;
  2. 核心模块在 elog_output 函数中组装日志(级别、时间、标签等),存入缓冲区 log_buf
  3. 核心模块调用 elog_port_output,将缓冲区数据传递给移植接口;
  4. 移植接口通过硬件驱动(如 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静默(不输出)

过滤优先级规则

  1. 标签独立级别(elog_set_filter_tag_lvl)优先级最高;
  2. 全局级别(elog_set_filter_lvl)作为默认;
  3. 标签过滤和关键词过滤为精确匹配。

四、移植实战:裸机平台适配

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值