feat(logs): enhance log fetching with incremental support using cursor and after parameters

This commit is contained in:
LTbinglingfeng
2026-06-15 22:51:08 +08:00
Unverified
parent 1279cc1299
commit b0db1dfd5d
2 changed files with 69 additions and 25 deletions
+56 -20
View File
@@ -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<LogsQuery, 'after' | 'cursor'>;
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<LogsQuery['after']>(undefined);
// 保存最新游标用于增量获取;新 CPA 后端优先使用 cursor,旧接口和 Home 继续使用 after。
const logPositionRef = useRef<LogPosition>({});
const resetLogPosition = () => {
logPositionRef.current = {};
};
const updateLogPosition = (
data: Awaited<ReturnType<typeof logsApi.fetchLogs>>,
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);
+13 -5
View File
@@ -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<string, string>;
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<string, unknown>): 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<string, unknown>): 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,
};
};