Skip to content

Commit b7e59c0

Browse files
committed
fix: address issue #726
Fixes #726
1 parent be177ea commit b7e59c0

11 files changed

Lines changed: 59 additions & 37 deletions

gui/src/pages/Logs.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -368,9 +368,10 @@ export default function Logs({ apiBase }: { apiBase: string }) {
368368
// failures flicker between the error banner, empty state, and stale table.
369369
if (!silent) setLoading(true);
370370
try {
371-
const res = await fetch(`${apiBase}/api/logs`);
371+
const res = await fetch(`${apiBase}/api/logs?limit=2000`);
372372
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`.trim());
373-
const next = await res.json() as LogEntry[];
373+
const body = await res.json() as LogEntry[] | { logs?: LogEntry[] };
374+
const next = Array.isArray(body) ? body : (body.logs ?? []);
374375
setLogs(next);
375376
writeSessionListCache(logsCacheKey(apiBase), next);
376377
setError(null);

src/server/management/logs-usage-routes.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,13 @@ export async function handleLogsUsageRoutes(ctx: ManagementContext): Promise<Res
124124
const { req, url, config, deps, refreshCodexCatalogBestEffort, syncClaudeAgentDefsBestEffort } = ctx;
125125

126126
if (url.pathname === "/api/logs" && req.method === "GET") {
127-
const logs = filterRequestLogs(getRequestLogEntries(), url.searchParams);
128-
return jsonResponse(logs.map(requestLogDto));
127+
const all = getRequestLogEntries();
128+
const logs = filterRequestLogs(all, url.searchParams);
129+
return jsonResponse({
130+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
131+
total: all.length,
132+
logs: logs.map(requestLogDto),
133+
});
129134
}
130135

131136
if (url.pathname === "/api/debug" && req.method === "GET") {

src/server/request-log.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ export interface RequestLogEntry {
126126
}
127127

128128
const requestLog: RequestLogEntry[] = [];
129-
const MAX_LOG_SIZE = 200;
129+
const MAX_LOG_SIZE = 2000;
130130
let requestLogSeq = 0;
131131
/** True after hydrateRequestLogsFromDisk ran once in this process. */
132132
let requestLogsHydratedFromDisk = false;
@@ -760,6 +760,17 @@ export function filterRequestLogs(logs: RequestLogEntry[], params: URLSearchPara
760760
const tail = Number.parseInt(tailRaw, 10);
761761
if (Number.isFinite(tail) && tail > 0) filtered = filtered.slice(-Math.min(tail, MAX_LOG_SIZE));
762762
}
763+
const offsetRaw = params.get("offset")?.trim();
764+
const limitRaw = params.get("limit")?.trim();
765+
if (limitRaw) {
766+
const limit = Number.parseInt(limitRaw, 10);
767+
const offset = offsetRaw ? Number.parseInt(offsetRaw, 10) : 0;
768+
if (Number.isFinite(limit) && limit > 0) {
769+
const capped = Math.min(limit, MAX_LOG_SIZE);
770+
const start = Number.isFinite(offset) && offset > 0 ? offset : 0;
771+
filtered = filtered.slice(start, start + capped);
772+
}
773+
}
763774
return filtered;
764775
}
765776

tests/helpers/logs-api.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export function logsFromApiBody<T extends Record<string, unknown> = Record<string, unknown>>(body: unknown): T[] {
2+
if (Array.isArray(body)) return body as T[];
3+
if (body && typeof body === "object" && Array.isArray((body as { logs?: unknown }).logs)) {
4+
return (body as { logs: T[] }).logs;
5+
}
6+
return [];
7+
}

tests/management-api-logs-metrics.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ async function readLogs(): Promise<Array<Record<string, any>>> {
1616
const url = new URL("http://localhost/api/logs");
1717
const response = await handleManagementAPI(new Request(url), url, config);
1818
expect(response?.status).toBe(200);
19-
return await response!.json() as Array<Record<string, any>>;
19+
const body = await response!.json() as { logs?: Array<Record<string, any>>; timeZone?: string };
20+
expect(typeof body.timeZone).toBe("string");
21+
expect(body.timeZone!.length).toBeGreaterThan(0);
22+
return body.logs ?? [];
2023
}
2124

2225
function baseEntry(overrides: Partial<RequestLogEntry>): RequestLogEntry {

tests/openai-api-virtual-models.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { afterEach, describe, expect, test } from "bun:test";
2+
import { logsFromApiBody } from "./helpers/logs-api";
23
import { managementHeaders } from "./helpers/management-auth";
34
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
45
import { tmpdir } from "node:os";
@@ -231,7 +232,8 @@ describe("OpenAI API compact transport", () => {
231232

232233
const server = startServer(0);
233234
const readLogs = () => originalFetch(new URL("/api/logs", server.url), { headers: managementHeaders() })
234-
.then(response => response.json()) as Promise<Array<Record<string, unknown>>>;
235+
.then(response => response.json())
236+
.then(body => logsFromApiBody(body));
235237
const readUsage = (): Array<Record<string, unknown>> => existsSync(usageLogPath())
236238
? readFileSync(usageLogPath(), "utf8").trim().split("\n").filter(Boolean).map(line => JSON.parse(line) as Record<string, unknown>)
237239
: [];
@@ -399,7 +401,8 @@ describe("OpenAI API Pro transport identities", () => {
399401
? readFileSync(usageLogPath(), "utf8").trim().split("\n").filter(Boolean).map(line => JSON.parse(line) as Record<string, unknown>)
400402
: [];
401403
const readLogs = () => originalFetch(new URL("/api/logs", server.url), { headers: managementHeaders() })
402-
.then(response => response.json()) as Promise<Array<Record<string, unknown>>>;
404+
.then(response => response.json())
405+
.then(body => logsFromApiBody(body));
403406
const expectOnePersisted = async (
404407
beforeLogs: number,
405408
beforeUsage: number,

tests/openai-provider-option-e2e.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { logsFromApiBody } from "./helpers/logs-api";
12
import { describe, expect, test } from "bun:test";
23
import { managementFetch as fetch } from "./helpers/management-auth";
34
import { createHash } from "node:crypto";
@@ -486,7 +487,7 @@ describe("OpenAI provider-option integration spine", () => {
486487
expect((await put("/api/injection-model", { model: selected, effort: "high" })).status).toBe(200);
487488
expect(await local("/api/injection-model").then(response => response.json())).toMatchObject({ model: selected, effort: "high" });
488489

489-
const logs = await local("/api/logs").then(response => response.json()) as Array<Record<string, unknown>>;
490+
const logs = logsFromApiBody(await local("/api/logs").then(response => response.json()));
490491
expect(logs.some(row => row.provider === "openai-p123abc"
491492
&& row.requestedModel === "gpt-5.6-sol"
492493
&& row.resolvedModel === "gpt-5.6-sol")).toBe(true);

tests/request-log.test.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,12 @@ describe("request log metadata", () => {
544544
expect(combined.map(entry => entry.requestId)).toEqual(["c"]);
545545
});
546546

547+
test("filters logs by offset and limit", () => {
548+
const logs = Array.from({ length: 5 }, (_, i) => log({ requestId: `r${i}`, provider: "openai", status: 200 }));
549+
expect(filterRequestLogs(logs, new URLSearchParams("limit=2")).map(entry => entry.requestId)).toEqual(["r0", "r1"]);
550+
expect(filterRequestLogs(logs, new URLSearchParams("offset=2&limit=2")).map(entry => entry.requestId)).toEqual(["r2", "r3"]);
551+
});
552+
547553
test("deferred JSON logging preserves response service tier before final log", async () => {
548554
const entries: RequestLogEntry[] = [];
549555
const logCtx = {
@@ -1250,7 +1256,7 @@ describe("request log restart hydrate", () => {
12501256

12511257
test("hydrate keeps only the newest MAX_LOG_SIZE rows from a long usage.jsonl", () => {
12521258
clearRequestLogsForTests();
1253-
const persisted: PersistedUsageEntry[] = Array.from({ length: 205 }, (_, i) => ({
1259+
const persisted: PersistedUsageEntry[] = Array.from({ length: 2005 }, (_, i) => ({
12541260
requestId: `ocx-${i}`,
12551261
timestamp: i,
12561262
provider: "openai",
@@ -1259,10 +1265,10 @@ describe("request log restart hydrate", () => {
12591265
durationMs: 1,
12601266
usageStatus: "unreported" as const,
12611267
}));
1262-
expect(hydrateRequestLogsFromDisk(() => persisted)).toBe(200);
1268+
expect(hydrateRequestLogsFromDisk(() => persisted)).toBe(2000);
12631269
const ids = getRequestLogEntries().map(e => e.requestId);
12641270
expect(ids[0]).toBe("ocx-5");
1265-
expect(ids.at(-1)).toBe("ocx-204");
1271+
expect(ids.at(-1)).toBe("ocx-2004");
12661272
});
12671273

12681274
test("hydrate swallows usage.jsonl read failures instead of crashing startup", () => {

tests/server-403-permission-e2e.test.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2+
import { logsFromApiBody } from "./helpers/logs-api";
23
import { managementFetch as fetch } from "./helpers/management-auth";
34
import { mkdtempSync, rmSync } from "node:fs";
45
import { tmpdir } from "node:os";
@@ -83,11 +84,7 @@ async function runUpstreamFailure(status: 401 | 403, body: unknown): Promise<{
8384
const payload = await response.json() as {
8485
error: { message?: string; type?: string; code?: string | null };
8586
};
86-
const logs = await fetch(new URL("/api/logs?tail=1", proxy.url)).then(res => res.json()) as Array<{
87-
status?: number;
88-
errorCode?: string;
89-
upstreamError?: string;
90-
}>;
87+
const logs = logsFromApiBody(await fetch(new URL("/api/logs?tail=1", proxy.url)).then(res => res.json()));
9188
return {
9289
path,
9390
responseStatus: response.status,

tests/server-auth.test.ts

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2+
import { logsFromApiBody } from "./helpers/logs-api";
23
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
34
import { request as httpRequest } from "node:http";
45
import { tmpdir } from "node:os";
@@ -1620,7 +1621,7 @@ describe("server local API auth", () => {
16201621
ws.close();
16211622

16221623
expect(seenAuth).toEqual(["Bearer old-access-token", "Bearer new-access-token"]);
1623-
const logs = await fetch(new URL("/api/logs?tail=2", server.url), { headers: managementHeaders() }).then(r => r.json()) as Array<{ status: number }>;
1624+
const logs = logsFromApiBody(await fetch(new URL("/api/logs?tail=2", server.url), { headers: managementHeaders() }).then(r => r.json()));
16241625
expect(logs.map(entry => entry.status)).toEqual([200, 200]);
16251626
} finally {
16261627
Date.now = originalNow;
@@ -1692,14 +1693,7 @@ describe("server local API auth", () => {
16921693
await waitForTerminal();
16931694
ws.close();
16941695

1695-
const logs = await fetch(new URL("/api/logs?tail=1", server.url), { headers: managementHeaders() }).then(r => r.json()) as Array<{
1696-
status: number;
1697-
terminalStatus?: string;
1698-
closeReason?: string;
1699-
usageStatus?: string;
1700-
totalTokens?: number;
1701-
usage?: { inputTokens: number; outputTokens: number; cachedInputTokens?: number };
1702-
}>;
1696+
const logs = logsFromApiBody(await fetch(new URL("/api/logs?tail=1", server.url), { headers: managementHeaders() }).then(r => r.json()));
17031697
expect(logs.at(-1)).toMatchObject({
17041698
status: 200,
17051699
terminalStatus: "completed",
@@ -2210,7 +2204,7 @@ describe("server local API auth", () => {
22102204
consecutiveFailures: 3,
22112205
lastFailureStatus: 502,
22122206
});
2213-
const logs = await fetch(new URL("/api/logs?tail=1", server.url), { headers: managementHeaders() }).then(r => r.json()) as Array<{ status: number; errorCode?: string; terminalStatus?: string; closeReason?: string }>;
2207+
const logs = logsFromApiBody(await fetch(new URL("/api/logs?tail=1", server.url), { headers: managementHeaders() }).then(r => r.json()));
22142208
expect(logs.at(-1)).toMatchObject({
22152209
status: 502,
22162210
errorCode: "upstream_server_error",
@@ -2266,14 +2260,7 @@ describe("server local API auth", () => {
22662260

22672261
expect(response.status).toBe(200);
22682262
await response.text();
2269-
const logs = await fetch(new URL("/api/logs?tail=1", server.url), { headers: managementHeaders() }).then(r => r.json()) as Array<{
2270-
status: number;
2271-
terminalStatus?: string;
2272-
closeReason?: string;
2273-
usageStatus?: string;
2274-
totalTokens?: number;
2275-
usage?: { inputTokens: number; outputTokens: number; cachedInputTokens?: number; reasoningOutputTokens?: number };
2276-
}>;
2263+
const logs = logsFromApiBody(await fetch(new URL("/api/logs?tail=1", server.url), { headers: managementHeaders() }).then(r => r.json()));
22772264
expect(logs.at(-1)).toMatchObject({
22782265
status: 200,
22792266
terminalStatus: "completed",

0 commit comments

Comments
 (0)