Kaynağa Gözat

fix: ensure logger cleanup properly orders list before deleting files (#22101)

Aiden Cline 4 ay önce
ebeveyn
işleme
fc01cad2b8

+ 7 - 10
packages/opencode/src/util/log.ts

@@ -3,7 +3,6 @@ import fs from "fs/promises"
 import { createWriteStream } from "fs"
 import { Global } from "../global"
 import z from "zod"
-import { Glob } from "./glob"
 
 export namespace Log {
   export const Level = z.enum(["DEBUG", "INFO", "WARN", "ERROR"]).meta({ ref: "LogLevel", description: "Log level" })
@@ -15,6 +14,8 @@ export namespace Log {
     WARN: 2,
     ERROR: 3,
   }
+  const keep = 10
+  const rx = /^\d{4}-\d{2}-\d{2}T\d{6}\.log$/
 
   let level: Level = "INFO"
 
@@ -78,15 +79,11 @@ export namespace Log {
   }
 
   async function cleanup(dir: string) {
-    const files = await Glob.scan("????-??-??T??????.log", {
-      cwd: dir,
-      absolute: true,
-      include: "file",
-    })
-    if (files.length <= 5) return
-
-    const filesToDelete = files.slice(0, -10)
-    await Promise.all(filesToDelete.map((file) => fs.unlink(file).catch(() => {})))
+    const files = (await fs.readdir(dir).catch(() => [])).filter((file) => rx.test(file)).sort()
+    if (files.length <= keep) return
+
+    const doomed = files.slice(0, -keep)
+    await Promise.all(doomed.map((file) => fs.unlink(path.join(dir, file)).catch(() => {})))
   }
 
   function formatError(error: Error, depth = 0): string {

+ 44 - 0
packages/opencode/test/util/log.test.ts

@@ -0,0 +1,44 @@
+import { afterEach, expect, test } from "bun:test"
+import fs from "fs/promises"
+import path from "path"
+import { Global } from "../../src/global"
+import { Log } from "../../src/util/log"
+import { tmpdir } from "../fixture/fixture"
+
+const log = Global.Path.log
+
+afterEach(() => {
+  Global.Path.log = log
+})
+
+async function files(dir: string) {
+  let last = ""
+  let same = 0
+
+  for (let i = 0; i < 50; i++) {
+    const list = (await fs.readdir(dir)).sort()
+    const next = JSON.stringify(list)
+    same = next === last ? same + 1 : 0
+    if (same >= 2 && list.length === 11) return list
+    last = next
+    await Bun.sleep(10)
+  }
+
+  return (await fs.readdir(dir)).sort()
+}
+
+test("init cleanup keeps the newest timestamped logs", async () => {
+  await using tmp = await tmpdir()
+  Global.Path.log = tmp.path
+
+  const list = Array.from({ length: 12 }, (_, i) => `2000-01-${String(i + 1).padStart(2, "0")}T000000.log`)
+
+  await Promise.all(list.map((file) => fs.writeFile(path.join(tmp.path, file), file)))
+
+  await Log.init({ print: false, dev: false })
+
+  const next = await files(tmp.path)
+
+  expect(next).not.toContain(list[0]!)
+  expect(next).toContain(list.at(-1)!)
+})