高密度、低内存,不该只是两个漂亮词。
它们应该能被设计出来,被验证出来,也能被推翻重来。

10 GiB 的日志文件,最麻烦的地方往往不是它占了多少磁盘,而是它让人失去耐心。
你只想看最后几十行,它偏要让工具先面对整个文件;你只想查一个关键词,它偏要把磁盘和内存都拖进来;你明明是在排障,却常常先被日志本身排了一遍障。
我想做 TinyLog,最早不是因为想写一个“日志产品”,而是因为这个问题实在太具体了:能不能把日志存得更紧凑一点,读得更轻一点?
如果放在几年前,这样一个念头很容易被我自己搁置。要选语言,要设计格式,要写压缩,要做 viewer,要补测试。每一步都不算难,但每一步都足够让人拖延。AI 出现以后,这件事变成了另一种做法:不先证明自己想得多对,而是先把想法变成可以被验证、可以被打脸、可以被重构的东西。
这个想法其实从 2021 年就开始了
这件事不是 2026 年突然冒出来的。
2021 年,公司要写专利,我顺势把自己对日志压缩存储的想法整理成了一份方案。那份专利叫《一种日志压缩存储方法、电子装置》,申请日是 2021 年 2 月 4 日,公开日是 2021 年 6 月 15 日。它当时想解决的,正是传统日志整体压缩之后不好浏览、解压成本高、跨时间范围定位麻烦这些问题。
那份方案的核心思路,是把日志文件划分为参数区、索引区和日志区:参数区记录压缩算法、时间戳格式、最小日志切割时间单位等信息;索引区按时间分段保存索引;日志区顺序保存压缩或未压缩的日志内容。浏览时,不必一次性解压完整文件,而是根据时间范围定位索引,再逐段解压、逐步展示。
换句话说,TinyLog 不是从零开始凭空长出来的。它更像是一个老想法,在几年以后遇到了新的实现条件。
先把问题说清楚
日志工具这个领域很容易一上来就谈实现。二进制格式、压缩算法、索引、viewer、SDK、异步写入,每个词都像正事。但如果问题没有说清楚,后面做得越多,可能偏得越远。
我先和 AI 讨论的是两个朴素目标。
第一,日志能不能存得更紧凑。明文日志有大量重复内容,时间戳、级别、模板、字段名、固定消息,天天在那里重复出现。这些东西看起来诚实,但很占地方。
第二,日志能不能读得更轻。排障时,我们常常只想看某个窗口、某个级别、某个关键词附近的几十行,但工具却经常被迫处理整个大文件。
所以 TinyLog 的设计目标不是“发明一个更酷的日志格式”,而是:在尽量接近 gzip 压缩密度的前提下,保留可浏览、可跳转、可按需解压的结构。
让 AI 先攻击我的假设
我最早的想法其实很天真:对每一行日志单独压缩。
这个想法看起来很合理。一行日志就是一个记录,压缩后存起来,读取时再按行解开。结构清楚,边界明确,好像还很工程化。
但我把这个想法和 AI 一沟通,再让它帮我写一个最小原型,很快就发现不对。单行压缩几乎没有明显收益。
原因也不复杂。gzip 这类压缩算法,恰恰是靠大段内容里的重复结构工作。你把日志拆成一行一行压缩,相当于把算法能利用的上下文切碎了。它还没来得及看见重复,文件就已经被你分段了。
这一步很重要。AI 的价值不是永远给你正确答案,而是能很快把一个看起来合理的错误答案变成可以验证的东西。过去这类试错可能会消耗一个周末,现在二十分钟就能知道:这条路别走了。
从“按行”改成“按 trunk”
既然按行不行,那就按多行。
这就是 trunk 方案的来处。不要把每一行日志都当成一个压缩单元,而是把连续多行组织成一个 trunk,再整体压缩。
plaintext log -> parse timestamp / level / content -> append records into trunk -> gzip whole trunk -> write .tog这样一来,压缩算法有足够上下文,可以吃到日志里的重复结构;读取时又不必解压整个文件,只需要解压目标窗口所在的 trunk。
这个方案不是 AI 一句话“灵机一动”给出来的,而是在来回讨论里长出来的。人负责提出问题和判断取舍,AI 负责快速实现、暴露问题、补全边界条件。两者合起来,设计迭代速度会快很多。
文件格式要服务访问方式
一旦确定 trunk 是压缩单元,下一个问题就是文件格式。
TinyLog 当前原型的 .tog 文件不是简单把 gzip 结果拼起来。它有 header,有 trunk 计数,有日志总行数,有每个 trunk 的压缩长度。trunk 内部则存每行的时间偏移、日志级别和内容长度。
[header][trunk-0: line_count + compressed_length + compressed_payload][trunk-1: line_count + compressed_length + compressed_payload][trunk-2: line_count + compressed_length + compressed_payload]...这里有一个很关键的设计原则:文件格式不是为了看起来规整,而是为了读取方式服务。
如果 viewer 要快速跳到最后一页,文件里就必须能知道总行数和 trunk 边界。如果搜索要按需继续,就不能一打开文件就把所有 trunk 解开。如果按级别过滤要可用,日志级别就不能只混在一段不可理解的字符串里。
这些设计都不是孤立的。写入格式、压缩粒度、索引扫描、viewer 交互,它们必须互相咬合。
viewer 不是附属品,而是设计约束
很多工具会先做格式,再想怎么展示。但 TinyLog 这个事情里,viewer 不是附属品,它反过来约束文件格式。
因为“低内存访问”不是一句口号,它必须体现为具体动作:
打开文件,不能把 10 GiB 明文读进内存;跳到最后一页,不能从第一行一路翻过去;搜索关键词,最好能从当前 trunk 开始按需扫描;按级别过滤,也不该一上来解压整文件。
所以 viewer 的动作很朴素,但每个动作都在逼问底层设计:
j / k 上下移动d / u 翻页G 跳到最后一页/keyword 按 trunk 搜索:error 按级别过滤这也是 AI 协作时很有价值的一点:我可以不断把“用户动作”丢给它,让它反推文件格式和读取接口是否够用。一个设计如果无法支撑真实动作,就算结构再漂亮,也只是纸上体面。
用基准测试把感觉变成数字
做工具最容易自我感动。感觉好像更快了,感觉好像更省了,感觉好像设计更合理了。但工具不能只靠感觉。
所以我最后把基准测试固化到了 TinyLog 项目里。脚本会生成有序的 10 GiB 合成日志,分别跑整文件 gzip、TinyLog 转换、viewer 打开并跳到最后一页,以及 Vim 打开原始日志并跳到最后一行。
这些数字不代表所有日志都会一样。字段越随机、重复越少,压缩率就会下降。但它们足以说明一个设计方向:TinyLog 不是为了在压缩率上羞辱 gzip,而是为了在接近 gzip 的压缩密度下,保留更适合浏览的结构。
AI 在这里到底做了什么
这件事里,AI 不是一个神奇外包团队。它也不是一个自动许愿机。
它更像一个很快的、不会嫌你反复的工程搭档。你给它一个想法,它可以马上写一个最小原型;你怀疑一个方向,它可以帮你补一个测试;你不知道 Rust 怎么写,它可以先把结构搭出来;你意识到设计不对,它可以陪你重构。
但真正重要的判断仍然要由人来做。
比如,为什么不按行压缩,为什么选 trunk,trunk 大小要不要可配置,viewer 为什么要支持跳到底部,基准测试应该测什么,哪些数字能说明问题,哪些数字不能夸大。这些都不是“让 AI 写代码”就会自然发生的。
AI 降低的是试错成本。试错成本降低以后,人就更敢设计,也更敢推翻自己。
先把想法变成可验证的东西
如果说 TinyLog 给我最大的启发是什么,不是“我终于写了一个日志工具”,而是“我终于能更快地验证一个工具想法”。
过去很多想法会卡在开始之前。因为你知道,一旦开始,就要准备环境、学语言、搭结构、写测试、修 bug。等这些事情想完,热情也差不多凉了。
有了 AI,设计工具的方式变了。
你可以先把问题说清楚,再让 AI 攻击你的假设;先写最小实验,再看数据;先承认一个方案没用,再换一个方案;先把核心链路跑通,再慢慢补工程细节。
高密度、低内存不是一开始就想对的。它们是在一次次失败、验证、重构和基准测试里长出来的。
AI 最有用的地方,也许不是替你完成一个伟大的想法。
而是让你有力气把一个普通想法,推到可以被验证的地方。
项目页面:https://huimang.github.io/tinylog/
资料来源:TinyLog README、专利参考材料《一种日志压缩存储方法、电子装置》、TinyLog git 提交记录、TinyLog 10 GiB 基准测试文档。
夜雨聆风