超过 1MB 的文件可选择压缩或分片存储 ​ --- ​ ## 文件系统容量计算 ​ 假设 littlefs 分区大小为 256KB: - 每个文件最大 1M

0 阅读5分钟

基于 littlefs 的日志存储解决方案

项目架构概览

littlefs 适配层(现有)

本项目在 src/middleware/chips/ws63/littlefs/ 中已提供了 littlefs 的适配层:

主要 API 函数:

fs_adapt_mount()              // 挂载文件系统
fs_adapt_unmount()            // 卸载文件系统
fs_adapt_open(path, flags)    // 打开文件:返回文件描述符 (fd)
fs_adapt_close(fd)            // 关闭文件
fs_adapt_write(fd, buf, len)  // 写入数据:成功返回写入字节数
fs_adapt_read(fd, buf, len)   // 读取数据:成功返回读取字节数
fs_adapt_seek(fd, offset, whence)  // 文件指针定位
fs_adapt_stat(path, &size)    // 获取文件大小
fs_adapt_delete(path)         // 删除文件
fs_adapt_mkdir(path)          // 创建目录
fs_adapt_sync(fd)             // 文件同步到存储

日志存储管理方案

核心功能需求

✅ 存储 20+ 个日志文件
✅ 根据时间戳命名文件
✅ 自动文件轮转(超过 1MB 自动创建新文件)
✅ 支持按文件名读取日志
✅ 支持日志追加写入

实现步骤

1️⃣ 初始化与挂载
#include "littlefs_adapt.h"

int init_log_storage(void)
{
    // 1. 挂载 littlefs 文件系统
    fs_adapt_mount();
    
    // 2. 创建日志目录
    int ret = fs_adapt_mkdir("/logs");
    if (ret != LFS_ERR_OK && ret != LFS_ERR_EXIST) {
        return -1;
    }
    
    return 0;
}

2️⃣ 生成时间戳文件名

示例:生成文件名如 2025-02-06_143022.log

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

// 生成时间戳格式的文件名
void generate_log_filename(char *filename, size_t max_len)
{
    time_t now = time(NULL);
    struct tm *timeinfo = localtime(&now);
    
    snprintf(filename, max_len, "/logs/%04d%02d%02d_%02d%02d%02d.log",
             timeinfo->tm_year + 1900,
             timeinfo->tm_mon + 1,
             timeinfo->tm_mday,
             timeinfo->tm_hour,
             timeinfo->tm_min,
             timeinfo->tm_sec);
}

3️⃣ 日志写入(带自动轮转)
#define LOG_FILE_MAX_SIZE (1024 * 1024)  // 1MB

int write_log(const char *log_content)
{
    char filename[64];
    unsigned int file_size = 0;
    
    // 获取当前日志文件名
    generate_log_filename(filename, sizeof(filename));
    
    // 检查文件是否存在及大小
    if (fs_adapt_stat(filename, &file_size) == 0) {
        // 文件存在,检查是否超过 1MB
        if (file_size >= LOG_FILE_MAX_SIZE) {
            // 文件已满,生成新文件名(加时间戳)
            char new_filename[64];
            time_t now = time(NULL);
            struct tm *timeinfo = localtime(&now);
            
            snprintf(new_filename, sizeof(new_filename), 
                     "/logs/%04d%02d%02d_%02d%02d%02d_%lu.log",
                     timeinfo->tm_year + 1900,
                     timeinfo->tm_mon + 1,
                     timeinfo->tm_mday,
                     timeinfo->tm_hour,
                     timeinfo->tm_min,
                     timeinfo->tm_sec,
                     (unsigned long)now);  // 添加时间戳避免重名
            strcpy(filename, new_filename);
        }
    }
    
    // 以追加模式打开文件(O_CREAT | O_APPEND)
    int fd = fs_adapt_open(filename, O_RDWR | O_CREAT | O_APPEND);
    if (fd < 0) {
        return -1;
    }
    
    // 写入日志内容
    int ret = fs_adapt_write(fd, log_content, strlen(log_content));
    if (ret > 0) {
        // 写入换行符
        fs_adapt_write(fd, "\n", 1);
    }
    
    // 同步到存储(确保数据不丢失)
    fs_adapt_sync(fd);
    
    // 关闭文件
    fs_adapt_close(fd);
    
    return ret;
}

4️⃣ 按文件名读取日志
// 定义缓冲区大小
#define LOG_BUFFER_SIZE (4096)

