refactor(daemon): replace daemon-launch.log with electron-log in desktop, remove from CLI/server

The file-based daemon-launch.log was added as temporary instrumentation
for debugging Windows startup. Desktop now logs lifecycle events through
electron-log; CLI and server supervisor drop the launch logging entirely.
This commit is contained in:
Mohamed Boudra
2026-03-29 16:42:31 +07:00
parent a460c5e9b9
commit b1d663557b
4 changed files with 15 additions and 215 deletions
@@ -1,5 +1,5 @@
import { spawn, spawnSync } from "node:child_process"; import { spawn, spawnSync } from "node:child_process";
import { appendFileSync, existsSync, readFileSync } from "node:fs"; import { existsSync, readFileSync } from "node:fs";
import { createRequire } from "node:module"; import { createRequire } from "node:module";
import path from "node:path"; import path from "node:path";
import { loadConfig, resolvePaseoHome } from "@getpaseo/server"; import { loadConfig, resolvePaseoHome } from "@getpaseo/server";
@@ -64,7 +64,6 @@ const DETACHED_STARTUP_GRACE_MS = 1200;
const PID_POLL_INTERVAL_MS = 100; const PID_POLL_INTERVAL_MS = 100;
const KILL_TIMEOUT_MS = 3000; const KILL_TIMEOUT_MS = 3000;
const DAEMON_LOG_FILENAME = "daemon.log"; const DAEMON_LOG_FILENAME = "daemon.log";
const DAEMON_LAUNCH_LOG_FILENAME = "daemon-launch.log";
const DAEMON_PID_FILENAME = "paseo.pid"; const DAEMON_PID_FILENAME = "paseo.pid";
export const DEFAULT_STOP_TIMEOUT_MS = 15_000; export const DEFAULT_STOP_TIMEOUT_MS = 15_000;
@@ -115,24 +114,6 @@ function buildChildEnv(options: DaemonStartOptions): NodeJS.ProcessEnv {
return childEnv; return childEnv;
} }
function appendLaunchLog(home: string, message: string, details?: Record<string, unknown>): void {
const line = [
new Date().toISOString(),
"[CLI daemon]",
`pid=${process.pid}`,
message,
details ? JSON.stringify(details) : "",
]
.filter((part) => part.length > 0)
.join(" ");
try {
appendFileSync(path.join(home, DAEMON_LAUNCH_LOG_FILENAME), `${line}\n`, "utf-8");
} catch {
// Keep launch debugging best-effort so daemon startup behavior is unchanged.
}
}
function resolveDaemonRunnerEntry(): string { function resolveDaemonRunnerEntry(): string {
const serverExportPath = require.resolve("@getpaseo/server"); const serverExportPath = require.resolve("@getpaseo/server");
let currentDir = path.dirname(serverExportPath); let currentDir = path.dirname(serverExportPath);
@@ -373,14 +354,6 @@ export async function startLocalDaemonDetached(
const paseoHome = resolvePaseoHome(childEnv); const paseoHome = resolvePaseoHome(childEnv);
const logPath = path.join(paseoHome, DAEMON_LOG_FILENAME); const logPath = path.join(paseoHome, DAEMON_LOG_FILENAME);
const daemonRunnerEntry = resolveDaemonRunnerEntry(); const daemonRunnerEntry = resolveDaemonRunnerEntry();
appendLaunchLog(paseoHome, "starting detached daemon", {
execPath: process.execPath,
execArgv: process.execArgv,
daemonRunnerEntry,
runnerArgs: buildRunnerArgs(options),
listen: childEnv.PASEO_LISTEN ?? null,
allowedHosts: childEnv.PASEO_ALLOWED_HOSTS ?? null,
});
const child = spawn( const child = spawn(
process.execPath, process.execPath,
[...process.execArgv, daemonRunnerEntry, ...buildRunnerArgs(options)], [...process.execArgv, daemonRunnerEntry, ...buildRunnerArgs(options)],
@@ -391,12 +364,6 @@ export async function startLocalDaemonDetached(
}, },
); );
appendLaunchLog(paseoHome, "detached spawn returned", {
childPid: child.pid ?? null,
spawnfile: child.spawnfile,
spawnargs: child.spawnargs,
});
child.unref(); child.unref();
const startup = await new Promise<DetachedStartupResult>((resolve) => { const startup = await new Promise<DetachedStartupResult>((resolve) => {
@@ -411,33 +378,16 @@ export async function startLocalDaemonDetached(
const timer = setTimeout(() => finish(startupReady()), DETACHED_STARTUP_GRACE_MS); const timer = setTimeout(() => finish(startupReady()), DETACHED_STARTUP_GRACE_MS);
child.once("error", (error) => { child.once("error", (error) => {
appendLaunchLog(paseoHome, "detached child emitted error during grace period", {
childPid: child.pid ?? null,
message: error.message,
});
clearTimeout(timer); clearTimeout(timer);
finish(startupExited({ code: null, signal: null, error })); finish(startupExited({ code: null, signal: null, error }));
}); });
child.once("exit", (code, signal) => { child.once("exit", (code, signal) => {
appendLaunchLog(paseoHome, "detached child emitted exit during grace period", {
childPid: child.pid ?? null,
code,
signal,
});
clearTimeout(timer); clearTimeout(timer);
finish(startupExited({ code, signal })); finish(startupExited({ code, signal }));
}); });
}); });
appendLaunchLog(paseoHome, "detached startup grace period completed", {
childPid: child.pid ?? null,
exitedEarly: startup.exitedEarly,
code: startup.exitedEarly ? startup.code : null,
signal: startup.exitedEarly ? startup.signal : null,
error: startup.exitedEarly ? startup.error?.message ?? null : null,
});
if (startup.exitedEarly) { if (startup.exitedEarly) {
const reason = startup.error const reason = startup.error
? startup.error.message ? startup.error.message
+13 -28
View File
@@ -1,7 +1,8 @@
import { spawn, type ChildProcess } from "node:child_process"; import { spawn, type ChildProcess } from "node:child_process";
import { appendFileSync, existsSync, readFileSync } from "node:fs"; import { existsSync, readFileSync } from "node:fs";
import path from "node:path"; import path from "node:path";
import { app, ipcMain } from "electron"; import { app, ipcMain } from "electron";
import log from "electron-log/main";
import { loadConfig, resolvePaseoHome, getOrCreateServerId } from "@getpaseo/server"; import { loadConfig, resolvePaseoHome, getOrCreateServerId } from "@getpaseo/server";
import { import {
copyAttachmentFileToManagedStorage, copyAttachmentFileToManagedStorage,
@@ -27,7 +28,6 @@ const STOP_TIMEOUT_MS = 15_000;
const KILL_TIMEOUT_MS = 3_000; const KILL_TIMEOUT_MS = 3_000;
const DETACHED_STARTUP_GRACE_MS = 1200; const DETACHED_STARTUP_GRACE_MS = 1200;
const DEFAULT_ELECTRON_DEV_SERVER_URL = "http://localhost:8081"; const DEFAULT_ELECTRON_DEV_SERVER_URL = "http://localhost:8081";
const DAEMON_LAUNCH_LOG_FILENAME = "daemon-launch.log";
type DesktopDaemonState = "starting" | "running" | "stopped" | "errored"; type DesktopDaemonState = "starting" | "running" | "stopped" | "errored";
@@ -76,10 +76,6 @@ function logFilePath(): string {
return path.join(getPaseoHome(), DAEMON_LOG_FILENAME); return path.join(getPaseoHome(), DAEMON_LOG_FILENAME);
} }
function launchLogFilePath(): string {
return path.join(getPaseoHome(), DAEMON_LAUNCH_LOG_FILENAME);
}
function isProcessRunning(pid: number): boolean { function isProcessRunning(pid: number): boolean {
try { try {
process.kill(pid, 0); process.kill(pid, 0);
@@ -145,22 +141,11 @@ function tailFile(filePath: string, lines = 50): string {
} }
} }
function appendLaunchLog(message: string, details?: Record<string, unknown>): void { function logDesktopDaemonLifecycle(message: string, details?: Record<string, unknown>): void {
const line = [ log.info("[desktop daemon]", message, {
new Date().toISOString(), pid: process.pid,
"[desktop daemon]", ...(details ?? {}),
`pid=${process.pid}`, });
message,
details ? JSON.stringify(details) : "",
]
.filter((part) => part.length > 0)
.join(" ");
try {
appendFileSync(launchLogFilePath(), `${line}\n`, "utf-8");
} catch {
// Keep launch debugging best-effort so desktop startup behavior is unchanged.
}
} }
function buildDesktopDaemonCorsOriginsEnv(): string | undefined { function buildDesktopDaemonCorsOriginsEnv(): string | undefined {
@@ -326,7 +311,7 @@ async function startDaemon(): Promise<DesktopDaemonStatus> {
}, },
}); });
appendLaunchLog("starting detached daemon", { logDesktopDaemonLifecycle("starting detached daemon", {
appIsPackaged: app.isPackaged, appIsPackaged: app.isPackaged,
daemonRunnerEntry: daemonRunner.entryPath, daemonRunnerEntry: daemonRunner.entryPath,
daemonRunnerExecArgv: daemonRunner.execArgv, daemonRunnerExecArgv: daemonRunner.execArgv,
@@ -346,7 +331,7 @@ async function startDaemon(): Promise<DesktopDaemonStatus> {
}, },
); );
appendLaunchLog("detached spawn returned", { logDesktopDaemonLifecycle("detached spawn returned", {
childPid: child.pid ?? null, childPid: child.pid ?? null,
spawnfile: child.spawnfile, spawnfile: child.spawnfile,
spawnargs: child.spawnargs, spawnargs: child.spawnargs,
@@ -366,14 +351,14 @@ async function startDaemon(): Promise<DesktopDaemonStatus> {
const timer = setTimeout(() => finish(false), DETACHED_STARTUP_GRACE_MS); const timer = setTimeout(() => finish(false), DETACHED_STARTUP_GRACE_MS);
child.once("error", () => { child.once("error", () => {
appendLaunchLog("detached child emitted error during grace period", { logDesktopDaemonLifecycle("detached child emitted error during grace period", {
childPid: child.pid ?? null, childPid: child.pid ?? null,
}); });
clearTimeout(timer); clearTimeout(timer);
finish(true); finish(true);
}); });
child.once("exit", () => { child.once("exit", () => {
appendLaunchLog("detached child emitted exit during grace period", { logDesktopDaemonLifecycle("detached child emitted exit during grace period", {
childPid: child.pid ?? null, childPid: child.pid ?? null,
}); });
clearTimeout(timer); clearTimeout(timer);
@@ -381,7 +366,7 @@ async function startDaemon(): Promise<DesktopDaemonStatus> {
}); });
}); });
appendLaunchLog("detached startup grace period completed", { logDesktopDaemonLifecycle("detached startup grace period completed", {
childPid: child.pid ?? null, childPid: child.pid ?? null,
exitedEarly, exitedEarly,
}); });
@@ -395,7 +380,7 @@ async function startDaemon(): Promise<DesktopDaemonStatus> {
for (let attempt = 0; attempt < STARTUP_POLL_MAX_ATTEMPTS; attempt++) { for (let attempt = 0; attempt < STARTUP_POLL_MAX_ATTEMPTS; attempt++) {
const status = resolveStatus(); const status = resolveStatus();
if (attempt === 0 || attempt === STARTUP_POLL_MAX_ATTEMPTS - 1 || attempt % 10 === 9) { if (attempt === 0 || attempt === STARTUP_POLL_MAX_ATTEMPTS - 1 || attempt % 10 === 9) {
appendLaunchLog("polling daemon status after detached start", { logDesktopDaemonLifecycle("polling daemon status after detached start", {
attempt: attempt + 1, attempt: attempt + 1,
status: status.status, status: status.status,
pid: status.pid, pid: status.pid,
+1 -47
View File
@@ -1,5 +1,5 @@
import { fileURLToPath } from "url"; import { fileURLToPath } from "url";
import { appendFileSync, existsSync } from "node:fs"; import { existsSync } from "node:fs";
import path from "node:path"; import path from "node:path";
import { loadConfig } from "../src/server/config.js"; import { loadConfig } from "../src/server/config.js";
import { acquirePidLock, PidLockError, releasePidLock } from "../src/server/pid-lock.js"; import { acquirePidLock, PidLockError, releasePidLock } from "../src/server/pid-lock.js";
@@ -68,28 +68,6 @@ function resolvePackagedNodeEntrypointRunnerPath(currentScriptPath: string): str
return existsSync(runnerPath) ? runnerPath : null; return existsSync(runnerPath) ? runnerPath : null;
} }
function appendLaunchLog(
launchLogPath: string,
message: string,
details?: Record<string, unknown>,
): void {
const line = [
new Date().toISOString(),
"[DaemonRunner]",
`pid=${process.pid}`,
message,
details ? JSON.stringify(details) : "",
]
.filter((part) => part.length > 0)
.join(" ");
try {
appendFileSync(launchLogPath, `${line}\n`, "utf-8");
} catch {
// Keep launch debugging best-effort so daemon startup behavior is unchanged.
}
}
async function main(): Promise<void> { async function main(): Promise<void> {
const config = parseConfig(process.argv.slice(2)); const config = parseConfig(process.argv.slice(2));
const workerEntry = config.devMode ? resolveDevWorkerEntry() : resolveWorkerEntry(); const workerEntry = config.devMode ? resolveDevWorkerEntry() : resolveWorkerEntry();
@@ -106,34 +84,14 @@ async function main(): Promise<void> {
applySherpaLoaderEnv(workerEnv); applySherpaLoaderEnv(workerEnv);
const paseoHome = resolvePaseoHome(workerEnv); const paseoHome = resolvePaseoHome(workerEnv);
const launchLogPath = path.join(paseoHome, "daemon-launch.log");
const daemonConfig = loadConfig(paseoHome, { env: workerEnv }); const daemonConfig = loadConfig(paseoHome, { env: workerEnv });
appendLaunchLog(launchLogPath, "daemon runner starting", {
argv: process.argv.slice(2),
execPath: process.execPath,
execArgv: process.execArgv,
workerEntry,
workerExecArgv,
devMode: config.devMode,
listen: daemonConfig.listen,
packagedNodeEntrypointRunner,
});
try { try {
await acquirePidLock(paseoHome, daemonConfig.listen, { await acquirePidLock(paseoHome, daemonConfig.listen, {
ownerPid: process.pid, ownerPid: process.pid,
}); });
appendLaunchLog(launchLogPath, "pid lock acquired", {
ownerPid: process.pid,
listen: daemonConfig.listen,
});
} catch (error) { } catch (error) {
if (error instanceof PidLockError) { if (error instanceof PidLockError) {
appendLaunchLog(launchLogPath, "pid lock acquisition failed", {
message: error.message,
existingLock: error.existingLock,
});
process.stderr.write(`${error.message}\n`); process.stderr.write(`${error.message}\n`);
process.exit(1); process.exit(1);
return; return;
@@ -147,9 +105,6 @@ async function main(): Promise<void> {
return; return;
} }
lockReleased = true; lockReleased = true;
appendLaunchLog(launchLogPath, "releasing pid lock", {
ownerPid: process.pid,
});
await releasePidLock(paseoHome, { await releasePidLock(paseoHome, {
ownerPid: process.pid, ownerPid: process.pid,
}); });
@@ -157,7 +112,6 @@ async function main(): Promise<void> {
runSupervisor({ runSupervisor({
name: "DaemonRunner", name: "DaemonRunner",
launchLogPath,
startupMessage: config.devMode startupMessage: config.devMode
? "Starting daemon worker (dev mode, crash restarts enabled)" ? "Starting daemon worker (dev mode, crash restarts enabled)"
: "Starting daemon worker (IPC restart enabled)", : "Starting daemon worker (IPC restart enabled)",
-89
View File
@@ -1,5 +1,4 @@
import { fork, spawn, type ChildProcess } from "child_process"; import { fork, spawn, type ChildProcess } from "child_process";
import { appendFileSync } from "node:fs";
type WorkerLifecycleMessage = type WorkerLifecycleMessage =
| { | {
@@ -13,7 +12,6 @@ type WorkerLifecycleMessage =
type SupervisorOptions = { type SupervisorOptions = {
name: string; name: string;
startupMessage: string; startupMessage: string;
launchLogPath?: string;
resolveWorkerEntry: () => string; resolveWorkerEntry: () => string;
workerArgs?: string[]; workerArgs?: string[];
workerEnv?: NodeJS.ProcessEnv; workerEnv?: NodeJS.ProcessEnv;
@@ -65,28 +63,6 @@ export function runSupervisor(options: SupervisorOptions): void {
process.stderr.write(`[${options.name}] ${message}\n`); process.stderr.write(`[${options.name}] ${message}\n`);
}; };
const logLaunch = (message: string, details?: Record<string, unknown>): void => {
if (!options.launchLogPath) {
return;
}
const line = [
new Date().toISOString(),
`[${options.name}]`,
`pid=${process.pid}`,
message,
details ? JSON.stringify(details) : "",
]
.filter((part) => part.length > 0)
.join(" ");
try {
appendFileSync(options.launchLogPath, `${line}\n`, "utf-8");
} catch {
// Keep launch debugging best-effort so daemon startup behavior is unchanged.
}
};
const exitSupervisor = (code: number): void => { const exitSupervisor = (code: number): void => {
if (exiting) { if (exiting) {
return; return;
@@ -116,22 +92,11 @@ export function runSupervisor(options: SupervisorOptions): void {
const spawnSpec = resolveWorkerSpawnSpec?.(workerEntry) ?? null; const spawnSpec = resolveWorkerSpawnSpec?.(workerEntry) ?? null;
if (spawnSpec) { if (spawnSpec) {
logLaunch("spawning worker via explicit spawn spec", {
workerEntry,
command: spawnSpec.command,
args: spawnSpec.args,
workerArgs,
});
child = spawn(spawnSpec.command, spawnSpec.args, { child = spawn(spawnSpec.command, spawnSpec.args, {
stdio: ["inherit", "inherit", "inherit", "ipc"], stdio: ["inherit", "inherit", "inherit", "ipc"],
env: spawnSpec.env ?? workerEnv, env: spawnSpec.env ?? workerEnv,
}); });
} else { } else {
logLaunch("spawning worker via fork", {
workerEntry,
workerArgs,
workerExecArgv,
});
child = fork(workerEntry, workerArgs, { child = fork(workerEntry, workerArgs, {
stdio: "inherit", stdio: "inherit",
env: workerEnv, env: workerEnv,
@@ -139,35 +104,8 @@ export function runSupervisor(options: SupervisorOptions): void {
}); });
} }
logLaunch("worker spawn returned", {
childPid: child.pid ?? null,
connected: child.connected,
spawnfile: child.spawnfile,
spawnargs: child.spawnargs,
});
child.on("spawn", () => {
logLaunch("worker emitted spawn", {
childPid: child?.pid ?? null,
});
});
child.on("error", (error) => {
logLaunch("worker emitted error", {
childPid: child?.pid ?? null,
message: error.message,
});
});
child.on("message", (msg: unknown) => { child.on("message", (msg: unknown) => {
const lifecycleMessage = parseLifecycleMessage(msg); const lifecycleMessage = parseLifecycleMessage(msg);
logLaunch("worker emitted ipc message", {
childPid: child?.pid ?? null,
rawType:
typeof msg === "object" && msg !== null && "type" in msg
? (msg as { type?: unknown }).type
: typeof msg,
});
if (!lifecycleMessage) { if (!lifecycleMessage) {
return; return;
} }
@@ -181,13 +119,6 @@ export function runSupervisor(options: SupervisorOptions): void {
}); });
child.on("exit", (code, signal) => { child.on("exit", (code, signal) => {
logLaunch("worker emitted exit", {
childPid: child?.pid ?? null,
code,
signal,
restarting,
shuttingDown,
});
const exitDescriptor = describeExit(code, signal); const exitDescriptor = describeExit(code, signal);
if (shuttingDown) { if (shuttingDown) {
@@ -206,20 +137,6 @@ export function runSupervisor(options: SupervisorOptions): void {
log(`Worker exited (${exitDescriptor}). Supervisor exiting.`); log(`Worker exited (${exitDescriptor}). Supervisor exiting.`);
exitSupervisor(typeof code === "number" ? code : 0); exitSupervisor(typeof code === "number" ? code : 0);
}); });
child.on("close", (code, signal) => {
logLaunch("worker emitted close", {
childPid: child?.pid ?? null,
code,
signal,
});
});
child.on("disconnect", () => {
logLaunch("worker emitted disconnect", {
childPid: child?.pid ?? null,
});
});
}; };
const requestRestart = (reason: string) => { const requestRestart = (reason: string) => {
@@ -252,12 +169,6 @@ export function runSupervisor(options: SupervisorOptions): void {
process.on("SIGINT", () => forwardSignal("SIGINT")); process.on("SIGINT", () => forwardSignal("SIGINT"));
process.on("SIGTERM", () => forwardSignal("SIGTERM")); process.on("SIGTERM", () => forwardSignal("SIGTERM"));
logLaunch("supervisor starting", {
startupMessage: options.startupMessage,
restartOnCrash,
workerArgs,
workerExecArgv,
});
process.stdout.write(`[${options.name}] ${options.startupMessage}\n`); process.stdout.write(`[${options.name}] ${options.startupMessage}\n`);
spawnWorker(); spawnWorker();
} }