4、本地日志存储:存储介质选型(eMMC/SD卡/NAND Flash)、日志文件轮转策略(大小/时间)、日志压缩与加密

大家好,我是你们的讲师。今天我们聊一个很实在的话题——日志到底存哪儿?怎么存?

很多刚入行的朋友觉得,日志嘛,随便找个地方写进去就行了。嗯,我当年也这么想。直到有一次,一台TBOX在高速上跑了三天,日志把Flash写穿了,整台设备直接变砖。从那以后,我对存储选型就格外上心。

4.1 存储介质选型:eMMC vs SD卡 vs NAND Flash

说白了,这三种介质各有各的脾气。我给大家拆开讲讲。

特性 eMMC SD卡 NAND Flash
接口 内置控制器,MMC协议 SPI/SDIO 裸NAND,需FTL
寿命 高(带磨损均衡) 中等 低(需软件管理)
成本 中等 最低
可靠性 中等(易松动) 低(坏块问题)
典型场景 车规级TBOX 后装/调试 低端设备

eMMC 是我个人最推荐的车规级方案。为什么?因为它自带控制器,帮你做了磨损均衡和坏块管理。我在项目中遇到过一台设备,用了三年,eMMC的寿命还剩80%以上。你想想看,这对TBOX这种7x24小时运行的设备来说,多重要。

SD卡 呢?便宜,方便。但我劝你,别用在量产车上。我曾经见过一台设备,SD卡在振动环境下接触不良,日志写到一半就断了。嗯,调试阶段用用还行,量产还是算了。

NAND Flash 是最便宜的,但也是最坑的。裸NAND没有FTL(Flash Translation Layer),你得自己处理坏块、擦写均衡。我刚开始做嵌入式时,觉得这有什么难的?结果写了一个月,发现坏块率飙升,数据全乱套了。后来我学乖了——除非成本压到极致,否则别碰裸NAND。

我的建议: 车规级TBOX首选eMMC,容量建议8GB起步。SD卡只用于调试或后装。NAND Flash?除非你团队有存储专家,否则别碰。

4.2 日志文件轮转策略:大小 vs 时间

日志不能无限写下去,对吧?你想想看,一个TBOX跑一年,日志能有多大?所以轮转策略是关键。

常见的策略有两种:按大小轮转,按时间轮转。我一般两种结合着用。

4.2.1 按大小轮转

这个逻辑很简单:每个日志文件设定一个上限,比如10MB。写满了就关掉,新建一个。我习惯保留最近10个文件,也就是总共100MB的日志空间。

// 伪代码示例:按大小轮转
#define LOG_FILE_MAX_SIZE  (10 * 1024 * 1024)  // 10MB
#define LOG_FILE_MAX_COUNT  10

void log_rotate_by_size() {
    if (current_file_size >= LOG_FILE_MAX_SIZE) {
        close_current_log();
        // 删除最旧的文件
        if (file_count >= LOG_FILE_MAX_COUNT) {
            delete_oldest_log();
        }
        create_new_log();
    }
}

这里有个坑:如果你只按大小轮转,万一某天日志量特别大,10个文件可能一天就写满了。所以我会加一个时间维度的保护。

4.2.2 按时间轮转

按时间轮转更直观:每天一个日志文件,或者每小时一个。我一般用每天一个,文件名带日期,比如 log_2024-01-15.txt

// 伪代码示例:按时间轮转
void log_rotate_by_time() {
    char new_filename[64];
    get_current_date_string(new_filename);  // 例如 "log_2024-01-15.txt"
    
    if (strcmp(current_filename, new_filename) != 0) {
        close_current_log();
        // 删除超过30天的旧日志
        delete_logs_older_than(30);
        open_new_log(new_filename);
    }
}

我个人习惯保留30天的日志。为什么是30天?因为大多数故障排查,一个月内的数据就够了。再老的日志,说实话,查到的概率很低。

避坑指南: 我曾经遇到过一个问题——按时间轮转时,如果设备断电了,当天的日志可能没写完。后来我加了一个机制:每次开机时检查上次日志文件的时间戳,如果跨天了就自动轮转。嗯,这个小细节救了我好几次。

4.3 日志压缩与加密

日志存下来了,但有两个问题:一是占空间,二是怕泄露。压缩和加密就是干这个的。

4.3.1 日志压缩

压缩算法我推荐两个:gziplz4

  • gzip:压缩率高,但慢。适合对空间敏感的场景。
  • lz4:压缩率一般,但极快。适合实时性要求高的场景。

我一般这么选:如果日志是后台批量上传的,用gzip。如果日志需要实时查看,用lz4。你想想看,TBOX的CPU资源有限,压缩太狠会影响其他任务。

// 伪代码示例:使用lz4压缩日志
#include <lz4.h>

void compress_log(const char* input, size_t input_size) {
    char* compressed = malloc(LZ4_compressBound(input_size));
    int compressed_size = LZ4_compress_default(input, compressed, input_size, LZ4_compressBound(input_size));
    
    // 将压缩后的数据写入文件
    write_to_file(compressed, compressed_size);
    free(compressed);
}

注意: 压缩后的日志文件,记得在文件名上加个后缀,比如 .gz.lz4。不然云端解析时,你都不知道该用什么解压。我当年就犯过这个错,上传了一堆无后缀的二进制文件,解析时一脸懵。

4.3.2 日志加密

加密是必须的。TBOX日志里可能包含车辆位置、用户行为等敏感信息。我推荐用 AES-256-GCM,既加密又带认证,防止篡改。

// 伪代码示例:AES-256-GCM加密日志
#include <openssl/evp.h>

void encrypt_log(const char* plaintext, size_t len, unsigned char* key, unsigned char* iv) {
    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, key, iv);
    
    unsigned char ciphertext[1024];
    int outlen;
    EVP_EncryptUpdate(ctx, ciphertext, &outlen, plaintext, len);
    
    unsigned char tag[16];
    EVP_EncryptFinal_ex(ctx, ciphertext + outlen, &outlen);
    EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag);
    
    // 存储 ciphertext + tag
    EVP_CIPHER_CTX_free(ctx);
}

密钥怎么管理?我习惯把密钥烧录在TBOX的SE(安全芯片)里,或者用HSM(硬件安全模块)。千万别把密钥硬编码在代码里——我见过有人这么干,结果固件被反编译,密钥直接暴露。嗯,那场面,挺尴尬的。

总结一下: 压缩和加密可以组合使用。我一般先压缩,再加密。这样既节省空间,又保证安全。顺序别搞反了——先加密再压缩,压缩率会大打折扣。

好了,这一节的内容就到这里。存储选型、轮转策略、压缩加密,这三块是本地日志存储的核心。下一节我们聊聊云端同步的协议和机制。有什么问题,欢迎在评论区留言。