Skip to content

Commit cd46a34

Browse files
authored
Merge pull request #784 from Wibias/fix/726-log-pagination
fix(logs): raise in-memory log buffer and support limit/offset
2 parents b1f33ab + 217db04 commit cd46a34

24 files changed

Lines changed: 121 additions & 62 deletions

.github/scripts/enforce-pr-target.test.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ describe("enforce-pr-target workflow", () => {
6565
assert.match(workflow, /stackedBase/);
6666
assert.match(workflow, /github\.rest\.pulls\.list/);
6767
assert.match(workflow, /treating as stacked/);
68+
assert.match(workflow, /other\.base\?\.repo\?\.owner/);
6869
const qualityCall = workflow.match(
6970
/collectPrQualityFailures\(\{([\s\S]*?)\}\);/,
7071
);

.github/workflows/enforce-pr-target.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,8 +299,8 @@ jobs:
299299
other =>
300300
other.number !== pull_number &&
301301
other.head?.ref === pr.base.ref &&
302-
(other.head?.repo?.owner?.login ?? owner) === baseOwner &&
303-
(other.head?.repo?.name ?? repo) === baseName
302+
(other.base?.repo?.owner?.login ?? owner) === baseOwner &&
303+
(other.base?.repo?.name ?? repo) === baseName
304304
);
305305
if (stackedBase) {
306306
core.info(

docs-site/src/content/docs/guides/web-dashboard.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ The GUI is a thin client over the proxy's JSON management API. Useful endpoints
137137
| `GET /api/codex-auth/accounts?refresh=1` | List main and pool accounts, force quota refresh, and report main-account `hasCredential` / terminal `needsReauth` state. |
138138
| `PUT /api/codex-auth/active` · `PUT /api/codex-auth/auto-switch` · `PUT /api/codex-auth/failover` | Select the account for the next request and configure pool routing. |
139139
| `POST /api/codex-auth/login` · `GET /api/codex-auth/login-status` | Add a pool account through browser login. |
140-
| `GET /api/logs?tail=50&provider=...&status=5xx` | Read recent request metadata with optional tail, provider, and exact/class status filters. |
140+
| `GET /api/logs?tail=50&limit=20&offset=0&provider=...&status=5xx` | Read recent request metadata with optional tail, provider, and exact/class status filters. With `limit`/`offset`, paging walks backward from the newest row (`offset=0` returns the latest page). Response shape: `{ timeZone, total, logs }` where `total` is the filtered row count before pagination. |
141141
| `GET` / `PUT /api/subagent-models` | Read or set the five featured `spawn_agent` override models. |
142142
| `POST /api/stop` | Stop the proxy/service, restore native Codex, and exit. |
143143

docs-site/src/content/docs/ja/guides/web-dashboard.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ GUI はプロキシの JSON 管理 API を使うシンクライアントです
113113
| `GET /api/codex-auth/accounts?refresh=1` | メインおよびプールアカウントを参照しクォータを強制更新し、メインの `hasCredential` / terminal `needsReauth` 状態を返します。 |
114114
| `PUT /api/codex-auth/active` · `PUT /api/codex-auth/auto-switch` · `PUT /api/codex-auth/failover` | 次のリクエストで使うアカウントとプールルーティングポリシーを設定します。 |
115115
| `POST /api/codex-auth/login` · `GET /api/codex-auth/login-status` | ブラウザログインでプールアカウントを追加します。 |
116-
| `GET /api/logs?tail=50&provider=...&status=5xx` | tail、プロバイダー、正確な状態コードまたは状態等級で最近のリクエストメタデータを参照します。 |
116+
| `GET /api/logs?tail=50&limit=20&offset=0&provider=...&status=5xx` | tail、プロバイダー、正確な状態コードまたは状態等級で最近のリクエストメタデータを参照します`limit`/`offset` は最新行から過去方向にページングします(`offset=0` が最新ページ)。応答は `{ timeZone, total, logs }` で、`total` はページング前の一致件数です|
117117
| `GET` / `PUT /api/subagent-models` | `spawn_agent` に優先公開するモデル 5 つを読むか設定します。 |
118118
| `POST /api/stop` | プロキシ/サービスを停止しネイティブ Codex を復元した後終了します。 |
119119

docs-site/src/content/docs/ko/guides/web-dashboard.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ GUI는 프록시의 JSON 관리 API를 사용하는 얇은 클라이언트입니
115115
| `GET /api/codex-auth/accounts?refresh=1` | main 및 pool 계정을 조회하고 할당량을 강제로 갱신하며 main 계정의 `hasCredential` / terminal `needsReauth` 상태를 표시합니다. |
116116
| `PUT /api/codex-auth/active` · `PUT /api/codex-auth/auto-switch` · `PUT /api/codex-auth/failover` | 다음 요청에 사용할 계정과 풀 라우팅 정책을 설정합니다. |
117117
| `POST /api/codex-auth/login` · `GET /api/codex-auth/login-status` | 브라우저 로그인으로 pool 계정을 추가합니다. |
118-
| `GET /api/logs?tail=50&provider=...&status=5xx` | tail, 프로바이더, 정확한 상태 코드 또는 상태 등급으로 최근 요청 메타데이터를 조회합니다. |
118+
| `GET /api/logs?tail=50&limit=20&offset=0&provider=...&status=5xx` | tail, 프로바이더, 정확한 상태 코드 또는 상태 등급으로 최근 요청 메타데이터를 조회합니다. `limit`/`offset`은 최신 행에서 과거 방향으로 페이지네이션합니다(`offset=0`이 최신 페이지). 응답은 `{ timeZone, total, logs }`이며 `total`은 페이지네이션 전 필터 일치 건수입니다. |
119119
| `GET` / `PUT /api/subagent-models` | `spawn_agent`에 우선 노출할 모델 5개를 읽거나 설정합니다. |
120120
| `POST /api/stop` | 프록시/서비스를 멈추고 네이티브 Codex를 복원한 뒤 종료합니다. |
121121

docs-site/src/content/docs/ru/guides/web-dashboard.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ GUI — это тонкий клиент поверх JSON-API управлен
119119
| `GET /api/codex-auth/accounts?refresh=1` | Список основного и пуловых аккаунтов с принудительным обновлением квот и состояниями `hasCredential` / terminal `needsReauth` основного аккаунта. |
120120
| `PUT /api/codex-auth/active` · `PUT /api/codex-auth/auto-switch` · `PUT /api/codex-auth/failover` | Выбор аккаунта для следующего запроса и настройка маршрутизации пула. |
121121
| `POST /api/codex-auth/login` · `GET /api/codex-auth/login-status` | Добавление аккаунта пула через вход в браузере. |
122-
| `GET /api/logs?tail=50&provider=...&status=5xx` | Чтение метаданных недавних запросов с необязательными фильтрами tail, провайдера и точного/классового статуса. |
122+
| `GET /api/logs?tail=50&limit=20&offset=0&provider=...&status=5xx` | Чтение метаданных недавних запросов с необязательными фильтрами tail, провайдера и точного/классового статуса. `limit`/`offset` листают назад от самой новой строки (`offset=0` — последняя страница). Ответ: `{ timeZone, total, logs }`, где `total` — число совпадений до пагинации. |
123123
| `GET` / `PUT /api/subagent-models` | Чтение или настройка пяти выделенных моделей переопределения `spawn_agent`. |
124124
| `POST /api/stop` | Остановка прокси/сервиса, восстановление нативного Codex и выход. |
125125

docs-site/src/content/docs/zh-cn/guides/web-dashboard.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ GUI 是代理 JSON 管理 API 之上的轻量客户端。常用 endpoint 包括
107107
| `GET /api/codex-auth/accounts?refresh=1` | 列出主账号与池账号、强制刷新配额,并返回主账号的 `hasCredential` / terminal `needsReauth` 状态。 |
108108
| `PUT /api/codex-auth/active` · `PUT /api/codex-auth/auto-switch` · `PUT /api/codex-auth/failover` | 选择下一次请求使用的账号并配置账号池路由。 |
109109
| `POST /api/codex-auth/login` · `GET /api/codex-auth/login-status` | 通过浏览器登录添加池账号。 |
110-
| `GET /api/logs?tail=50&provider=...&status=5xx` | 使用 tail、provider、精确状态码或状态类别筛选近期请求元数据。 |
110+
| `GET /api/logs?tail=50&limit=20&offset=0&provider=...&status=5xx` | 使用 tail、provider、精确状态码或状态类别筛选近期请求元数据`limit`/`offset` 从最新一行向前分页(`offset=0` 为最新一页)。响应为 `{ timeZone, total, logs }`,其中 `total` 为分页前的匹配行数|
111111
| `GET` / `PUT /api/subagent-models` | 读取或设置五个置顶的 `spawn_agent` override 模型。 |
112112
| `POST /api/stop` | 停止代理/服务,恢复原生 Codex 并退出。 |
113113

gui/src/pages/Logs.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -395,9 +395,10 @@ export default function Logs({ apiBase }: { apiBase: string }) {
395395
const selectTab = selectLogsTab;
396396

397397
const loadLogs = useCallback(async (signal: AbortSignal): Promise<LogEntry[]> => {
398-
const res = await fetch(`${apiBase}/api/logs`, { signal });
398+
const res = await fetch(`${apiBase}/api/logs?limit=2000`, { signal });
399399
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`.trim());
400-
const next = await res.json() as LogEntry[];
400+
const body = await res.json() as LogEntry[] | { logs?: LogEntry[] };
401+
const next = Array.isArray(body) ? body : (body.logs ?? []);
401402
writeSessionListCache(logsCacheKey(apiBase), next);
402403
return next;
403404
}, [apiBase]);

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ import {
6868
} from "../../lib/debug-settings";
6969
import type { OcxClaudeCodeConfig, OcxConfig, OcxCustomModel, OcxProviderConfig } from "../../types";
7070
import { drainAndShutdown } from "../lifecycle";
71-
import { filterRequestLogs, getRequestLogEntries, type RequestLogEntry } from "../request-log";
71+
import { filterRequestLogs, filteredRequestLogCount, getRequestLogEntries, type RequestLogEntry } from "../request-log";
7272
import { estimateComboCost, estimateRequestCost, normalizeCostTokens, tokensPerSecond } from "../../usage/cost";
7373
import type { PersistedUsageAttempt } from "../../usage/log";
7474
import { isAllowedRequestOrigin, jsonResponse, providerManagementConfigError, publicProviderBaseUrl, safeConfigDTO } from "../auth-cors";
@@ -124,8 +124,14 @@ 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 total = filteredRequestLogCount(all, url.searchParams);
129+
const logs = filterRequestLogs(all, url.searchParams);
130+
return jsonResponse({
131+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
132+
total,
133+
logs: logs.map(requestLogDto),
134+
});
129135
}
130136

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

src/server/request-log.ts

Lines changed: 24 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,9 +760,32 @@ 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 startOffset = Number.isFinite(offset) && offset > 0 ? offset : 0;
771+
const end = filtered.length - startOffset;
772+
if (end <= 0) filtered = [];
773+
else {
774+
const begin = Math.max(0, end - capped);
775+
filtered = filtered.slice(begin, end);
776+
}
777+
}
778+
}
763779
return filtered;
764780
}
765781

782+
export function filteredRequestLogCount(logs: RequestLogEntry[], params: URLSearchParams): number {
783+
const withoutPagination = new URLSearchParams(params);
784+
withoutPagination.delete("limit");
785+
withoutPagination.delete("offset");
786+
return filterRequestLogs(logs, withoutPagination).length;
787+
}
788+
766789
interface FinalizedUsageResult {
767790
usage?: OcxUsage;
768791
status: UsageStatus;

0 commit comments

Comments
 (0)