仅追加 JSONL 模式

OpenClaw 在多个子系统中使用仅追加 JSONL 文件,实现崩溃安全、Git 友好和可流式处理的持久化。

使用场景

会话记录

  • 位置: src/config/sessions/store.ts
  • 会话将对话记录存储为仅追加的 .jsonl 文件
  • 每行包含一个完整的 JSON 对象,表示单个消息或事件
  • 会话元数据单独存储在 sessions.json

Cron 运行日志

  • 位置: src/cron/run-log.ts
  • Cron 作业执行日志使用 .jsonl 格式
  • 在 2MB 或 2000 行时自动修剪(以先达到者为准)
  • 常量: DEFAULT_CRON_RUN_LOG_MAX_BYTES = 2_000_000, DEFAULT_CRON_RUN_LOG_KEEP_LINES = 2_000
  • 修剪函数: appendCronRunLog() 在每次追加后调用 pruneIfNeeded()

写入序列化

OpenClaw 使用基于 Map 的排队写入系统确保序列化:

// 来自 src/cron/run-log.ts
const writesByPath = new Map<string, Promise<void>>();

export async function appendCronRunLog(filePath: string, entry: CronRunLogEntry) {
  const prev = writesByPath.get(filePath) ?? Promise.resolve();
  const next = prev
    .catch(() => undefined)
    .then(async () => {
      await fs.mkdir(path.dirname(filePath), { recursive: true });
      await fs.appendFile(filePath, `${JSON.stringify(entry)}\n`, "utf-8");
      await pruneIfNeeded(filePath, { maxBytes, keepLines });
    });
  writesByPath.set(filePath, next);
  await next;
}

此模式确保:

  • 序列化写入: 每个文件同时只有一个写入操作
  • 无丢失写入: 每次写入等待上一次完成
  • 自动清理: 完成后从 Map 中删除条目

主要优势

崩溃安全

  • 部分写入不会损坏现有数据
  • 每行都是完整、有效的 JSON 对象
  • 如果写入中途失败,只丢失该行
  • 之前的行保持完整且可读

Git 友好

  • 基于行的格式产生清晰的差异
  • 每个新事件恰好添加一行
  • 无批量重写,不会使 git 历史混乱
  • 合并冲突基于行,更易解决

可流式处理

  • 可逐行读取,无需加载整个文件
  • 高效支持大文件
  • 通过 tail/follow 实时监控
  • 易于使用标准 Unix 工具过滤/搜索

写入流程图

交叉引用