基于 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)