From b0db1dfd5da5926f7f5c11220faae8ba8b900edf Mon Sep 17 00:00:00 2001 From: LTbinglingfeng Date: Mon, 15 Jun 2026 22:51:08 +0800 Subject: [PATCH] feat(logs): enhance log fetching with incremental support using cursor and after parameters --- src/pages/LogsPage.tsx | 76 +++++++++++++++++++++++++++++----------- src/services/api/logs.ts | 18 +++++++--- 2 files changed, 69 insertions(+), 25 deletions(-) diff --git a/src/pages/LogsPage.tsx b/src/pages/LogsPage.tsx index 66eb6c8..44066b7 100644 --- a/src/pages/LogsPage.tsx +++ b/src/pages/LogsPage.tsx @@ -52,9 +52,27 @@ const MAX_BUFFER_LINES = 10000; const LONG_PRESS_MS = 650; const LONG_PRESS_MOVE_THRESHOLD = 10; -const getIncrementalAfter = (cursor: LogsQuery['after']): LogsQuery['after'] => { - if (typeof cursor !== 'number') return cursor; - return cursor > 1 ? cursor - 1 : undefined; +type LogPosition = Pick; + +const getIncrementalAfter = (after: LogsQuery['after']): LogsQuery['after'] => { + if (typeof after !== 'number') return after; + return after > 1 ? after - 1 : undefined; +}; + +const buildLogsQuery = (incremental: boolean, position: LogPosition): LogsQuery => { + const params: LogsQuery = { limit: MAX_BUFFER_LINES }; + if (!incremental) return params; + + if (position.cursor) { + params.cursor = position.cursor; + } + + const after = getIncrementalAfter(position.after); + if (after !== undefined) { + params.after = after; + } + + return params; }; const findLineOverlap = (currentLines: string[], incomingLines: string[]): number => { @@ -174,8 +192,29 @@ export function LogsPage() { const logRequestInFlightRef = useRef(false); const pendingFullReloadRef = useRef(false); - // 保存最新游标用于增量获取 - const latestCursorRef = useRef(undefined); + // 保存最新游标用于增量获取;新 CPA 后端优先使用 cursor,旧接口和 Home 继续使用 after。 + const logPositionRef = useRef({}); + + const resetLogPosition = () => { + logPositionRef.current = {}; + }; + + const updateLogPosition = ( + data: Awaited>, + incremental: boolean + ) => { + const currentPosition = logPositionRef.current; + const nextPosition: LogPosition = {}; + if (data.nextCursor) { + nextPosition.cursor = data.nextCursor; + } + if (data.latestAfter !== undefined) { + nextPosition.after = data.latestAfter; + } else if (incremental && currentPosition.after !== undefined) { + nextPosition.after = currentPosition.after; + } + logPositionRef.current = nextPosition; + }; const disableControls = connectionStatus !== 'connected'; const refreshDisabled = disableControls || loading || cpaNeedsFileLogging; @@ -190,7 +229,7 @@ export function LogsPage() { if (cpaNeedsFileLogging) { if (!incremental) { - latestCursorRef.current = undefined; + resetLogPosition(); requestLogHomeIpByIdRef.current = {}; setFileLoggingRequired(false); setLogState({ buffer: [], visibleFrom: 0 }); @@ -222,19 +261,12 @@ export function LogsPage() { scrollerInstance?.requestScrollToBottom(); } - const params: LogsQuery = - incremental && latestCursorRef.current - ? { after: getIncrementalAfter(latestCursorRef.current), limit: MAX_BUFFER_LINES } - : { limit: MAX_BUFFER_LINES }; + const params = buildLogsQuery(incremental, logPositionRef.current); const data = await logsApi.fetchLogs(params); setFileLoggingRequired(false); - // 更新游标 - if (data.latestCursor) { - latestCursorRef.current = data.latestCursor; - } else if (!incremental) { - latestCursorRef.current = undefined; - } + updateLogPosition(data, incremental); + if (data.requestLogHomeIpById) { requestLogHomeIpByIdRef.current = incremental ? { ...requestLogHomeIpByIdRef.current, ...data.requestLogHomeIpById } @@ -245,7 +277,11 @@ export function LogsPage() { const newLines = Array.isArray(data.lines) ? data.lines : []; - if (incremental && newLines.length > 0) { + if (incremental && data.cursorReset) { + const buffer = newLines.slice(-MAX_BUFFER_LINES); + const visibleFrom = Math.max(buffer.length - INITIAL_DISPLAY_LINES, 0); + setLogState({ buffer, visibleFrom }); + } else if (incremental && newLines.length > 0) { // 增量更新:追加新日志并限制缓冲区大小(避免内存与渲染膨胀) setLogState((prev) => { const prevRenderedCount = prev.buffer.length - prev.visibleFrom; @@ -271,7 +307,7 @@ export function LogsPage() { console.error('Failed to load logs:', err); if (isLoggingToFileDisabledError(err)) { if (!incremental) { - latestCursorRef.current = undefined; + resetLogPosition(); requestLogHomeIpByIdRef.current = {}; setFileLoggingRequired(true); setLogState({ buffer: [], visibleFrom: 0 }); @@ -318,7 +354,7 @@ export function LogsPage() { try { await logsApi.clearLogs(); setLogState({ buffer: [], visibleFrom: 0 }); - latestCursorRef.current = undefined; + resetLogPosition(); requestLogHomeIpByIdRef.current = {}; setFileLoggingRequired(false); showNotification(t('logs.clear_success'), 'success'); @@ -429,7 +465,7 @@ export function LogsPage() { useEffect(() => { if (connectionStatus === 'connected') { - latestCursorRef.current = undefined; + resetLogPosition(); requestLogHomeIpByIdRef.current = {}; setFileLoggingRequired(false); loadLogs(false); diff --git a/src/services/api/logs.ts b/src/services/api/logs.ts index ef4bf70..de97de4 100644 --- a/src/services/api/logs.ts +++ b/src/services/api/logs.ts @@ -11,6 +11,7 @@ export type LogBackendKind = 'unknown' | 'file' | 'home-db'; export interface LogsQuery { after?: LogCursor; + cursor?: string; limit?: number; offset?: number; } @@ -42,7 +43,9 @@ export interface HomeLogsResponse { export interface LogsResponse { lines: string[]; lineCount: number; - latestCursor?: LogCursor; + latestAfter?: LogCursor; + nextCursor?: string; + cursorReset?: boolean; logBackendKind: LogBackendKind; requestLogHomeIpById?: Record; total?: number; @@ -67,6 +70,9 @@ const numberValue = (value: unknown): number | undefined => { return Number.isFinite(parsed) ? parsed : undefined; }; +const booleanValue = (value: unknown): boolean => + value === true || (typeof value === 'string' && value.trim().toLowerCase() === 'true'); + const positiveNumberValue = (value: unknown): number | undefined => { const parsed = numberValue(value); return parsed !== undefined && parsed > 0 ? parsed : undefined; @@ -104,8 +110,10 @@ const normalizeCPALogs = (data: Record): LogsResponse => { return { lines, lineCount: Number.isFinite(lineCount) ? lineCount : lines.length, - latestCursor: latestTimestamp > 0 ? latestTimestamp : undefined, - logBackendKind: 'file' + latestAfter: latestTimestamp > 0 ? latestTimestamp : undefined, + nextCursor: stringValue(data['next-cursor']) || undefined, + cursorReset: booleanValue(data['cursor-reset']), + logBackendKind: 'file', }; }; @@ -140,12 +148,12 @@ const normalizeHomeLogs = (data: Record): LogsResponse => { return { lines, lineCount: Number.isFinite(total) ? total : lines.length, - latestCursor, + latestAfter: latestCursor, logBackendKind: 'home-db', requestLogHomeIpById, total: Number.isFinite(total) ? total : undefined, limit: Number.isFinite(limit) ? limit : undefined, - offset: Number.isFinite(offset) ? offset : undefined + offset: Number.isFinite(offset) ? offset : undefined, }; };