



















@@ -37,6 +37,21 @@ const OUTPUT_CAPTURE_CHARS = readPositiveInt(
3737);
3838const DEFAULT_PORT = 19000 + Math.floor(Math.random() * 1000);
3939const LOG_SCAN_CHUNK_BYTES = 64 * 1024;
40+const LOG_SCAN_MAX_LINE_CHARS = 16 * 1024;
41+const LOG_TAIL_BYTES = 256 * 1024;
42+const ERROR_LOG_DENY_PATTERNS = [
43+/\buncaught exception\b/iu,
44+/\bunhandled rejection\b/iu,
45+/\bfatal\b/iu,
46+/\bpanic\b/iu,
47+/\blevel["']?\s*:\s*["']error["']/iu,
48+/\[(?:error|ERROR)\]/u,
49+];
50+const ERROR_LOG_ALLOW_PATTERNS = [
51+/0 errors?/iu,
52+/expected no diagnostics errors?/iu,
53+/diagnostics errors?:\s*$/iu,
54+];
40554156let callGatewayModulePromise;
4257@@ -1138,37 +1153,109 @@ export function assertResourceCeiling(sample) {
11381153}
11391154}
114011551156+export function findErrorLogFindings(logPath) {
1157+if (!fs.existsSync(logPath)) {
1158+return [];
1159+}
1160+const scanBytes = fs.statSync(logPath).size;
1161+1162+const findings = [];
1163+let currentLine = "";
1164+let currentLineNumber = 1;
1165+let currentLineHasFinding = false;
1166+let currentLineTruncated = false;
1167+const recordLine = (lineNumber, line) => {
1168+if (currentLineHasFinding) {
1169+return;
1170+}
1171+if (
1172+ERROR_LOG_ALLOW_PATTERNS.some((pattern) => pattern.test(line)) ||
1173+!ERROR_LOG_DENY_PATTERNS.some((pattern) => pattern.test(line))
1174+) {
1175+return;
1176+}
1177+currentLineHasFinding = true;
1178+findings.push({ line, lineNumber });
1179+if (findings.length > 20) {
1180+findings.shift();
1181+}
1182+};
1183+const inspectCurrentLine = () => {
1184+const normalizedLine = currentLine.replace(/\r$/u, "");
1185+const line = currentLineTruncated ? `[truncated] ${normalizedLine}` : normalizedLine;
1186+recordLine(currentLineNumber, line);
1187+};
1188+const appendLineFragment = (fragment) => {
1189+currentLine += fragment;
1190+if (currentLine.length <= LOG_SCAN_MAX_LINE_CHARS) {
1191+return;
1192+}
1193+inspectCurrentLine();
1194+currentLine = currentLine.slice(-LOG_SCAN_MAX_LINE_CHARS);
1195+currentLineTruncated = true;
1196+};
1197+const finishLine = () => {
1198+inspectCurrentLine();
1199+currentLine = "";
1200+currentLineNumber += 1;
1201+currentLineHasFinding = false;
1202+currentLineTruncated = false;
1203+};
1204+1205+const fd = fs.openSync(logPath, "r");
1206+try {
1207+const buffer = Buffer.allocUnsafe(LOG_SCAN_CHUNK_BYTES);
1208+let offset = 0;
1209+while (offset < scanBytes) {
1210+const bytesToRead = Math.min(buffer.length, scanBytes - offset);
1211+const bytesRead = fs.readSync(fd, buffer, 0, bytesToRead, offset);
1212+if (bytesRead <= 0) {
1213+break;
1214+}
1215+offset += bytesRead;
1216+const lines = buffer.subarray(0, bytesRead).toString("utf8").split(/\n/u);
1217+for (const [index, line] of lines.entries()) {
1218+appendLineFragment(line);
1219+if (index < lines.length - 1) {
1220+finishLine();
1221+}
1222+}
1223+}
1224+} finally {
1225+fs.closeSync(fd);
1226+}
1227+if (currentLine) {
1228+inspectCurrentLine();
1229+}
1230+return findings;
1231+}
1232+11411233function assertNoErrorLogs(logPath) {
1142-const log = fs.existsSync(logPath) ? fs.readFileSync(logPath, "utf8") : "";
1143-const deny = [
1144-/\buncaught exception\b/iu,
1145-/\bunhandled rejection\b/iu,
1146-/\bfatal\b/iu,
1147-/\bpanic\b/iu,
1148-/\blevel["']?\s*:\s*["']error["']/iu,
1149-/\[(?:error|ERROR)\]/u,
1150-];
1151-const allow = [/0 errors?/iu, /expected no diagnostics errors?/iu, /diagnostics errors?:\s*$/iu];
1152-const findings = log
1153-.split(/\r?\n/u)
1154-.map((line, index) => ({ line, lineNumber: index + 1 }))
1155-.filter(({ line }) => !allow.some((pattern) => pattern.test(line)))
1156-.filter(({ line }) => deny.some((pattern) => pattern.test(line)));
1234+const findings = findErrorLogFindings(logPath);
11571235if (findings.length > 0) {
11581236throw new Error(
11591237`unexpected error-like gateway logs:\n${findings
1160- .slice(-20)
11611238 .map(({ line, lineNumber }) => `${logPath}:${lineNumber}: ${line}`)
11621239 .join("\n")}`,
11631240);
11641241}
11651242}
116612431167-function tailFile(file) {
1244+export function tailFile(file, maxBytes = LOG_TAIL_BYTES) {
11681245if (!fs.existsSync(file)) {
11691246return "";
11701247}
1171-return tailText(fs.readFileSync(file, "utf8"));
1248+const stat = fs.statSync(file);
1249+const start = Math.max(0, stat.size - Math.max(1, maxBytes));
1250+const length = stat.size - start;
1251+const fd = fs.openSync(file, "r");
1252+try {
1253+const buffer = Buffer.allocUnsafe(length);
1254+fs.readSync(fd, buffer, 0, length, start);
1255+return tailText(buffer.toString("utf8"));
1256+} finally {
1257+fs.closeSync(fd);
1258+}
11721259}
1173126011741261function tailText(text) {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。