ARM环境日志系统的简单设计思路

在ARM系统开发中(各种架构的嵌入式系统开发也同理),日志系统是至关重要的调试和诊断工具。 一个设计良好的日志系统能够帮助开发者快速定位问题、分析系统行为并监控运行状态。核心需求如下:

  1. 多级别日志:支持不同重要程度的日志级别(DEBUG、INFO、WARN、ERROR等)2. 低资源占用:内存占用小,CPU开销低 3. 实时性:不影响主程序实时性能 4. 多种输出方式:支持串口、文件系统、网络等多种输出 5. 时间戳:提供精确的时间信息 6. 线程/任务安全:多任务环境下安全使用 7. 低功耗模式:在节能模式下仍能正常工作

架构设计采用分层架构设计,接口层:提供日志API,处理层:格式化、过滤、分级处理,输出层:控制日志输出目的地。

核心要点:一个分等级的ARM日志系统,包含日志级别管理、格式化输出、时间戳等功能。

日志级别:DEBUG、INFO、WARN、ERROR、FATAL

输出控制:可设置当前日志级别,过滤低级别日志

格式规范:包含时间戳、级别、文件、行号等信息

性能考虑:使用宏定义避免低级别日志的函数调用开销

数据结构

cpp 复制代码
typedef enum 
{
    LOG_LEVEL_DEBUG = 0,
    LOG_LEVEL_INFO,
    LOG_LEVEL_WARNING,
    LOG_LEVEL_ERROR,
    LOG_LEVEL_CRITICAL,
    LOG_LEVEL_NONE // 禁用所有日志
} log_level_t;


typedef struct 
{
    uint32_t timestamp;
    log_level_t level;
    uint16_t line_number;
    const char *filename;
    const char *function;
    char message[LOG_MAX_MESSAGE_LENGTH];
} log_entry_t;

typedef struct 
{
    void (*output_func)(const log_entry_t*);
    log_level_t current_level;
    bool enabled;
    uint32_t dropped_count; // 丢弃的日志计数
} logger_t;

核心API

cpp 复制代码
// 日志系统初始化
void log_init(log_level_t default_level, 
              void (*output_func)(const log_entry_t*));

// 设置日志级别
void log_set_level(log_level_t level);

// 核心日志函数
void log_write(log_level_t level, 
               const char* filename, 
               uint16_t line,
               const char* function,
               const char* format, ...);

宏简化调用

