ARM环境日志系统的简单设计思路
在ARM系统开发中(各种架构的嵌入式系统开发也同理),日志系统是至关重要的调试和诊断工具。 一个设计良好的日志系统能够帮助开发者快速定位问题、分析系统行为并监控运行状态。核心需求如下:
1. 多级别日志:支持不同重要程度的日志级别(DEBUG、INFO、WARN、ERROR等)2. 低资源占用:内存占用小,CPU开销低 3. 实时性:不影响主程序实时性能 4. 多种输出方式:支持串口、文件系统、网络等多种输出 5. 时间戳:提供精确的时间信息 6. 线程/任务安全:多任务环境下安全使用 7. 低功耗模式:在节能模式下仍能正常工作
架构设计采用分层架构设计,接口层:提供日志API,处理层:格式化、过滤、分级处理,输出层:控制日志输出目的地。
核心要点:一个分等级的ARM日志系统,包含日志级别管理、格式化输出、时间戳等功能。
日志级别:DEBUG、INFO、WARN、ERROR、FATAL
输出控制:可设置当前日志级别,过滤低级别日志
格式规范:包含时间戳、级别、文件、行号等信息
性能考虑:使用宏定义避免低级别日志的函数调用开销
数据结构
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
// 日志系统初始化
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, ...);
宏简化调用
#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__)
环形缓冲区实现
/*环形缓冲区实现 为避免日志输出阻塞主程序,实现环形缓冲区*/#define LOG_BUFFER_SIZE 1024typedef 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格式化;
视觉优化:支持彩色输出(可选);
/*** 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;
}
这个一般用例可以进行如下高级功能扩展
环形缓冲区日志(避免阻塞)
#include <pthread.h>#define LOG_BUFFER_SIZE 1024
#define LOG_QUEUE_SIZE 100typedef 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);
}
文件输出支持
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); // 确保立即写入
}
按级别过滤的宏优化版本
// 编译时级别检查 - 完全消除低级别日志的运行时开销
#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
用例测试
// 初始化示例
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, "数据必须为非负数");
}
日志系统的优化
/*日志过滤 实现基于模块、级别和关键词的过滤:*/
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 • 避免在关键路径中调用格式化函数 • 使用位操作代替除法和乘法
功耗优化 • 实现日志批处理,减少设备唤醒次数 • 在低功耗模式下禁用非关键日志 • 动态调整日志级别基于电源状态
/*日志压缩 在存储到文件系统前进行简单压缩:*/
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());
}
测试
/*单元测试 编写测试用例验证核心功能:*/
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
/*与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);
生产环境考量
• 提供机制动态调整日志级别而不重启系统 • 实现日志远程传输和集中管理 • 添加日志旋转和自动清理功能
可以根据具体项目需求进行扩展和优化。 良好的日志系统不仅能加速调试过程,还能在产品部署后提供宝贵的运行洞察,是嵌入式系统不可或缺的组成部分。通过遵循分层设计原则、实现适当的抽象和提供灵活的配置选项,可以创建出既强大又高效的日志系统,满足从开发调试到生产监控的全生命周期需求。