mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:20:43 +00:00
fix(qqbot): avoid log export filename collisions (#77765)
* fix(qqbot): avoid log export filename collisions * test(qqbot): narrow log export result assertions
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const platformMock = await vi.hoisted(async () => {
|
||||
const fs = await import("node:fs");
|
||||
const path = await import("node:path");
|
||||
return {
|
||||
fs,
|
||||
homeDir: "",
|
||||
path,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../../utils/platform.js", () => ({
|
||||
getHomeDir: () => platformMock.homeDir,
|
||||
getQQBotDataDir: (...subPaths: string[]) => {
|
||||
const dir = platformMock.path.join(platformMock.homeDir, ".openclaw", "qqbot", ...subPaths);
|
||||
platformMock.fs.mkdirSync(dir, { recursive: true });
|
||||
return dir;
|
||||
},
|
||||
isWindows: () => false,
|
||||
}));
|
||||
|
||||
import { buildBotLogsResult } from "./log-helpers.js";
|
||||
|
||||
describe("buildBotLogsResult", () => {
|
||||
let tempHome: string;
|
||||
|
||||
beforeEach(() => {
|
||||
tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-qqbot-logs-"));
|
||||
platformMock.homeDir = tempHome;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
fs.rmSync(tempHome, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("suffixes same-second log exports instead of overwriting", () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date("2026-05-05T10:11:12.345Z"));
|
||||
const logDir = path.join(tempHome, ".openclaw", "logs");
|
||||
fs.mkdirSync(logDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(logDir, "gateway.log"), "line 1\nline 2\n", "utf8");
|
||||
|
||||
const first = buildBotLogsResult();
|
||||
const second = buildBotLogsResult();
|
||||
|
||||
expect(typeof first).toBe("object");
|
||||
expect(typeof second).toBe("object");
|
||||
if (!first || !second || typeof first === "string" || typeof second === "string") {
|
||||
throw new Error("expected file upload results");
|
||||
}
|
||||
expect(path.basename(first.filePath)).toBe("bot-logs-2026-05-05T10-11-12.txt");
|
||||
expect(path.basename(second.filePath)).toBe("bot-logs-2026-05-05T10-11-12-2.txt");
|
||||
expect(fs.readFileSync(first.filePath, "utf8")).toContain("line 1");
|
||||
expect(fs.readFileSync(second.filePath, "utf8")).toContain("line 2");
|
||||
});
|
||||
});
|
||||
@@ -128,6 +128,28 @@ type LogCandidate = {
|
||||
mtimeMs: number;
|
||||
};
|
||||
|
||||
function addCollisionSuffix(filePath: string, suffix: number): string {
|
||||
const ext = path.extname(filePath);
|
||||
const baseName = path.basename(filePath, ext);
|
||||
return path.join(path.dirname(filePath), `${baseName}-${suffix}${ext}`);
|
||||
}
|
||||
|
||||
function writeNewTextFileSync(filePath: string, contents: string): string {
|
||||
for (let suffix = 1; suffix <= 100; suffix++) {
|
||||
const candidate = suffix === 1 ? filePath : addCollisionSuffix(filePath, suffix);
|
||||
try {
|
||||
fs.writeFileSync(candidate, contents, { encoding: "utf8", flag: "wx" });
|
||||
return candidate;
|
||||
} catch (error) {
|
||||
if (typeof error === "object" && error && "code" in error && error.code === "EEXIST") {
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
throw new Error(`Could not find an unused log export filename near ${filePath}`);
|
||||
}
|
||||
|
||||
function collectRecentLogFiles(logDirs: string[]): LogCandidate[] {
|
||||
const candidates: LogCandidate[] = [];
|
||||
const dedupe = new Set<string>();
|
||||
@@ -303,8 +325,10 @@ export function buildBotLogsResult(): SlashCommandResult {
|
||||
|
||||
const tmpDir = getQQBotDataDir("downloads");
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
||||
const tmpFile = path.join(tmpDir, `bot-logs-${timestamp}.txt`);
|
||||
fs.writeFileSync(tmpFile, lines.join("\n"), "utf8");
|
||||
const tmpFile = writeNewTextFileSync(
|
||||
path.join(tmpDir, `bot-logs-${timestamp}.txt`),
|
||||
lines.join("\n"),
|
||||
);
|
||||
|
||||
const fileCount = recentFiles.length;
|
||||
const topSources = Array.from(new Set(recentFiles.map((item) => item.sourceDir))).slice(0, 3);
|
||||
|
||||
Reference in New Issue
Block a user