From a297bd086da8b2890f148db13e3dc329c1ddada1 Mon Sep 17 00:00:00 2001 From: Adam Bowker Date: Fri, 1 May 2026 09:45:35 -0700 Subject: [PATCH] chore(code): add chromium logs --- apps/code/src/main/bootstrap.ts | 20 ++++++++++++ apps/code/src/main/index.ts | 19 ++++++++++++ apps/code/src/main/utils/logger.ts | 49 +++++++++++++++++++++++++++++- apps/code/src/main/window.ts | 25 ++++++++++++++- 4 files changed, 111 insertions(+), 2 deletions(-) diff --git a/apps/code/src/main/bootstrap.ts b/apps/code/src/main/bootstrap.ts index 591a247f6..cdb038f4a 100644 --- a/apps/code/src/main/bootstrap.ts +++ b/apps/code/src/main/bootstrap.ts @@ -15,6 +15,8 @@ */ import dns from "node:dns"; +import { mkdirSync } from "node:fs"; +import os from "node:os"; import path from "node:path"; import { app, protocol } from "electron"; import { fixPath } from "./utils/fixPath"; @@ -37,6 +39,24 @@ process.env.POSTHOG_CODE_DATA_DIR = userDataPath; process.env.POSTHOG_CODE_IS_DEV = String(isDev); process.env.POSTHOG_CODE_VERSION = app.getVersion(); +// Enable Chromium internal logging to a dedicated file. Without this, Chromium +// crashes (black screens, render-process-gone, GPU process death) leave no +// trail because Electron silently swallows the underlying logs. Must run +// before app.whenReady() so the switches take effect on the GPU/renderer +// child processes. +const chromiumLogDir = path.join( + os.homedir(), + ".posthog-code", + isDev ? "logs-dev" : "logs", +); +mkdirSync(chromiumLogDir, { recursive: true }); +const chromiumLogPath = path.join(chromiumLogDir, "chromium.log"); +process.env.ELECTRON_ENABLE_LOGGING = "1"; +process.env.POSTHOG_CODE_CHROMIUM_LOG_PATH = chromiumLogPath; +app.commandLine.appendSwitch("enable-logging", "file"); +app.commandLine.appendSwitch("log-file", chromiumLogPath); +app.commandLine.appendSwitch("log-level", "0"); + // Force IPv4 resolution when "localhost" is used so the agent hits 127.0.0.1 // instead of ::1. This matches how the renderer already reaches the PostHog API. dns.setDefaultResultOrder("ipv4first"); diff --git a/apps/code/src/main/index.ts b/apps/code/src/main/index.ts index 9a9ea2c4e..0a6967925 100644 --- a/apps/code/src/main/index.ts +++ b/apps/code/src/main/index.ts @@ -28,6 +28,11 @@ import type { TaskLinkService } from "./services/task-link/service"; import type { UpdatesService } from "./services/updates/service"; import type { WorkspaceService } from "./services/workspace/service"; import { ensureClaudeConfigDir } from "./utils/env"; +import { + getChromiumLogFilePath, + getLogFilePath, + readChromiumLogTail, +} from "./utils/logger"; import { createWindow } from "./window"; // Single instance lock must be acquired FIRST before any other app setup @@ -92,6 +97,9 @@ app.whenReady().then(async () => { `OS: ${process.platform} ${process.arch} ${os.release()}`, ].join(" | "), ); + log.info( + `Logs: main=${getLogFilePath()} chromium=${getChromiumLogFilePath() ?? "(disabled)"}`, + ); ensureClaudeConfigDir(); registerMcpSandboxProtocol(); createWindow(); @@ -103,6 +111,17 @@ app.on("window-all-closed", () => { app.quit(); }); +app.on("child-process-gone", (_event, details) => { + log.error("Child process gone", { + type: details.type, + reason: details.reason, + exitCode: details.exitCode, + serviceName: details.serviceName, + name: details.name, + chromiumLogTail: readChromiumLogTail(), + }); +}); + app.on("before-quit", async (event) => { let lifecycleService: AppLifecycleService; try { diff --git a/apps/code/src/main/utils/logger.ts b/apps/code/src/main/utils/logger.ts index 65b0c3d10..203f1e227 100644 --- a/apps/code/src/main/utils/logger.ts +++ b/apps/code/src/main/utils/logger.ts @@ -1,4 +1,13 @@ -import { existsSync, mkdirSync, renameSync, unlinkSync } from "node:fs"; +import { + closeSync, + existsSync, + fstatSync, + mkdirSync, + openSync, + readSync, + renameSync, + unlinkSync, +} from "node:fs"; import os from "node:os"; import { join } from "node:path"; import { initOtelTransport } from "@main/utils/otel-log-transport"; @@ -57,3 +66,41 @@ export { shutdownOtelTransport } from "@main/utils/otel-log-transport"; export function getLogFilePath(): string { return join(LOG_DIR, LOG_FILE); } + +export function getChromiumLogFilePath(): string | undefined { + return process.env.POSTHOG_CODE_CHROMIUM_LOG_PATH; +} + +const CHROMIUM_LOG_TAIL_BYTES = 32 * 1024; + +/** + * Read the last ~32 KB of the Chromium internal log file. Used by crash + * handlers to attach the tail to OTEL/electron-log so PostHog gets the native + * V8/GPU output around a renderer death — Chromium writes chromium.log + * directly from native code and never goes through electron-log otherwise. + */ +export function readChromiumLogTail(): string | undefined { + const path = getChromiumLogFilePath(); + if (!path) return undefined; + + let fd: number | undefined; + try { + fd = openSync(path, "r"); + const { size } = fstatSync(fd); + if (size === 0) return undefined; + const length = Math.min(size, CHROMIUM_LOG_TAIL_BYTES); + const buf = Buffer.alloc(length); + readSync(fd, buf, 0, length, size - length); + return buf.toString("utf8"); + } catch { + return undefined; + } finally { + if (fd !== undefined) { + try { + closeSync(fd); + } catch { + // Best-effort close + } + } + } +} diff --git a/apps/code/src/main/window.ts b/apps/code/src/main/window.ts index e8920c6b2..40a8fba7c 100644 --- a/apps/code/src/main/window.ts +++ b/apps/code/src/main/window.ts @@ -14,7 +14,7 @@ import { buildApplicationMenu } from "./menu"; import type { ElectronMainWindow } from "./platform-adapters/electron-main-window"; import { trpcRouter } from "./trpc/router"; import { isDevBuild } from "./utils/env"; -import { logger } from "./utils/logger"; +import { logger, readChromiumLogTail } from "./utils/logger"; import { type WindowStateSchema, windowStateStore } from "./utils/store"; const log = logger.scope("window"); @@ -103,6 +103,28 @@ function setupExternalLinkHandlers(window: BrowserWindow): void { }); } +function setupCrashLogging(window: BrowserWindow): void { + window.webContents.on("render-process-gone", (_event, details) => { + log.error("Renderer process gone", { + reason: details.reason, + exitCode: details.exitCode, + url: window.webContents.getURL(), + chromiumLogTail: readChromiumLogTail(), + }); + }); + + window.on("unresponsive", () => { + log.warn("Window unresponsive", { + url: window.webContents.getURL(), + chromiumLogTail: readChromiumLogTail(), + }); + }); + + window.on("responsive", () => { + log.info("Window responsive again"); + }); +} + function setupEditableContextMenu(window: BrowserWindow): void { window.webContents.on("context-menu", (_event, params) => { if (!params.isEditable) return; @@ -212,6 +234,7 @@ export function createWindow(): void { setupExternalLinkHandlers(mainWindow); setupEditableContextMenu(mainWindow); + setupCrashLogging(mainWindow); buildApplicationMenu(); if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {