日志存储格式基础:文本日志、二进制日志、结构化日志

聊日志存储,先得搞清楚“怎么写”这个问题。

我做了这么多年存储优化,见过太多团队一上来就追求“最新方案”,结果连基础格式都没选对。其实,文本、二进制、结构化这三种格式,各有各的脾气。选对了,后续索引和压缩事半功倍;选错了,后面全得返工。

文本日志(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 全耗在字符串拼接上了。
我曾经遇到过一个线上事故:某电商大促时,日志系统因为文本解析太慢,导致业务线程阻塞。最后发现,仅仅是把日志格式从纯文本改成结构化 JSON,CPU 占用就从 85% 降到了 20%。血的教训。

二进制日志(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(比如多层嵌套、超大数组)会拖垮解析器。
  • 写入时序列化开销比纯文本高一点。
避坑指南:我曾经在一个高并发服务里,把日志字段设计成了 5 层嵌套。结果就是,写入 JSON 序列化占用了 15% 的 CPU。后来我拍平了结构,只保留 2 层,CPU 占用降到了 3%。规则很简单:日志字段越平越好,避免冗余嵌套。

三种格式怎么选?

没有银弹。我一般按这个规则来选:

格式类型 推荐场景 我的经验备注
文本日志 开发调试、小型项目、日志量 < 1GB/天 记得加固定列宽,方便 cut 命令处理
二进制日志 嵌入式设备、高频交易、日志量 > 100GB/天 必须设计好版本号字段,否则升级就是灾难
结构化日志 微服务架构、需要多维查询、统一日志平台 强烈推荐 JSON Lines(每行一个 JSON 对象),避免换行嵌套
一个小技巧:如果你用结构化日志,建议在写入时就把时间戳转成 epoch_ms(毫秒整数)。这样虽然读起来不如 ISO 8601 直观,但在排序和范围查询上快得多。我通常同时保留两个字段:@timestamp 给人看,ts_ms 给机器算。

其实,这三种格式并不是非此即彼的。我见过最好的方案是:写入时用结构化日志,归档时转成二进制压缩存储,调试时再转回文本。后两章我们就会讲到怎么打通这条链路。

日志存储格式演进与选择路径 日志存储格式 文本日志 结构化日志 二进制日志 特性 • 人类可读,随时用 cat/less 查看 • 无固定结构,提取字段需要正则 • 存储空间大,IO 开销高 • 推荐场景:调试、小规模项目 • 工具:grep/awk/Logtail 轻量版 特性 • 键值对结构,语义清晰 • 可被日志系统直接解析 • 存储压缩比不错 • 推荐场景:微服务、统一日志平台 • 工具:Elasticsearch/Logstash 特性 • 极致压缩,体积最小 • 解析性能最好 • 需配套解析器,版本耦合 • 推荐场景:高频交易、嵌入式 • 工具:Protobuf/自制解析器

上面这张图把三种格式的核心差异和适用场景串起来了。你能看到,从左到右,可读性递减,性能递增。大部分现代系统会选在“结构化日志”这个中间态——这也是我最推荐的起点。