cpp 复制代码
#define LOG_DEBUG(format, ...) \
    log_write(LOG_LEVEL_DEBUG, __FILE__, __LINE__, __func__, format, ##__VA_ARGS__)

#define LOG_INFO(format, ...) \
    log_write(LOG_LEVEL_INFO, __FILE__, __LINE__, __func__, format, ##__VA_ARGS__)

#define LOG_WARN(format, ...) \
    log_write(LOG_LEVEL_WARNING, __FILE__, __LINE__, __func__, format, ##__VA_ARGS__)

#define LOG_ERROR(format, ...) \
    log_write(LOG_LEVEL_ERROR, __FILE__, __LINE__, __func__, format, ##__VA_ARGS__)

环形缓冲区实现

cpp 复制代码
/*环形缓冲区实现 为避免日志输出阻塞主程序,实现环形缓冲区*/

#define LOG_BUFFER_SIZE 1024

typedef struct 
{
    log_entry_t entries[LOG_BUFFER_SIZE];
    uint32_t head;
    uint32_t tail;
    bool full;
} log_buffer_t;

// 初始化缓冲区
void log_buffer_init(log_buffer_t* buffer);

// 写入日志到缓冲区
bool log_buffer_put(log_buffer_t* buffer, const log_entry_t* entry);

// 从缓冲区读取日志
bool log_buffer_get(log_buffer_t* buffer, log_entry_t* entry);



/*输出处理线程 创建专用线程处理日志输出:*/
void log_output_task(void* argument) {
    log_entry_t entry;
    
    while(1) {
        if(log_buffer_get(&log_buffer, &entry)) {
            // 调用输出函数
            if(logger.output_func != NULL) {
                logger.output_func(&entry);
            }
        } else {
            // 缓冲区空,休眠等待
            osDelay(10);
        }
    }
}


/*格式化输出函数*/
void log_format_default(const log_entry_t* entry, char* buffer, size_t size) {
    const char* level_str;
    
    switch(entry->level) {
        case LOG_LEVEL_DEBUG: level_str = "DEBUG"; break;
        case LOG_LEVEL_INFO: level_str = "INFO"; break;
        case LOG_LEVEL_WARNING: level_str = "WARN"; break;
        case LOG_LEVEL_ERROR: level_str = "ERROR"; break;
        case LOG_LEVEL_CRITICAL: level_str = "CRITICAL"; break;
        default: level_str = "UNKNOWN"; break;
    }
    
    snprintf(buffer, size, "[%08lu][%s] %s:%d (%s) - %s",
             entry->timestamp,
             level_str,
             entry->filename,
             entry->line_number,
             entry->function,
             entry->message);
}

基础实现的一般用例

可以根据具体的ARM平台需求进行调整,比如替换时间函数、输出函数等。

多级别控制:支持5个标准日志级别;

性能优化:使用宏定义避免低级别日志的函数调用开销;

丰富信息:包含时间戳、文件、行号等定位信息;

可扩展性:支持自定义输出处理器;

线程安全:可扩展为线程安全版本;

格式化支持:支持变参和标准printf格式化;

视觉优化:支持彩色输出(可选);

cpp 复制代码
/**
 * ARM日志系统
 * 支持多级别日志输出,包含时间戳和代码位置信息
 */

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>

// 日志级别定义
typedef enum {
    LOG_LEVEL_DEBUG = 0,
    LOG_LEVEL_INFO,
    LOG_LEVEL_WARN,
    LOG_LEVEL_ERROR,
    LOG_LEVEL_FATAL,
    LOG_LEVEL_NONE  // 禁用所有日志
} log_level_t;

// 日志系统配置
typedef struct {
    log_level_t current_level;
    int enable_timestamp;
    int enable_color;
    void (*output_handler)(const char*);
} log_config_t;

// 全局配置
static log_config_t g_log_config = {
    .current_level = LOG_LEVEL_DEBUG,
    .enable_timestamp = 1,
    .enable_color = 1,
    .output_handler = NULL
};

// 颜色定义 (ANSI)
#define COLOR_DEBUG   "\033[36m"  // 青色
#define COLOR_INFO    "\033[32m"  // 绿色
#define COLOR_WARN    "\033[33m"  // 黄色
#define COLOR_ERROR   "\033[31m"  // 红色
#define COLOR_FATAL   "\033[35m"  // 洋红
#define COLOR_RESET   "\033[0m"   // 重置

// 级别字符串
static const char* level_strings[] = {
    "DEBUG", "INFO", "WARN", "ERROR", "FATAL"
};

// 级别颜色
static const char* level_colors[] = {
    COLOR_DEBUG, COLOR_INFO, COLOR_WARN, COLOR_ERROR, COLOR_FATAL
};

/**
 * 初始化日志系统
 */
void log_init(log_level_t level, int enable_timestamp, int enable_color, 
              void (*output_handler)(const char*)) {
    g_log_config.current_level = level;
    g_log_config.enable_timestamp = enable_timestamp;
    g_log_config.enable_color = enable_color;
    g_log_config.output_handler = output_handler;
}

/**
 * 设置日志级别
 */
void log_set_level(log_level_t level) {
    g_log_config.current_level = level;
}

/**
 * 获取当前时间字符串
 */
static void get_timestamp_string(char* buffer, int size) {
    time_t rawtime;
    struct tm* timeinfo;
    
    time(&rawtime);
    timeinfo = localtime(&rawtime);
    
    strftime(buffer, size, "%Y-%m-%d %H:%M:%S", timeinfo);
}

/**
 * 默认输出处理函数
 */
static void default_output_handler(const char* message) {
    printf("%s", message);
}

/**
 * 核心日志函数
 */
void log_output(log_level_t level, const char* file, int line, const char* format, ...) {
    // 级别检查
    if (level < g_log_config.current_level) {
        return;
    }
    
    char message[512];
    char timestamp[32];
    va_list args;
    
    // 获取时间戳
    if (g_log_config.enable_timestamp) {
        get_timestamp_string(timestamp, sizeof(timestamp));
    }
    
    // 构建日志前缀
    int prefix_len = 0;
    
    // 时间戳
    if (g_log_config.enable_timestamp) {
        prefix_len += snprintf(message + prefix_len, sizeof(message) - prefix_len, 
                              "[%s] ", timestamp);
    }
    
    // 级别(带颜色)
    if (g_log_config.enable_color) {
        prefix_len += snprintf(message + prefix_len, sizeof(message) - prefix_len, 
                              "%s%-5s%s ", level_colors[level], 
                              level_strings[level], COLOR_RESET);
    } else {
        prefix_len += snprintf(message + prefix_len, sizeof(message) - prefix_len, 
                              "%-5s ", level_strings[level]);
    }
    
    // 文件位置
    prefix_len += snprintf(message + prefix_len, sizeof(message) - prefix_len, 
                          "%s:%d: ", file, line);
    
    // 格式化日志内容
    va_start(args, format);
    vsnprintf(message + prefix_len, sizeof(message) - prefix_len, format, args);
    va_end(args);
    
    // 确保以换行结束
    int total_len = strlen(message);
    if (total_len < sizeof(message) - 2 && message[total_len - 1] != '\n') {
        strcat(message, "\n");
    }
    
    // 输出日志
    if (g_log_config.output_handler) {
        g_log_config.output_handler(message);
    } else {
        default_output_handler(message);
    }
    
    // FATAL级别退出程序
    if (level == LOG_LEVEL_FATAL) {
        exit(1);
    }
}

// 日志宏定义 - 自动获取文件和行号信息
#define LOG_DEBUG(format, ...) \
    log_output(LOG_LEVEL_DEBUG, __FILE__, __LINE__, format, ##__VA_ARGS__)

#define LOG_INFO(format, ...) \
    log_output(LOG_LEVEL_INFO, __FILE__, __LINE__, format, ##__VA_ARGS__)

#define LOG_WARN(format, ...) \
    log_output(LOG_LEVEL_WARN, __FILE__, __LINE__, format, ##__VA_ARGS__)

#define LOG_ERROR(format, ...) \
    log_output(LOG_LEVEL_ERROR, __FILE__, __LINE__, format, ##__VA_ARGS__)

#define LOG_FATAL(format, ...) \
    log_output(LOG_LEVEL_FATAL, __FILE__, __LINE__, format, ##__VA_ARGS__)

// 断言宏
#define LOG_ASSERT(condition, format, ...) \
    do { \
        if (!(condition)) { \
            LOG_FATAL("Assertion failed: " format, ##__VA_ARGS__); \
        } \
    } while(0)

// 示例用法
int main() {
    // 初始化日志系统
    log_init(LOG_LEVEL_DEBUG, 1, 1, NULL);
    
    LOG_DEBUG("这是一条调试信息");
    LOG_INFO("系统初始化完成");
    LOG_WARN("内存使用率较高: %d%%", 85);
    LOG_ERROR("打开文件失败: %s", "test.txt");
    
    // 改变日志级别
    log_set_level(LOG_LEVEL_WARN);
    LOG_INFO("这条信息不会被显示");  // 级别低于WARN,不会输出
    LOG_WARN("只有警告和更高级别的日志会显示");
    
    // 测试断言
    int value = 10;
    LOG_ASSERT(value == 10, "value should be 10, but got %d", value);
    
    return 0;
}

这个一般用例可以进行如下高级功能扩展

环形缓冲区日志(避免阻塞)

cpp 复制代码
#include <pthread.h>

#define LOG_BUFFER_SIZE 1024
#define LOG_QUEUE_SIZE 100

typedef struct {
    char messages[LOG_QUEUE_SIZE][LOG_BUFFER_SIZE];
    int head;
    int tail;
    int count;
    pthread_mutex_t mutex;
} log_queue_t;

static log_queue_t g_log_queue;

void log_queue_init() {
    g_log_queue.head = 0;
    g_log_queue.tail = 0;
    g_log_queue.count = 0;
    pthread_mutex_init(&g_log_queue.mutex, NULL);
}

void log_async_output(const char* message) {
    pthread_mutex_lock(&g_log_queue.mutex);
    
    if (g_log_queue.count < LOG_QUEUE_SIZE) {
        strncpy(g_log_queue.messages[g_log_queue.tail], message, LOG_BUFFER_SIZE - 1);
        g_log_queue.messages[g_log_queue.tail][LOG_BUFFER_SIZE - 1] = '\0';
        g_log_queue.tail = (g_log_queue.tail + 1) % LOG_QUEUE_SIZE;
        g_log_queue.count++;
    }
    
    pthread_mutex_unlock(&g_log_queue.mutex);
}

文件输出支持

cpp 复制代码
void log_to_file(const char* message) {
    static FILE* log_file = NULL;
    
    if (log_file == NULL) {
        log_file = fopen("system.log", "a");
        if (log_file == NULL) {
            return;
        }
    }
    
    fprintf(log_file, "%s", message);
    fflush(log_file);  // 确保立即写入
}

按级别过滤的宏优化版本

cpp 复制代码
// 编译时级别检查 - 完全消除低级别日志的运行时开销
#if LOG_COMPILE_LEVEL <= LOG_LEVEL_DEBUG
#define LOG_DEBUG_OPT(format, ...) \
    log_output(LOG_LEVEL_DEBUG, __FILE__, __LINE__, format, ##__VA_ARGS__)
#else
#define LOG_DEBUG_OPT(format, ...) ((void)0)
#endif

用例测试

cpp 复制代码
// 初始化示例
void system_init() {
    // 启用时间戳,禁用颜色,输出到文件
    log_init(LOG_LEVEL_INFO, 1, 0, log_to_file);
    
    LOG_INFO("=== 系统启动 ===");
    LOG_DEBUG("硬件初始化中...");  // 不会输出,因为级别是INFO
}

void process_data(int data) {
    LOG_DEBUG("处理数据: %d", data);
    
    if (data < 0) {
        LOG_WARN("接收到异常数据: %d", data);
    }
    
    if (data == -1) {
        LOG_ERROR("数据格式错误");
        return;
    }
    
    // 关键检查
    LOG_ASSERT(data >= 0, "数据必须为非负数");
}

日志系统的优化

cpp 复制代码
/*日志过滤 实现基于模块、级别和关键词的过滤:*/
typedef struct {
    const char* module_name;
    log_level_t min_level;
} log_filter_t;

void log_add_filter(const char* module, log_level_t min_level);
bool log_check_filter(const char* filename, log_level_t level);

(异步日志处理,使用DMA或专用硬件实现异步串口输出,减少CPU占用。)

内存优化 • 使用静态内存分配避免碎片 • 优化字符串存储,避免重复 • 实现日志消息池复用内存

性能优化 • 减少字符串操作,使用memcpy代替strcpy • 避免在关键路径中调用格式化函数 • 使用位操作代替除法和乘法

功耗优化 • 实现日志批处理,减少设备唤醒次数 • 在低功耗模式下禁用非关键日志 • 动态调整日志级别基于电源状态

cpp 复制代码
/*日志压缩 在存储到文件系统前进行简单压缩:*/
void log_compress_entry(const log_entry_t* entry, uint8_t* output, size_t* output_size);

/*系统状态日志 自动记录关键系统状态:*/
void log_system_status(void) {
    LOG_INFO("System status - Heap free: %lu, CPU usage: %d%%", 
             get_free_heap(), get_cpu_usage());
}

测试

cpp 复制代码
/*单元测试 编写测试用例验证核心功能:*/
void test_log_level_filtering(void) {
    log_set_level(LOG_LEVEL_INFO);
    
    LOG_DEBUG("This should not appear"); // 应被过滤
    LOG_INFO("This should appear"); // 应显示
    
    // 验证实际输出
}

/*性能测试 测量最坏情况下的执行时间和内存使用:*/
void test_log_performance(void) {
    uint32_t start_time = get_current_time();
    
    for(int i = 0; i < 1000; i++) {
        LOG_INFO("Performance test message %d", i);
    }
    
    uint32_t duration = get_current_time() - start_time;
    printf("1000 logs took %lu ms\n", duration);
}

/*压力测试 测试缓冲区满时的行为:*/
void test_log_stress(void) {
    log_set_level(LOG_LEVEL_DEBUG);
    
    // 快速产生大量日志填满缓冲区
    for(int i = 0; i < LOG_BUFFER_SIZE * 2; i++) {
        LOG_DEBUG("Stress test message %d", i);
    }
    
    // 验证系统没有崩溃且记录了丢弃的日志数量
}

部署到RTOS

cpp 复制代码
/*与RTOS集成 提供RTOS特定适配层:*/

#ifdef USE_FREERTOS
#include "FreeRTOS.h"
#define LOG_MUTEX_TYPE SemaphoreHandle_t
#define LOG_MUTEX_CREATE() xSemaphoreCreateMutex()
#define LOG_MUTEX_LOCK(m) xSemaphoreTake(m, portMAX_DELAY)
#define LOG_MUTEX_UNLOCK(m) xSemaphoreGive(m)
#endif

/*配置系统 提供编译时和运行时配置选项:*/
typedef struct {
    log_level_t default_level;
    size_t buffer_size;
    bool enable_timestamp;
    bool enable_filename;
    output_destination_t destination;
} log_config_t;

void log_configure(const log_config_t* config);

生产环境考量

• 提供机制动态调整日志级别而不重启系统 • 实现日志远程传输和集中管理 • 添加日志旋转和自动清理功能

可以根据具体项目需求进行扩展和优化。 良好的日志系统不仅能加速调试过程,还能在产品部署后提供宝贵的运行洞察,是嵌入式系统不可或缺的组成部分。通过遵循分层设计原则、实现适当的抽象和提供灵活的配置选项,可以创建出既强大又高效的日志系统,满足从开发调试到生产监控的全生命周期需求。

相关推荐
jiunian_cn3 小时前
【Linux网络】IP协议
linux·网络·tcp/ip
tt5555555555556 小时前
Linux启动流程与字符设备驱动详解 - 从bootloader到驱动开发
linux·运维·驱动开发
一只游鱼8 小时前
linux使用yum安装数据库
linux·mysql·adb
光电笑映9 小时前
C++list全解析
c语言·开发语言·数据结构·c++·list
大白的编程日记.10 小时前
【Linux学习笔记】线程概念和控制(三)
linux·笔记·学习
小龙报10 小时前
《构建模块化思维---函数(下)》
c语言·开发语言·c++·算法·visualstudio·学习方法
say_fall11 小时前
C语言底层学习(4.数据在内存中的存储)
c语言·学习
9523611 小时前
数据结构—双链表
c语言·开发语言·数据结构·学习
L_090712 小时前
【Linux】Linux 常用指令2
linux·服务器