feat: add uwf log subcommands (list, show, clean)
- uwf log list: list log files with sizes - uwf log show --thread <id>: filter by thread ID - uwf log show --process <pid>: filter by process ID - uwf log clean --before <date>: delete old log files - Tests: 12 new tests covering all subcommands Implemented by solve-issue workflow, biome fixes applied manually. Closes #413 Refs #411, #410
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { mkdir, readdir, rm, writeFile } from "node:fs/promises";
|
import { mkdir, readdir, rm, writeFile } from "node:fs/promises";
|
||||||
import { join } from "node:path";
|
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
||||||
import { cmdLogClean, cmdLogList, cmdLogShow } from "../commands/log.js";
|
import { cmdLogClean, cmdLogList, cmdLogShow } from "../commands/log.js";
|
||||||
|
|
||||||
@@ -95,21 +95,33 @@ describe("cmdLogList", () => {
|
|||||||
describe("cmdLogShow", () => {
|
describe("cmdLogShow", () => {
|
||||||
test("filters by thread ID", async () => {
|
test("filters by thread ID", async () => {
|
||||||
await writeLogFiles();
|
await writeLogFiles();
|
||||||
const result = await cmdLogShow(storageRoot, { thread: "01J1234ABCDEF", process: null, date: null });
|
const result = await cmdLogShow(storageRoot, {
|
||||||
|
thread: "01J1234ABCDEF",
|
||||||
|
process: null,
|
||||||
|
date: null,
|
||||||
|
});
|
||||||
expect(result).toHaveLength(2);
|
expect(result).toHaveLength(2);
|
||||||
expect(result.every((e) => e.thread === "01J1234ABCDEF")).toBe(true);
|
expect(result.every((e) => e.thread === "01J1234ABCDEF")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("filters by process ID", async () => {
|
test("filters by process ID", async () => {
|
||||||
await writeLogFiles();
|
await writeLogFiles();
|
||||||
const result = await cmdLogShow(storageRoot, { thread: null, process: "1716200000000-1234", date: null });
|
const result = await cmdLogShow(storageRoot, {
|
||||||
|
thread: null,
|
||||||
|
process: "1716200000000-1234",
|
||||||
|
date: null,
|
||||||
|
});
|
||||||
expect(result).toHaveLength(2);
|
expect(result).toHaveLength(2);
|
||||||
expect(result.every((e) => e.pid === "1716200000000-1234")).toBe(true);
|
expect(result.every((e) => e.pid === "1716200000000-1234")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("filters by date", async () => {
|
test("filters by date", async () => {
|
||||||
await writeLogFiles();
|
await writeLogFiles();
|
||||||
const result = await cmdLogShow(storageRoot, { thread: null, process: null, date: "2026-05-19" });
|
const result = await cmdLogShow(storageRoot, {
|
||||||
|
thread: null,
|
||||||
|
process: null,
|
||||||
|
date: "2026-05-19",
|
||||||
|
});
|
||||||
expect(result).toHaveLength(1);
|
expect(result).toHaveLength(1);
|
||||||
expect(result[0].msg).toBe("old entry");
|
expect(result[0].msg).toBe("old entry");
|
||||||
});
|
});
|
||||||
@@ -125,13 +137,21 @@ describe("cmdLogShow", () => {
|
|||||||
|
|
||||||
test("returns empty when no matches", async () => {
|
test("returns empty when no matches", async () => {
|
||||||
await writeLogFiles();
|
await writeLogFiles();
|
||||||
const result = await cmdLogShow(storageRoot, { thread: "NONEXISTENT", process: null, date: null });
|
const result = await cmdLogShow(storageRoot, {
|
||||||
|
thread: "NONEXISTENT",
|
||||||
|
process: null,
|
||||||
|
date: null,
|
||||||
|
});
|
||||||
expect(result).toEqual([]);
|
expect(result).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("combined thread + date filter", async () => {
|
test("combined thread + date filter", async () => {
|
||||||
await writeLogFiles();
|
await writeLogFiles();
|
||||||
const result = await cmdLogShow(storageRoot, { thread: "01J1234ABCDEF", process: null, date: "2026-05-20" });
|
const result = await cmdLogShow(storageRoot, {
|
||||||
|
thread: "01J1234ABCDEF",
|
||||||
|
process: null,
|
||||||
|
date: "2026-05-20",
|
||||||
|
});
|
||||||
expect(result).toHaveLength(2);
|
expect(result).toHaveLength(2);
|
||||||
expect(result.every((e) => e.thread === "01J1234ABCDEF")).toBe(true);
|
expect(result.every((e) => e.thread === "01J1234ABCDEF")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
cmdCasSchemaList,
|
cmdCasSchemaList,
|
||||||
cmdCasWalk,
|
cmdCasWalk,
|
||||||
} from "./commands/cas.js";
|
} from "./commands/cas.js";
|
||||||
|
import { cmdLogClean, cmdLogList, cmdLogShow } from "./commands/log.js";
|
||||||
import { cmdSetup, cmdSetupInteractive } from "./commands/setup.js";
|
import { cmdSetup, cmdSetupInteractive } from "./commands/setup.js";
|
||||||
import { cmdSkillCli } from "./commands/skill.js";
|
import { cmdSkillCli } from "./commands/skill.js";
|
||||||
import {
|
import {
|
||||||
@@ -29,7 +30,6 @@ import {
|
|||||||
THREAD_READ_DEFAULT_QUOTA,
|
THREAD_READ_DEFAULT_QUOTA,
|
||||||
} from "./commands/thread.js";
|
} from "./commands/thread.js";
|
||||||
import { cmdWorkflowList, cmdWorkflowPut, cmdWorkflowShow } from "./commands/workflow.js";
|
import { cmdWorkflowList, cmdWorkflowPut, cmdWorkflowShow } from "./commands/workflow.js";
|
||||||
import { cmdLogClean, cmdLogList, cmdLogShow } from "./commands/log.js";
|
|
||||||
import { formatOutput, type OutputFormat } from "./format.js";
|
import { formatOutput, type OutputFormat } from "./format.js";
|
||||||
import { resolveStorageRoot } from "./store.js";
|
import { resolveStorageRoot } from "./store.js";
|
||||||
|
|
||||||
@@ -399,17 +399,23 @@ log
|
|||||||
.option("--thread <thread-id>", "Filter by thread ID")
|
.option("--thread <thread-id>", "Filter by thread ID")
|
||||||
.option("--process <pid>", "Filter by process ID")
|
.option("--process <pid>", "Filter by process ID")
|
||||||
.option("--date <date>", "Filter by date (YYYY-MM-DD)")
|
.option("--date <date>", "Filter by date (YYYY-MM-DD)")
|
||||||
.action((opts: { thread: string | undefined; process: string | undefined; date: string | undefined }) => {
|
.action(
|
||||||
const storageRoot = resolveStorageRoot();
|
(opts: {
|
||||||
runAction(async () => {
|
thread: string | undefined;
|
||||||
const result = await cmdLogShow(storageRoot, {
|
process: string | undefined;
|
||||||
thread: opts.thread ?? null,
|
date: string | undefined;
|
||||||
process: opts.process ?? null,
|
}) => {
|
||||||
date: opts.date ?? null,
|
const storageRoot = resolveStorageRoot();
|
||||||
|
runAction(async () => {
|
||||||
|
const result = await cmdLogShow(storageRoot, {
|
||||||
|
thread: opts.thread ?? null,
|
||||||
|
process: opts.process ?? null,
|
||||||
|
date: opts.date ?? null,
|
||||||
|
});
|
||||||
|
writeOutput(result);
|
||||||
});
|
});
|
||||||
writeOutput(result);
|
},
|
||||||
});
|
);
|
||||||
});
|
|
||||||
|
|
||||||
log
|
log
|
||||||
.command("clean")
|
.command("clean")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { readFile, readdir, stat, unlink } from "node:fs/promises";
|
import { readdir, readFile, stat, unlink } from "node:fs/promises";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
|
|
||||||
type LogListItem = {
|
type LogListItem = {
|
||||||
@@ -45,7 +45,10 @@ function dateFromFilename(name: string): string {
|
|||||||
|
|
||||||
async function parseJsonlFile(path: string): Promise<Array<LogEntry>> {
|
async function parseJsonlFile(path: string): Promise<Array<LogEntry>> {
|
||||||
const content = await readFile(path, "utf-8");
|
const content = await readFile(path, "utf-8");
|
||||||
const lines = content.trim().split("\n").filter((l) => l.length > 0);
|
const lines = content
|
||||||
|
.trim()
|
||||||
|
.split("\n")
|
||||||
|
.filter((l) => l.length > 0);
|
||||||
return lines.map((line) => JSON.parse(line) as LogEntry);
|
return lines.map((line) => JSON.parse(line) as LogEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,10 +99,7 @@ export async function cmdLogShow(
|
|||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function cmdLogClean(
|
export async function cmdLogClean(storageRoot: string, before: string): Promise<LogCleanResult> {
|
||||||
storageRoot: string,
|
|
||||||
before: string,
|
|
||||||
): Promise<LogCleanResult> {
|
|
||||||
const dir = logsDir(storageRoot);
|
const dir = logsDir(storageRoot);
|
||||||
const files = await listLogFiles(dir);
|
const files = await listLogFiles(dir);
|
||||||
let deleted = 0;
|
let deleted = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user