日志存储格式基础:文本日志、二进制日志、结构化日志
聊日志存储,先得搞清楚“怎么写”这个问题。
我做了这么多年存储优化,见过太多团队一上来就追求“最新方案”,结果连基础格式都没选对。其实,文本、二进制、结构化这三种格式,各有各的脾气。选对了,后续索引和压缩事半功倍;选错了,后面全得返工。
文本日志(Plain Text Log)
这是最原始的日志形式。就是一行一行的字符串,用换行符分隔。像这样:
2025-01-15 10:23:45.678 INFO [http-nio-8080-exec-3] 用户登录成功: userId=1024, ip=192.168.1.10
2025-01-15 10:23:46.012 WARN [scheduler-1] 数据库连接池使用率 85%, 建议扩容
2025-01-15 10:23:46.890 ERROR [http-nio-8080-exec-7] 订单创建失败: timeout=3000ms, orderId=8876
说白了,这种格式就是给人看的。你拿个 cat 或者 tail 就能读。我早期做运维时,排查问题全靠 grep 加 awk,那时候觉得文本日志就是全世界最好的东西。
优点很明显:
- 可读性极强,任何文本编辑器都能打开。
- 工具链成熟,
grep、sed、awk随便用。 - 跨平台没毛病,Windows 和 Linux 通用。
但是,坑也在这里:
- 没有固定结构。你没法“按字段查询”,只能整行匹配。
- 体积爆炸。同样的信息,文本比二进制大 5~10 倍。
- 解析性能差。我见过一个系统每秒写 20 万行日志,CPU 全耗在字符串拼接上了。
二进制日志(Binary Log)
二进制日志,就是直接往文件里写字节流。比如 Thrift、Protobuf 序列化后的数据,或者自定义的紧凑结构体。
我参与过一个游戏后端项目,每秒需要记录 50 万条玩家操作日志。文本格式根本扛不住,所以我们用了自定义的二进制格式,字段用固定长度编码,时间戳用 uint64,字符串用长度前缀+内容。效果立竿见影。
一个典型的二进制日志结构:
struct LogEntry {
uint64_t timestamp; // 8 字节
uint32_t thread_id; // 4 字节
uint16_t level; // 2 字节(INFO=0, WARN=1, ERROR=2)
uint32_t msg_len; // 4 字节
char msg[msg_len]; // 消息内容
};
二进制日志的优势:
- 体积小。同样的日志,二进制可能只有文本的 1/5。
- 解析快。读固定偏移量,不需逐字符扫描。
- 适合高性能场景。写 IO 次数少很多。
但是,二进制日志的缺点同样要命:
- 不可读。你用
cat看到的全是乱码。必须配专门的解析工具。 - 版本兼容麻烦。字段增减,解析器就得跟着改。我曾经因为二进制日志协议升级忘通知下游,导致监控系统挂了三天。
- 调试困难。线上出问题,你不能直接
grep。
结构化日志(Structured Log)
结构化日志是文本日志的升级版。它依然是人类可读的,但每个字段都有明确的键值对关系。最常见的格式就是 JSON:
{
"ts": "2025-01-15T10:23:45.678Z",
"level": "info",
"thread": "http-nio-8080-exec-3",
"msg": "用户登录成功",
"userId": 1024,
"ip": "192.168.1.10"
}
你想想看,这种格式的日志,本质上就是 半结构化数据。它兼具了文本的可读性和二进制的高效查询潜力。
我在重构公司统一日志平台时,把所有核心服务的日志都改成了 JSON 格式。当时团队里有人反对,说“好好的文本加个花括号干嘛”。但后来他们发现,用 Elasticsearch 直接解析 JSON 日志,不需要写复杂的 Grok 正则了,索引速度提升了 3 倍。
结构化日志的核心优势:
- 查询效率高。你直接对
userId字段建索引,不用再grep整行。 - 工具生态好。Logstash、Fluentd、Elasticsearch 全都原生支持 JSON。
- 容易扩展。加个新字段不影响老解析器。
要注意的问题:
- 体积比纯文本大一些(因为有键名和花括号)。
- 不规范的 JSON(比如多层嵌套、超大数组)会拖垮解析器。
- 写入时序列化开销比纯文本高一点。
三种格式怎么选?
没有银弹。我一般按这个规则来选:
| 格式类型 | 推荐场景 | 我的经验备注 |
|---|---|---|
| 文本日志 | 开发调试、小型项目、日志量 < 1GB/天 | 记得加固定列宽,方便 cut 命令处理 |
| 二进制日志 | 嵌入式设备、高频交易、日志量 > 100GB/天 | 必须设计好版本号字段,否则升级就是灾难 |
| 结构化日志 | 微服务架构、需要多维查询、统一日志平台 | 强烈推荐 JSON Lines(每行一个 JSON 对象),避免换行嵌套 |
epoch_ms(毫秒整数)。这样虽然读起来不如 ISO 8601 直观,但在排序和范围查询上快得多。我通常同时保留两个字段:@timestamp 给人看,ts_ms 给机器算。
其实,这三种格式并不是非此即彼的。我见过最好的方案是:写入时用结构化日志,归档时转成二进制压缩存储,调试时再转回文本。后两章我们就会讲到怎么打通这条链路。
上面这张图把三种格式的核心差异和适用场景串起来了。你能看到,从左到右,可读性递减,性能递增。大部分现代系统会选在“结构化日志”这个中间态——这也是我最推荐的起点。