fix(desktop): export tail of oversized logs in debug bundle (#47672)

This commit is contained in:
Luke Parker
2026-09-07 08:47:30 +10:00
committed by GitHub
Unverified
parent a02415a3eb
commit 0d70595338
+18 -7
View File
@@ -2,7 +2,7 @@ export * as DesktopLogging from "./logging"
import log from "electron-log/main.js"
import { app, crashReporter, netLog, shell } from "electron"
import { Context, Effect, FileSystem, Layer, Logger, Option, Path, References } from "effect"
import { Context, Effect, FileSystem, Layer, Logger, Option, Path, References, Stream } from "effect"
import { homedir } from "node:os"
import { VERSION } from "../constants"
@@ -134,11 +134,15 @@ function exportDebugLogsEffect(fs: FileSystem.FileSystem, path: Path.Path) {
const output = path.join(app.getPath("downloads"), `opencode-debug-${stamp()}.zip`)
return yield* Effect.gen(function* () {
yield* Effect.logInfo("exporting debug logs", { output })
yield* writeZip(fs, output, [
{ name: "manifest.json", data: Buffer.from(JSON.stringify(manifest(path), null, 2)) },
const files = [
...(yield* collect(fs, path, root, "desktop")),
...(yield* Effect.forEach(serverLogRoots(path), (dir, i) => collect(fs, path, dir, `server-${i + 1}`))).flat(),
...(yield* collect(fs, path, app.getPath("crashDumps"), "crashpad")),
]
const truncated = files.filter((file) => file.offset > 0).map((file) => file.name)
yield* writeZip(fs, output, [
{ name: "manifest.json", data: Buffer.from(JSON.stringify({ ...manifest(path), truncated }, null, 2)) },
...files,
])
yield* Effect.sync(() => shell.showItemInFolder(output))
return output
@@ -229,7 +233,7 @@ function serverLogRoots(path: Path.Path) {
]
}
type Entry = { name: string; path: string } | { name: string; data: Uint8Array }
type Entry = { name: string; path: string; offset: number } | { name: string; data: Uint8Array }
function collect(fs: FileSystem.FileSystem, path: Path.Path, dir: string, prefix: string) {
return Effect.gen(function* () {
@@ -242,9 +246,11 @@ function collect(fs: FileSystem.FileSystem, path: Path.Path, dir: string, prefix
const info = yield* fs.stat(file)
if (info.type === "Directory") return null
if (Option.getOrElse(info.mtime, () => new Date(0)).getTime() < cutoff) return null
if (info.size > FileSystem.Size(MAX_EXPORT_FILE_SIZE)) return null
if (file.endsWith(".heapsnapshot")) return null
return { name: path.join(prefix, entry).replace(/\\/g, "/"), path: file }
// Server logs append forever without rotation, so the active log is often the largest
// file. Export its tail rather than dropping the most relevant file from the bundle.
const offset = Math.max(0, Number(info.size) - MAX_EXPORT_FILE_SIZE)
return { name: path.join(prefix, entry).replace(/\\/g, "/"), path: file, offset }
}),
)).filter((entry) => entry !== null)
})
@@ -258,7 +264,12 @@ function writeZip(fs: FileSystem.FileSystem, output: string, entries: Entry[]) {
entries,
(entry) =>
Effect.gen(function* () {
const data = "data" in entry ? entry.data : yield* fs.readFile(entry.path)
const data =
"data" in entry
? entry.data
: entry.offset === 0
? yield* fs.readFile(entry.path)
: Buffer.concat(yield* Stream.runCollect(fs.stream(entry.path, { offset: entry.offset })))
yield* Effect.tryPromise(() => writer.add(entry.name, new BlobReader(new Blob([new Uint8Array(data)]))))
}),
{ concurrency: 1, discard: true },