Download src/daemon/diagnostics.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 4.36 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/daemon/diagnostics.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/daemon/diagnostics.ts
-
curl -L -o diagnostics.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/daemon/diagnostics.ts
4.36 kB
| /** Reads recent gateway service logs for actionable daemon restart diagnostics. */ | |
| import fs, { type FileHandle } from "node:fs/promises"; | |
| import { readFileWindowFully } from "../infra/file-read.js"; | |
| import { resolveGatewayLogPaths, resolveGatewaySupervisorLogPaths } from "./restart-logs.js"; | |
| // Error patterns worth surfacing from gateway service logs after failed starts. | |
| const GATEWAY_LOG_ERROR_PATTERNS = [ | |
| /\bENOSPC\b/i, | |
| /no space left on device/i, | |
| /refusing to bind gateway/i, | |
| /gateway auth mode/i, | |
| /gateway start blocked/i, | |
| /failed to bind gateway socket/i, | |
| /tailscale .* requires/i, | |
| ]; | |
| const GATEWAY_DIAGNOSTIC_LOG_TAIL_BYTES = 256 * 1024; | |
| async function readTailWindow(handle: FileHandle, size: number) { | |
| const length = Math.min(size, GATEWAY_DIAGNOSTIC_LOG_TAIL_BYTES); | |
| const readStart = size - length; | |
| const buffer = Buffer.alloc(length); | |
| const bytesRead = await readFileWindowFully(handle, buffer, readStart); | |
| return { buffer, bytesRead, readStart }; | |
| } | |
| /** Reads complete lines from a bounded gateway log tail. */ | |
| export async function readGatewayLogTailLines(filePath: string): Promise<string[]> { | |
| const handle = await fs.open(filePath, "r"); | |
| try { | |
| const stat = await handle.stat(); | |
| if (!stat.isFile() || stat.size <= 0) { | |
| return []; | |
| } | |
| let window = await readTailWindow(handle, stat.size); | |
| if (window.bytesRead < window.buffer.length) { | |
| const refreshedStat = await handle.stat(); | |
| if (!refreshedStat.isFile() || refreshedStat.size <= 0) { | |
| return []; | |
| } | |
| if (refreshedStat.size !== stat.size) { | |
| window = await readTailWindow(handle, refreshedStat.size); | |
| } | |
| } | |
| const { buffer, bytesRead, readStart } = window; | |
| let textStart = 0; | |
| if (readStart > 0) { | |
| const precedingByte = Buffer.alloc(1); | |
| const precedingRead = await handle.read(precedingByte, 0, 1, readStart - 1); | |
| if (precedingRead.bytesRead !== 1 || precedingByte[0] !== 0x0a) { | |
| // A byte-bound tail can start inside a line or UTF-8 sequence. Drop that | |
| // fragment so diagnostics never report a stale, corrupted partial line. | |
| const firstNewline = buffer.subarray(0, bytesRead).indexOf(0x0a); | |
| if (firstNewline === -1) { | |
| return []; | |
| } | |
| textStart = firstNewline + 1; | |
| } | |
| } | |
| const lines = buffer.subarray(textStart, bytesRead).toString("utf8").split(/\r?\n/u); | |
| if (lines.at(-1) === "") { | |
| lines.pop(); | |
| } | |
| return lines; | |
| } finally { | |
| await handle.close(); | |
| } | |
| } | |
| function findLastNonEmptyLine(lines: string[]): string | null { | |
| for (let i = lines.length - 1; i >= 0; i -= 1) { | |
| const line = lines[i]?.trim(); | |
| if (line) { | |
| return line; | |
| } | |
| } | |
| return null; | |
| } | |
| export async function readLastGatewayErrorLine( | |
| env: NodeJS.ProcessEnv, | |
| options?: { platform?: NodeJS.Platform; requirePatternMatch?: boolean }, | |
| ): Promise<string | null> { | |
| const platform = options?.platform ?? process.platform; | |
| const readStderr = platform !== "darwin"; | |
| // launchd combines child stderr into stdout only because the plist points both | |
| // handles at one file (buildLaunchAgentPlist); break that and darwin startup | |
| // crashes stop reaching this reader. Other platforms keep stderr separate. | |
| const { stdoutPath, stderrPath } = | |
| platform === "darwin" | |
| ? resolveGatewaySupervisorLogPaths(env, { platform }) | |
| : resolveGatewayLogPaths(env); | |
| const stderrLines = readStderr ? await readGatewayLogTailLines(stderrPath).catch(() => []) : []; | |
| const stdoutLines = await readGatewayLogTailLines(stdoutPath).catch(() => []); | |
| // stderr is the strongest failure signal on non-darwin platforms, so place it | |
| // last and scan from the end: the most recent stderr error line then wins over | |
| // any (possibly stale) stdout match, matching the stderr-first fallback below. | |
| const lines = [...stdoutLines, ...stderrLines].map((line) => line.trim()); | |
| for (let i = lines.length - 1; i >= 0; i -= 1) { | |
| const line = lines[i]; | |
| if (!line) { | |
| continue; | |
| } | |
| if (GATEWAY_LOG_ERROR_PATTERNS.some((pattern) => pattern.test(line))) { | |
| return line; | |
| } | |
| } | |
| if (options?.requirePatternMatch) { | |
| return null; | |
| } | |
| return readStderr | |
| ? (findLastNonEmptyLine(stderrLines) ?? findLastNonEmptyLine(stdoutLines)) | |
| : findLastNonEmptyLine(stdoutLines); | |
| } | |