Skip to content

Commit fc01cad

Browse files
authored
fix: ensure logger cleanup properly orders list before deleting files (#22101)
1 parent c1ddc0e commit fc01cad

2 files changed

Lines changed: 51 additions & 10 deletions

File tree

packages/opencode/src/util/log.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import fs from "fs/promises"
33
import { createWriteStream } from "fs"
44
import { Global } from "../global"
55
import z from "zod"
6-
import { Glob } from "./glob"
76

87
export namespace Log {
98
export const Level = z.enum(["DEBUG", "INFO", "WARN", "ERROR"]).meta({ ref: "LogLevel", description: "Log level" })
@@ -15,6 +14,8 @@ export namespace Log {
1514
WARN: 2,
1615
ERROR: 3,
1716
}
17+
const keep = 10
18+
const rx = /^\d{4}-\d{2}-\d{2}T\d{6}\.log$/
1819

1920
let level: Level = "INFO"
2021

@@ -78,15 +79,11 @@ export namespace Log {
7879
}
7980

8081
async function cleanup(dir: string) {
81-
const files = await Glob.scan("????-??-??T??????.log", {
82-
cwd: dir,
83-
absolute: true,
84-
include: "file",
85-
})
86-
if (files.length <= 5) return
87-
88-
const filesToDelete = files.slice(0, -10)
89-
await Promise.all(filesToDelete.map((file) => fs.unlink(file).catch(() => {})))
82+
const files = (await fs.readdir(dir).catch(() => [])).filter((file) => rx.test(file)).sort()
83+
if (files.length <= keep) return
84+
85+
const doomed = files.slice(0, -keep)
86+
await Promise.all(doomed.map((file) => fs.unlink(path.join(dir, file)).catch(() => {})))
9087
}
9188

9289
function formatError(error: Error, depth = 0): string {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { afterEach, expect, test } from "bun:test"
2+
import fs from "fs/promises"
3+
import path from "path"
4+
import { Global } from "../../src/global"
5+
import { Log } from "../../src/util/log"
6+
import { tmpdir } from "../fixture/fixture"
7+
8+
const log = Global.Path.log
9+
10+
afterEach(() => {
11+
Global.Path.log = log
12+
})
13+
14+
async function files(dir: string) {
15+
let last = ""
16+
let same = 0
17+
18+
for (let i = 0; i < 50; i++) {
19+
const list = (await fs.readdir(dir)).sort()
20+
const next = JSON.stringify(list)
21+
same = next === last ? same + 1 : 0
22+
if (same >= 2 && list.length === 11) return list
23+
last = next
24+
await Bun.sleep(10)
25+
}
26+
27+
return (await fs.readdir(dir)).sort()
28+
}
29+
30+
test("init cleanup keeps the newest timestamped logs", async () => {
31+
await using tmp = await tmpdir()
32+
Global.Path.log = tmp.path
33+
34+
const list = Array.from({ length: 12 }, (_, i) => `2000-01-${String(i + 1).padStart(2, "0")}T000000.log`)
35+
36+
await Promise.all(list.map((file) => fs.writeFile(path.join(tmp.path, file), file)))
37+
38+
await Log.init({ print: false, dev: false })
39+
40+
const next = await files(tmp.path)
41+
42+
expect(next).not.toContain(list[0]!)
43+
expect(next).toContain(list.at(-1)!)
44+
})

0 commit comments

Comments
 (0)