ssize_t read_log_file(const char *filename, char *buffer, size_t max_len)
{
    // 完整路径
    char filepath[64];
    snprintf(filepath, sizeof(filepath), "/logs/%s", filename);
    
    // 以只读模式打开文件
    int fd = fs_adapt_open(filepath, O_RDONLY);
    if (fd < 0) {
        return -1;  // 文件不存在
    }
    
    // 从文件开头读取
    ssize_t bytes_read = fs_adapt_read(fd, buffer, max_len - 1);
    
    if (bytes_read > 0) {
        buffer[bytes_read] = '\0';  // 字符串空终止
    }
    
    fs_adapt_close(fd);
    
    return bytes_read;
}

5️⃣ 列出所有日志文件
#include <stdlib.h>

typedef struct {
    char filename[64];
    unsigned int size;
} log_file_info_t;

// 列出 /logs 目录下的所有文件
int list_all_logs(log_file_info_t *log_files, int max_count, int *actual_count)
{
    lfs_dir_t dir;
    struct lfs_info info;
    int count = 0;
    
    // 打开 /logs 目录(需要使用原生 littlefs API)
    // 注意:当前适配层不提供目录遍历,需要扩展
    
    // 暂时使用文件时间戳约定来管理
    // 应用层可维护一个日志文件索引表
    
    return count;
}

完整使用示例

#include "littlefs_adapt.h"
#include <stdio.h>
#include <string.h>
#include <time.h>

// 完整的日志管理示例
void log_management_example(void)
{
    // 1️⃣ 初始化
    init_log_storage();
    
    // 2️⃣ 写入日志(模拟多个日志条目)
    for (int i = 0; i < 100; i++) {
        char log_str[256];
        snprintf(log_str, sizeof(log_str), 
                 "[INFO] Log entry %d - Timestamp: %ld", i, time(NULL));
        
        write_log(log_str);
        
        // 模拟延迟
        usleep(10000);
    }
    
    // 3️⃣ 读取特定日志文件
    char buffer[4096];
    ssize_t ret = read_log_file("20250206_143022.log", buffer, sizeof(buffer));
    if (ret > 0) {
        printf("Log content:\n%s\n", buffer);
    }
    
    // 4️⃣ 卸载文件系统
    fs_adapt_unmount();
}

关键 API 调用总结

操作API 调用说明
初始化fs_adapt_mount()文件系统初始化
创建目录fs_adapt_mkdir("/logs")创建日志目录
打开文件fs_adapt_open(path, O_RDWR|O_CREAT|O_APPEND)以追加模式打开
写入数据fs_adapt_write(fd, buf, len)写入日志内容
同步数据fs_adapt_sync(fd)确保数据刷新到 Flash
关闭文件fs_adapt_close(fd)关闭文件描述符
读取数据fs_adapt_read(fd, buf, len)读取文件内容
检查大小fs_adapt_stat(path, &size)获取文件大小
删除文件fs_adapt_delete(path)删除旧日志
定位指针fs_adapt_seek(fd, offset, SEEK_SET)移动文件指针

高级特性建议

1. 日志文件索引管理

维护内存中的索引表,记录所有日志文件及其元数据:

typedef struct {
    char filename[64];
    unsigned int size;
    time_t created_time;
    time_t last_modified;
} log_index_t;

2. 自动清理机制

当日志总数超过 20 个时,删除最旧的文件:

void cleanup_old_logs(int max_files)
{
    // 按修改时间排序
    // 删除超过数量的最旧文件
}

3. 环形缓冲优化

对于高频日志场景,使用环形缓冲区减少 I/O:

typedef struct {
    char buffer[4096];
    int write_pos;
    int flush_threshold;
} log_circular_buffer_t;

4. 日志压缩

超过 1MB 的文件可选择压缩或分片存储


文件系统容量计算

假设 littlefs 分区大小为 256KB:

  • 每个文件最大 1MB (可配置)
  • 可存储约 20-25 个 日志文件
  • 需在 littlefs_config.h 中配置适当的分区大小

常见问题

Q: 如何确保日志不丢失?
A: 每次写入后调用 fs_adapt_sync(fd) 强制刷新到 Flash

Q: 如何处理文件名冲突?
A: 添加微秒级时间戳或序列号后缀

Q: 如何快速查询特定时间段的日志?
A: 解析文件名中的时间戳,或维护时间索引表

Q: littlefs 性能如何?
A: 小文件性能优秀,适合 MCU 场景(延迟 < 10ms)