日志采集与传输:本地日志采集、远程日志传输(TCP/UDP/HTTP)、日志压缩与加密
日志这东西,平时没人看,一出问题就成救命稻草了。我在密钥烧录项目里吃过不少亏,日志不全、传丢了、被篡改,哪个都够你喝一壶的。今天咱们就把日志采集和传输这块掰开揉碎了讲清楚。
本地日志采集:第一道防线
日志采集,说白了就是把设备运行时的关键信息记下来。我习惯把日志分成三个等级:
| 等级 | 说明 | 我常用的场景 |
|---|---|---|
| INFO | 正常流程记录 | 烧录开始、密钥写入成功、校验通过 |
| WARN | 异常但可恢复 | 通信超时重试、校验码不一致但重试后通过 |
| ERROR | 不可恢复故障 | 硬件错误、密钥写入失败、安全违规 |
嗯,这里要注意:ERROR 级别的日志必须包含时间戳、设备ID、错误码和上下文数据。我曾经遇到过一个案子,设备报错但日志里只有"烧录失败"四个字,排查了三天才发现是电源波动导致的——要是当时多记个电压值,半小时就能定位。
本地日志采集的核心原则:
- 环形缓冲区:固定大小,新日志覆盖最旧的,防止撑爆存储
- 原子写入:每条日志必须完整写入,不能出现半条
- 分级过滤:生产环境只记录 WARN 及以上,调试时开 INFO
代码实现上,我推荐用这种结构:
typedef struct {
uint32_t timestamp;
uint8_t device_id[16];
uint8_t level; // 0=INFO, 1=WARN, 2=ERROR
uint16_t error_code;
uint8_t data_len;
uint8_t data[256]; // 上下文数据
uint32_t crc32; // 完整性校验
} log_entry_t;
为什么加 CRC32?你想想看,日志在存储过程中可能因为掉电、存储介质老化而损坏。没有校验,你拿到的就是一堆垃圾数据。
远程日志传输:把数据送出去
本地日志再全,人不去现场也白搭。远程传输才是真正的价值所在。我做过三种传输方式,各有各的脾气。
TCP 传输:可靠但重
TCP 适合关键日志,比如 ERROR 级别的。它保证数据不丢、不乱序。但代价是连接开销大,握手三次,断线重连也麻烦。
我的经验:TCP 传输一定要加心跳机制。我遇到过设备 TCP 连接处于半开状态,服务器以为还连着,设备其实早挂了。心跳间隔设 30 秒,连续三次无响应就重建连接。
UDP 传输:轻量但丢
UDP 适合大量 INFO 日志,丢了也不心疼。但要注意:UDP 包最大 1472 字节(以太网 MTU 1500 减去 IP 头 20 和 UDP 头 8)。超了就得分片,分片丢一个整个包就废了。
我一般这么处理:
// 每条日志单独一个 UDP 包
// 包结构:包序号(4字节) + 日志数据(最多1460字节)
// 接收端按序号排序,检测丢包
HTTP 传输:简单但慢
HTTP 适合设备有完整网络栈的场景。用 POST 请求把日志以 JSON 或二进制形式发到服务器。优点是防火墙友好,缺点嘛——每次请求都要建连接,效率低。
| 方式 | 可靠性 | 实时性 | 资源消耗 | 我推荐场景 |
|---|---|---|---|---|
| TCP | 高 | 中 | 高 | ERROR 日志、密钥操作记录 |
| UDP | 低 | 高 | 低 | INFO 日志、性能数据 |
| HTTP | 中 | 低 | 中 | 批量上报、非实时场景 |
日志压缩:省带宽就是省钱
设备多了,日志量是惊人的。一台烧录设备一天可能产生几十 MB 日志,一万台就是几百 GB。不压缩,传输成本和存储成本都扛不住。
我常用的压缩方案:
- LZ4:压缩速度极快,适合实时传输。压缩比一般 2:1 到 3:1
- zlib:压缩比高(4:1 到 6:1),但慢。适合非实时批量上传
- 自定义差分编码:连续日志有很多重复字段(设备ID、时间戳),只传变化的部分
注意:压缩和解压都要消耗 CPU。低端 MCU 上跑 zlib 可能把 CPU 占满,影响烧录主流程。我建议在 MCU 上用 LZ4,在网关上用 zlib 二次压缩。
日志加密:别让密钥裸奔
日志里可能包含密钥的哈希值、设备序列号、固件版本等敏感信息。明文传输等于把家底亮给黑客看。
我个人的加密策略:
- 传输层加密:TLS/DTLS 加密整个通道。这是基础,必须做
- 应用层加密:对日志中的敏感字段单独加密。即使通道被破解,数据还是安全的
- 完整性校验:HMAC 或数字签名,防止日志被篡改
// 应用层加密示例(AES-128-GCM)
// 加密日志中的密钥相关字段
uint8_t key_hash[32];
uint8_t encrypted_hash[48]; // 密文 + IV + tag
aes_gcm_encrypt(key_hash, sizeof(key_hash),
device_key, encrypted_hash);
为什么会这样设计?因为传输层加密只保护传输过程,日志落地后还是明文的。应用层加密让日志在服务器上也是密文,只有授权人员才能解密查看。
整体架构:一张图说清楚
下面这张图展示了日志从采集到传输的完整链路:
避坑指南
做日志传输这些年,我踩过的坑能写本书。挑几个典型的说说:
- 我曾经把日志缓冲区设成全局变量,结果多任务环境下日志乱序了。后来改成每个任务独立的日志缓冲区,再统一合并。
- 我曾经用 UDP 传 ERROR 日志,结果网络抖动丢了几条关键日志,排查事故时死活找不到原因。现在 ERROR 日志必须走 TCP。
- 我曾经忘了给日志加时间戳,结果日志到了服务器上,根本不知道哪条在前哪条在后。嗯,这个教训挺深刻的。
我的建议:日志系统上线前,一定要做压力测试。模拟设备满负荷运行,看日志会不会丢、会不会撑爆存储。我一般会连续跑 72 小时,检查日志的完整性和连续性。
日志采集与传输,看似简单,实则处处是坑。但只要把采集、压缩、加密、传输这四个环节都做到位,你的日志系统就能在关键时刻救你一命。