From 3a86e15189b56901c90a0bf27cf027b7b417cf96 Mon Sep 17 00:00:00 2001 From: Marius Date: Thu, 2 Apr 2026 11:54:49 +0200 Subject: [PATCH] fix(cli): prevent unbounded log file growth with size-based rotation (#8136) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(cli): prevent unbounded log file growth with size-based rotation Fix two issues causing log files to grow to 50+ GB: 1. Server middleware leaked a "started" log line for skipped endpoints because log.time() was called unconditionally — only timer.stop() was gated by the skipLogging flag. Restructure to early-return for skipped paths so no logging occurs at all. 2. Log files had no size limit within a session. A long-running kilo serve process (e.g. VS Code extension) would write a single file indefinitely. Add rotating-file-stream (50 MB, maxFiles: 1) to cap log file size automatically. * fix(cli): address review feedback on log rotation - Revert server.ts changes (handled separately in #8141) - Add error/warning event handlers on rotating-file-stream - Increase maxFiles from 1 to 3 for better debug headroom - Set explicit history file path to avoid .txt side-effect * fix(cli): revert maxFiles back to 1 maxFiles: 1 is sufficient — we only need to cap disk usage, not retain old rotated fragments. The existing cleanup() handles session-level file retention separately. * fix(cli): set maxFiles to 10 for log rotation --- bun.lock | 3 +++ packages/opencode/package.json | 1 + packages/opencode/src/util/log.ts | 28 +++++++++++++++++++--------- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/bun.lock b/bun.lock index a78c9245ce4..a9821225cf6 100644 --- a/bun.lock +++ b/bun.lock @@ -401,6 +401,7 @@ "opentui-spinner": "0.0.6", "partial-json": "0.1.7", "remeda": "catalog:", + "rotating-file-stream": "3.2.9", "simple-git": "3.31.1", "solid-js": "catalog:", "strip-ansi": "7.1.2", @@ -3797,6 +3798,8 @@ "rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="], + "rotating-file-stream": ["rotating-file-stream@3.2.9", "", {}, "sha512-i9i0KkHh12ryl4xtELg+0gyoFre2PJ9RcQQLzquWsiqygyYsrZLckrqqYrthhnJZGZb4g+KUHtcoWYVq34gaug=="], + "roughjs": ["roughjs@4.6.6", "", { "dependencies": { "hachure-fill": "^0.5.2", "path-data-parser": "^0.1.0", "points-on-curve": "^0.2.0", "points-on-path": "^0.2.1" } }, "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ=="], "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 5d184292e35..91a053b04c7 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -126,6 +126,7 @@ "opentui-spinner": "0.0.6", "partial-json": "0.1.7", "remeda": "catalog:", + "rotating-file-stream": "3.2.9", "simple-git": "3.31.1", "solid-js": "catalog:", "strip-ansi": "7.1.2", diff --git a/packages/opencode/src/util/log.ts b/packages/opencode/src/util/log.ts index 2ca4c0a3de3..e080648bc4d 100644 --- a/packages/opencode/src/util/log.ts +++ b/packages/opencode/src/util/log.ts @@ -1,9 +1,9 @@ import path from "path" import fs from "fs/promises" -import { createWriteStream } from "fs" import { Global } from "../global" import z from "zod" import { Glob } from "./glob" +import { createStream } from "rotating-file-stream" // kilocode_change export namespace Log { export const Level = z.enum(["DEBUG", "INFO", "WARN", "ERROR"]).meta({ ref: "LogLevel", description: "Log level" }) @@ -66,15 +66,25 @@ export namespace Log { options.dev ? "dev.log" : new Date().toISOString().split(".")[0].replace(/:/g, "") + ".log", ) await fs.truncate(logpath).catch(() => {}) - const stream = createWriteStream(logpath, { flags: "a" }) - write = async (msg: any) => { - return new Promise((resolve, reject) => { - stream.write(msg, (err) => { - if (err) reject(err) - else resolve(msg.length) - }) - }) + // kilocode_change start - use rotating-file-stream to cap log files at 50 MB + const dir = path.dirname(logpath) + const stream = createStream(path.basename(logpath), { + size: "50M", + maxFiles: 10, + history: path.join(dir, ".log-history"), + path: dir, + }) + stream.on("error", (err: Error) => { + process.stderr.write("log stream error: " + err.message + "\n") + }) + stream.on("warning", (err: Error) => { + process.stderr.write("log stream warning: " + err.message + "\n") + }) + write = (msg: any) => { + stream.write(msg) + return msg.length } + // kilocode_change end } async function cleanup(dir: string) {