Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/opencode/src/util/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export namespace Log {

export async function init(options: Options) {
if (options.level) level = options.level
cleanup(Global.Path.log)
await cleanup(Global.Path.log)
if (options.print) return
logpath = path.join(
Global.Path.log,
Expand All @@ -83,7 +83,7 @@ export namespace Log {
)
if (files.length <= 5) return

const filesToDelete = files.slice(0, -10)
const filesToDelete = files.sort().slice(0, -10)
await Promise.all(filesToDelete.map((file) => fs.unlink(file).catch(() => {})))
}

Expand Down
140 changes: 140 additions & 0 deletions packages/opencode/test/util/log.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { describe, test, expect, beforeEach, afterEach, setSystemTime } from "bun:test"
import { Log } from "../../src/util/log"
import { Global } from "../../src/global"
import fs from "fs/promises"
import path from "path"

describe("Log.init", () => {
const CLEANUP_THRESHOLD = 5

beforeEach(clearLogDir)
afterEach(clearLogDir)

test("creates a new log file with timestamp name", async () => {
setSystemTime(new Date("2026-01-07T15:00:00.000Z"))

await Log.init({ print: false })

const logFile = Log.file()
expect(logFile).toBeTruthy()
expect(path.basename(logFile)).toBe("2026-01-07T150000.log")
expect(await Bun.file(logFile).exists()).toBe(true)

setSystemTime()
})

test("creates dev.log when dev option is true", async () => {
await Log.init({ print: false, dev: true })

const logFile = Log.file()
expect(path.basename(logFile)).toBe("dev.log")
expect(await Bun.file(logFile).exists()).toBe(true)
})

test("does not delete files when at threshold", async () => {
const startingAt = new Date("2020-01-01")
const oldFiles = createLogFiles(CLEANUP_THRESHOLD, startingAt, ONE_DAY)
await writeFiles(oldFiles)

await Log.init({ print: false })

for (const filename of oldFiles) {
const file = getLogFile(filename)
expect(await file.exists()).toBe(true)
}
})

test("does not delete files when threshold exceeded by 5", async () => {
const startingAt = new Date("2020-01-01")
const oldFiles = createLogFiles(CLEANUP_THRESHOLD + 5, startingAt, ONE_DAY)
await writeFiles(oldFiles)

await Log.init({ print: false })

for (const filename of oldFiles) {
const file = getLogFile(filename)
expect(await file.exists()).toBe(true)
}
})

test("deletes the oldest file when threshold exceeded by 6", async () => {
const startingAt = new Date("2020-01-01")
const oldFiles = createLogFiles(CLEANUP_THRESHOLD + 6, startingAt, ONE_DAY)
await writeFiles(oldFiles)

await Log.init({ print: false })

const file = getLogFile("2020-01-01T000000.log")
expect(await file.exists()).toBe(false)
})

test("preserves the newest 10 files when threshold exceeded by 6", async () => {
const startingAt = new Date("2020-01-01")
const oldFiles = createLogFiles(CLEANUP_THRESHOLD + 6, startingAt, ONE_DAY)
const newestFiles = oldFiles.slice(-10)
await writeFiles(oldFiles)

await Log.init({ print: false })

for (const filename of newestFiles) {
const file = getLogFile(filename)
expect(await file.exists()).toBe(true)
}
})

test("does not delete dev.log during cleanup", async () => {
const startingAt = new Date("2020-01-01")
const oldFiles = createLogFiles(CLEANUP_THRESHOLD + 6, startingAt, ONE_DAY)
await writeFiles([...oldFiles, "dev.log"])

await Log.init({ print: false })

const file = getLogFile("dev.log")
expect(await file.exists()).toBe(true)
})

test("creates new log file after cleanup runs", async () => {
setSystemTime(new Date("2025-11-02T00:00:00.000Z"))
const startingAt = new Date("2020-01-01")
const oldFiles = createLogFiles(CLEANUP_THRESHOLD + 6, startingAt, ONE_DAY)
await writeFiles(oldFiles)

await Log.init({ print: false })

const oldestFile = getLogFile("2020-01-01T000000.log")
const newFile = getLogFile("2025-11-02T000000.log")
expect(await oldestFile.exists()).toBe(false)
expect(await newFile.exists()).toBe(true)

setSystemTime()
})
})

async function clearLogDir() {
const existingLogFiles = await fs.readdir(Global.Path.log).catch(() => [])

await Promise.all(
existingLogFiles.map((existingLogFile) => {
const filepath = path.join(Global.Path.log, existingLogFile)
return fs.unlink(filepath).catch(() => {})
}),
)
}

function writeFiles(filenames: string[]) {
return Promise.all(filenames.map((f) => Bun.write(path.join(Global.Path.log, f), "test")))
}

function getLogFile(filename: string) {
return Bun.file(path.join(Global.Path.log, filename))
}

const ONE_DAY = 1000 * 60 * 60 * 24

function createLogFiles(count: number, startDate: Date, increment = ONE_DAY): string[] {
return Array.from({ length: count }, (_, index) => {
const creationOffset = index * increment
const fileCreatedAt = new Date(startDate.getTime() + creationOffset)
return fileCreatedAt.toISOString().split(".")[0].replace(/:/g, "") + ".log"
})
}