mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-30 22:06:31 +00:00
fix(scripts): bound gateway watch log capture
This commit is contained in:
@@ -42,6 +42,33 @@ const WATCH_GATEWAY_SKIP_ENV = {
|
||||
NODE_ENV: "test",
|
||||
};
|
||||
|
||||
export const WATCH_LOG_CAPTURE_MAX_CHARS = 2 * 1024 * 1024;
|
||||
const WATCH_BUILD_DETECTION_MAX_CHARS = 4096;
|
||||
|
||||
export function appendBoundedWatchLog(current, chunk, maxChars = WATCH_LOG_CAPTURE_MAX_CHARS) {
|
||||
const next = `${current}${String(chunk)}`;
|
||||
if (next.length <= maxChars) {
|
||||
return { text: next, truncated: false };
|
||||
}
|
||||
return { text: next.slice(-maxChars), truncated: true };
|
||||
}
|
||||
|
||||
function formatCapturedWatchLog(text, truncated) {
|
||||
return truncated ? `[openclaw] log truncated to last ${WATCH_LOG_CAPTURE_MAX_CHARS} chars\n${text}` : text;
|
||||
}
|
||||
|
||||
export function updateWatchBuildDetection(state, chunk) {
|
||||
const combined = `${state.buffer ?? ""}${String(chunk)}`;
|
||||
const next = appendBoundedWatchLog("", combined, WATCH_BUILD_DETECTION_MAX_CHARS);
|
||||
const reason = detectWatchBuildReason(combined, "");
|
||||
const triggered = state.triggered || combined.includes("Building TypeScript (dist is stale");
|
||||
return {
|
||||
buffer: next.text,
|
||||
triggered,
|
||||
reason: state.reason ?? reason,
|
||||
};
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
const options = { ...DEFAULTS };
|
||||
for (let i = 0; i < argv.length; i += 1) {
|
||||
@@ -464,11 +491,20 @@ async function runTimedWatch(options, outputDir) {
|
||||
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
let stdoutTruncated = false;
|
||||
let stderrTruncated = false;
|
||||
let buildDetection = { buffer: "", triggered: false, reason: null };
|
||||
child.stdout?.on("data", (chunk) => {
|
||||
stdout += String(chunk);
|
||||
const next = appendBoundedWatchLog(stdout, chunk);
|
||||
stdout = next.text;
|
||||
stdoutTruncated ||= next.truncated;
|
||||
buildDetection = updateWatchBuildDetection(buildDetection, chunk);
|
||||
});
|
||||
child.stderr?.on("data", (chunk) => {
|
||||
stderr += String(chunk);
|
||||
const next = appendBoundedWatchLog(stderr, chunk);
|
||||
stderr = next.text;
|
||||
stderrTruncated ||= next.truncated;
|
||||
buildDetection = updateWatchBuildDetection(buildDetection, chunk);
|
||||
});
|
||||
|
||||
let watchPid = null;
|
||||
@@ -492,8 +528,8 @@ async function runTimedWatch(options, outputDir) {
|
||||
const idleCpuEndMs = watchPid ? readProcessTreeCpuMs(watchPid) : null;
|
||||
|
||||
const exit = await stopTimedWatchChild(child, watchPid, options);
|
||||
fs.writeFileSync(stdoutPath, stdout, "utf8");
|
||||
fs.writeFileSync(stderrPath, stderr, "utf8");
|
||||
fs.writeFileSync(stdoutPath, formatCapturedWatchLog(stdout, stdoutTruncated), "utf8");
|
||||
fs.writeFileSync(stderrPath, formatCapturedWatchLog(stderr, stderrTruncated), "utf8");
|
||||
const timing = fs.existsSync(timeFilePath)
|
||||
? parseTimingFile(timeFilePath)
|
||||
: { userSeconds: Number.NaN, sysSeconds: Number.NaN, elapsedSeconds: Number.NaN };
|
||||
@@ -509,6 +545,8 @@ async function runTimedWatch(options, outputDir) {
|
||||
stdoutPath,
|
||||
stderrPath,
|
||||
timeFilePath,
|
||||
watchTriggeredBuild: buildDetection.triggered,
|
||||
watchBuildReason: buildDetection.reason,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -706,15 +744,8 @@ async function main() {
|
||||
(watchResult.timing.userSeconds + watchResult.timing.sysSeconds) * 1000,
|
||||
);
|
||||
const cpuMs = watchResult.idleCpuMs ?? totalCpuMs;
|
||||
const watchTriggeredBuild =
|
||||
fs
|
||||
.readFileSync(watchResult.stderrPath, "utf8")
|
||||
.includes("Building TypeScript (dist is stale") ||
|
||||
fs.readFileSync(watchResult.stdoutPath, "utf8").includes("Building TypeScript (dist is stale");
|
||||
const watchBuildReason = detectWatchBuildReason(
|
||||
fs.readFileSync(watchResult.stdoutPath, "utf8"),
|
||||
fs.readFileSync(watchResult.stderrPath, "utf8"),
|
||||
);
|
||||
const watchTriggeredBuild = watchResult.watchTriggeredBuild;
|
||||
const watchBuildReason = watchResult.watchBuildReason;
|
||||
|
||||
const summary = {
|
||||
windowMs: options.windowMs,
|
||||
|
||||
@@ -4,9 +4,12 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
appendBoundedWatchLog,
|
||||
hasGatewayReadyLog,
|
||||
shouldRefreshBuildStampForRestoredArtifacts,
|
||||
stopTimedWatchChild,
|
||||
updateWatchBuildDetection,
|
||||
WATCH_LOG_CAPTURE_MAX_CHARS,
|
||||
writeBuildAndRuntimePostBuildStamps,
|
||||
} from "../../scripts/check-gateway-watch-regression.mjs";
|
||||
import {
|
||||
@@ -21,6 +24,34 @@ describe("check-gateway-watch-regression", () => {
|
||||
expect(hasGatewayReadyLog("[gateway] starting HTTP server...")).toBe(false);
|
||||
});
|
||||
|
||||
it("bounds in-memory watch output capture while keeping the newest logs", () => {
|
||||
const first = appendBoundedWatchLog("abc", "def", 8);
|
||||
expect(first).toEqual({ text: "abcdef", truncated: false });
|
||||
|
||||
const second = appendBoundedWatchLog(first.text, "ghijkl", 8);
|
||||
expect(second).toEqual({ text: "efghijkl", truncated: true });
|
||||
expect(second.text).toHaveLength(8);
|
||||
expect(WATCH_LOG_CAPTURE_MAX_CHARS).toBeGreaterThan(1024);
|
||||
});
|
||||
|
||||
it("keeps build-regression detection after diagnostic logs truncate", () => {
|
||||
const detected = updateWatchBuildDetection(
|
||||
{ buffer: "", triggered: false, reason: null },
|
||||
"Building TypeScript (dist is stale: source_mtime_newer)\n",
|
||||
);
|
||||
const afterNoise = updateWatchBuildDetection(detected, "x".repeat(10_000));
|
||||
|
||||
expect(afterNoise.triggered).toBe(true);
|
||||
expect(afterNoise.reason).toBe("source_mtime_newer");
|
||||
|
||||
const coalesced = updateWatchBuildDetection(
|
||||
{ buffer: "", triggered: false, reason: null },
|
||||
`Building TypeScript (dist is stale: config_newer)\n${"x".repeat(10_000)}`,
|
||||
);
|
||||
expect(coalesced.triggered).toBe(true);
|
||||
expect(coalesced.reason).toBe("config_newer");
|
||||
});
|
||||
|
||||
it("refreshes restored build stamps only for skip-build config mtime drift", () => {
|
||||
expect(
|
||||
shouldRefreshBuildStampForRestoredArtifacts({
|
||||
|
||||
Reference in New Issue
Block a user