JSONL 日志的三个隐形坑:BOM、半行、和按字节轮转

JSONL(每行一个 JSON)是做日志的省事选择:追加写、流式读、不用把整个文件读进内存。但它有三个坑,平时不疼,出事时正好是你最需要日志的时候。

坑一:BOM 和半行,让"读日志"这件事先崩掉

用带 BOM 的 UTF-8 写文件,第一行会多出 \ufeff,json.loads 直接抛异常。Python 里读日志统一用 encoding="utf-8-sig",写出时确认用 utf-8。

更常见的是半行:进程被强杀时,最后一行往往只写了一半。如果读取代码写成"整体读入再逐行解析,出错就 return []",那么一次半行就能让你看到零条日志------排查时比没有日志更误导。正确做法是逐行解析,坏行计数并跳过:

python 复制代码
ok = bad = 0
with open(path, encoding="utf-8-sig", errors="replace") as f:
    for line in f:
        line = line.strip()
        if not line:
            continue
        try:
            rec = json.loads(line)
        except json.JSONDecodeError:
            bad += 1
            continue
        ok += 1

关键不是 try,而是把 bad 打出来。静默跳过坏行,等于把"日志被截断"这个事实藏起来。

坑二:多进程同时追加

在 Linux 上,小于 PIPE_BUF(通常 4096 字节)的单次 write 到以 O_APPEND 打开的文件是原子的。超过这个大小就可能交错,两行互相穿插,得到的 JSON 全部报废。Windows 对追加写没有同样的保证。

三种实用做法,按成本排序:单写者 (所有日志打到队列,由唯一进程落盘,最省心)、按进程分片 (app_{pid}.jsonl,读的时候合并,零锁)、文件锁 (不要自己发明,用 filelock 之类的库)。

坑三:轮转时按字节切

日志轮转最常见的写法是"文件超过 10MB 就切"。按字节切会把某一行劈成两半:前半留在旧文件末尾,后半落到新文件开头,两侧都变成坏行。

按行轮转即可:读时累计字节数,超过阈值就在行边界切换输出文件。这样每个文件都是完整的 JSONL。

一张检查清单

  • 读:utf-8-sig + 逐行 try + 坏行计数上报
  • 写:单写者,或按 pid 分片,不要把锁逻辑写进业务代码
  • 转:按行轮转,不按字节
  • 删:明确保留策略,别让日志把磁盘吃满
  • 验:新加一条日志后,回头真的读一遍,确认能解析

最后一句经验:日志系统的正确性,从来不体现在写的时候,而体现在出故障那天你能不能把最后几行读出来。


相关推荐
许雪里1 天前
XXL-AI v1.4.0 发布|AI应用开发平台
agent·ai编程
EnCi Zheng1 天前
Agent 如何进行工具调用:从一次 Function Calling 到生产级工具编排的完整链路
大数据·人工智能·agent·ai编程·工具调用
狼爷1 天前
AI正在制造通缩:一场闭门会背后,三个被大多数人忽略的底层逻辑
人工智能·aigc·ai编程
超级架构师1 天前
PRAXO-CORE 2.5.0:用 conformance 和显式边界构建 Go Agent 框架
开发语言·人工智能·后端·golang·ai编程·智能体框架·praxo-core
十七喜欢前端1 天前
工具调用没那么简单:Agent 和外部世界打交道时,有四道坎
agent·ai编程
jarvisuni1 天前
白嫖Tripo3D ?Kimi 3D Make可以玩起来!
人工智能·ai编程
码间留白1 天前
鸿蒙也能热更新了:uni-app WGT 热更新的真机结论、六道校验闸与五个深坑
uniapp·ai编程·鸿蒙os开发·鸿蒙热更新·wgt热更新
掘黄金的打工人2 天前
DeepSeek 参数解读
openai·ai编程
金銀銅鐵2 天前
[Java] 用纯 Swing 做一个《論語·學而》阅读器
java·ai编程·交互设计
李剑一2 天前
为什么几乎所有的知名开源项目都是外国人写的?
前端·ai编程·deepseek