2025-04-04 20:53:12 +08:00
|
|
|
import {
|
|
|
|
|
createContext,
|
|
|
|
|
Dispatch,
|
|
|
|
|
ReactNode,
|
|
|
|
|
SetStateAction,
|
|
|
|
|
useCallback,
|
|
|
|
|
useContext,
|
|
|
|
|
useEffect,
|
2025-06-01 20:29:49 +08:00
|
|
|
useMemo,
|
2025-04-04 20:53:12 +08:00
|
|
|
useState,
|
|
|
|
|
} from 'react';
|
2025-02-28 00:34:02 +08:00
|
|
|
|
2025-04-07 22:38:55 +08:00
|
|
|
import {
|
|
|
|
|
CoreApp,
|
2025-05-21 01:28:35 +08:00
|
|
|
DataFrame,
|
2025-04-07 22:38:55 +08:00
|
|
|
LogLevel,
|
|
|
|
|
LogRowModel,
|
|
|
|
|
LogsDedupStrategy,
|
|
|
|
|
LogsMetaItem,
|
|
|
|
|
LogsSortOrder,
|
|
|
|
|
shallowCompare,
|
|
|
|
|
store,
|
|
|
|
|
} from '@grafana/data';
|
2025-02-28 00:34:02 +08:00
|
|
|
import { PopoverContent } from '@grafana/ui';
|
|
|
|
|
|
2025-06-01 20:29:49 +08:00
|
|
|
import { DownloadFormat, checkLogsError, checkLogsSampled, downloadLogs as download } from '../../utils';
|
2025-04-07 22:38:55 +08:00
|
|
|
|
2025-06-01 20:29:49 +08:00
|
|
|
import { GetRowContextQueryFn, LogLineMenuCustomItem } from './LogLineMenu';
|
2025-05-21 01:28:35 +08:00
|
|
|
import { LogListModel } from './processing';
|
2025-02-28 00:34:02 +08:00
|
|
|
|
2025-05-21 01:28:35 +08:00
|
|
|
export interface LogListContextData extends Omit<Props, 'containerElement' | 'logs' | 'logsMeta' | 'showControls'> {
|
|
|
|
|
closeDetails: () => void;
|
|
|
|
|
detailsDisplayed: (log: LogListModel) => boolean;
|
|
|
|
|
detailsWidth: number;
|
2025-04-07 22:38:55 +08:00
|
|
|
downloadLogs: (format: DownloadFormat) => void;
|
2025-05-21 01:28:35 +08:00
|
|
|
enableLogDetails: boolean;
|
2025-04-04 20:53:12 +08:00
|
|
|
filterLevels: LogLevel[];
|
2025-06-01 20:29:49 +08:00
|
|
|
hasLogsWithErrors?: boolean;
|
|
|
|
|
hasSampledLogs?: boolean;
|
2025-04-11 02:47:17 +08:00
|
|
|
hasUnescapedContent?: boolean;
|
2025-06-01 20:29:49 +08:00
|
|
|
logLineMenuCustomItems?: LogLineMenuCustomItem[];
|
2025-04-04 20:53:12 +08:00
|
|
|
setDedupStrategy: (dedupStrategy: LogsDedupStrategy) => void;
|
2025-05-21 01:28:35 +08:00
|
|
|
setDetailsWidth: (width: number) => void;
|
2025-04-04 20:53:12 +08:00
|
|
|
setFilterLevels: (filterLevels: LogLevel[]) => void;
|
2025-04-11 02:47:17 +08:00
|
|
|
setForceEscape: (forceEscape: boolean) => void;
|
2025-04-04 20:53:12 +08:00
|
|
|
setLogListState: Dispatch<SetStateAction<LogListState>>;
|
|
|
|
|
setPinnedLogs: (pinnedlogs: string[]) => void;
|
2025-04-07 22:38:55 +08:00
|
|
|
setPrettifyJSON: (prettifyJSON: boolean) => void;
|
2025-04-04 20:53:12 +08:00
|
|
|
setSyntaxHighlighting: (syntaxHighlighting: boolean) => void;
|
|
|
|
|
setShowTime: (showTime: boolean) => void;
|
2025-04-07 22:38:55 +08:00
|
|
|
setShowUniqueLabels: (showUniqueLabels: boolean) => void;
|
2025-04-04 20:53:12 +08:00
|
|
|
setSortOrder: (sortOrder: LogsSortOrder) => void;
|
|
|
|
|
setWrapLogMessage: (showTime: boolean) => void;
|
2025-05-21 01:28:35 +08:00
|
|
|
showDetails: LogListModel[];
|
|
|
|
|
toggleDetails: (log: LogListModel) => void;
|
2025-02-28 00:34:02 +08:00
|
|
|
}
|
|
|
|
|
|
2025-04-04 20:53:12 +08:00
|
|
|
export const LogListContext = createContext<LogListContextData>({
|
|
|
|
|
app: CoreApp.Unknown,
|
2025-05-21 01:28:35 +08:00
|
|
|
closeDetails: () => {},
|
2025-04-04 20:53:12 +08:00
|
|
|
dedupStrategy: LogsDedupStrategy.none,
|
2025-05-21 01:28:35 +08:00
|
|
|
detailsDisplayed: () => false,
|
|
|
|
|
detailsWidth: 0,
|
2025-04-04 20:53:12 +08:00
|
|
|
displayedFields: [],
|
2025-04-07 22:38:55 +08:00
|
|
|
downloadLogs: () => {},
|
2025-05-21 01:28:35 +08:00
|
|
|
enableLogDetails: false,
|
2025-04-04 20:53:12 +08:00
|
|
|
filterLevels: [],
|
2025-04-11 02:47:17 +08:00
|
|
|
hasUnescapedContent: false,
|
2025-04-04 20:53:12 +08:00
|
|
|
setDedupStrategy: () => {},
|
2025-05-21 01:28:35 +08:00
|
|
|
setDetailsWidth: () => {},
|
2025-04-04 20:53:12 +08:00
|
|
|
setFilterLevels: () => {},
|
2025-04-11 02:47:17 +08:00
|
|
|
setForceEscape: () => {},
|
2025-04-04 20:53:12 +08:00
|
|
|
setLogListState: () => {},
|
|
|
|
|
setPinnedLogs: () => {},
|
2025-04-07 22:38:55 +08:00
|
|
|
setPrettifyJSON: () => {},
|
2025-04-04 20:53:12 +08:00
|
|
|
setShowTime: () => {},
|
2025-04-07 22:38:55 +08:00
|
|
|
setShowUniqueLabels: () => {},
|
2025-04-04 20:53:12 +08:00
|
|
|
setSortOrder: () => {},
|
|
|
|
|
setSyntaxHighlighting: () => {},
|
|
|
|
|
setWrapLogMessage: () => {},
|
2025-05-21 01:28:35 +08:00
|
|
|
showDetails: [],
|
2025-04-04 20:53:12 +08:00
|
|
|
showTime: true,
|
|
|
|
|
sortOrder: LogsSortOrder.Ascending,
|
|
|
|
|
syntaxHighlighting: true,
|
2025-05-21 01:28:35 +08:00
|
|
|
toggleDetails: () => {},
|
2025-04-04 20:53:12 +08:00
|
|
|
wrapLogMessage: false,
|
|
|
|
|
});
|
2025-02-28 00:34:02 +08:00
|
|
|
|
|
|
|
|
export const useLogListContextData = (key: keyof LogListContextData) => {
|
|
|
|
|
const data: LogListContextData = useContext(LogListContext);
|
|
|
|
|
return data[key];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const useLogListContext = (): LogListContextData => {
|
|
|
|
|
return useContext(LogListContext);
|
|
|
|
|
};
|
|
|
|
|
|
2025-05-23 02:12:06 +08:00
|
|
|
export const useLogIsPinned = (log: LogListModel) => {
|
2025-02-28 00:34:02 +08:00
|
|
|
const { pinnedLogs } = useContext(LogListContext);
|
|
|
|
|
return pinnedLogs?.some((logId) => logId === log.rowId);
|
|
|
|
|
};
|
2025-04-04 20:53:12 +08:00
|
|
|
|
2025-05-23 02:12:06 +08:00
|
|
|
export const useLogIsPermalinked = (log: LogListModel) => {
|
|
|
|
|
const { permalinkedLogId } = useContext(LogListContext);
|
|
|
|
|
return permalinkedLogId && permalinkedLogId === log.uid;
|
|
|
|
|
};
|
|
|
|
|
|
2025-04-04 20:53:12 +08:00
|
|
|
export type LogListState = Pick<
|
|
|
|
|
LogListContextData,
|
|
|
|
|
| 'dedupStrategy'
|
2025-04-11 02:47:17 +08:00
|
|
|
| 'forceEscape'
|
2025-04-04 20:53:12 +08:00
|
|
|
| 'filterLevels'
|
2025-04-11 02:47:17 +08:00
|
|
|
| 'hasUnescapedContent'
|
2025-04-04 20:53:12 +08:00
|
|
|
| 'pinnedLogs'
|
2025-04-07 22:38:55 +08:00
|
|
|
| 'prettifyJSON'
|
|
|
|
|
| 'showUniqueLabels'
|
2025-04-04 20:53:12 +08:00
|
|
|
| 'showTime'
|
|
|
|
|
| 'sortOrder'
|
|
|
|
|
| 'syntaxHighlighting'
|
|
|
|
|
| 'wrapLogMessage'
|
|
|
|
|
>;
|
|
|
|
|
|
|
|
|
|
export interface Props {
|
|
|
|
|
app: CoreApp;
|
|
|
|
|
children?: ReactNode;
|
2025-05-21 01:28:35 +08:00
|
|
|
containerElement?: HTMLDivElement;
|
2025-04-04 20:53:12 +08:00
|
|
|
dedupStrategy: LogsDedupStrategy;
|
|
|
|
|
displayedFields: string[];
|
2025-05-21 01:28:35 +08:00
|
|
|
enableLogDetails: boolean;
|
2025-04-04 20:53:12 +08:00
|
|
|
filterLevels?: LogLevel[];
|
2025-04-11 02:47:17 +08:00
|
|
|
forceEscape?: boolean;
|
|
|
|
|
hasUnescapedContent?: boolean;
|
2025-04-04 20:53:12 +08:00
|
|
|
getRowContextQuery?: GetRowContextQueryFn;
|
2025-05-21 01:28:35 +08:00
|
|
|
isLabelFilterActive?: (key: string, value: string, refId?: string) => Promise<boolean>;
|
2025-04-07 22:38:55 +08:00
|
|
|
logs: LogRowModel[];
|
2025-06-01 20:29:49 +08:00
|
|
|
logLineMenuCustomItems?: LogLineMenuCustomItem[];
|
2025-04-07 22:38:55 +08:00
|
|
|
logsMeta?: LogsMetaItem[];
|
2025-04-04 20:53:12 +08:00
|
|
|
logOptionsStorageKey?: string;
|
|
|
|
|
logSupportsContext?: (row: LogRowModel) => boolean;
|
2025-05-21 01:28:35 +08:00
|
|
|
onClickFilterLabel?: (key: string, value: string, frame?: DataFrame) => void;
|
|
|
|
|
onClickFilterOutLabel?: (key: string, value: string, frame?: DataFrame) => void;
|
|
|
|
|
onClickFilterString?: (value: string, refId?: string) => void;
|
|
|
|
|
onClickFilterOutString?: (value: string, refId?: string) => void;
|
|
|
|
|
onClickShowField?: (key: string) => void;
|
|
|
|
|
onClickHideField?: (key: string) => void;
|
2025-04-04 20:53:12 +08:00
|
|
|
onLogOptionsChange?: (option: keyof LogListState, value: string | boolean | string[]) => void;
|
|
|
|
|
onLogLineHover?: (row?: LogRowModel) => void;
|
|
|
|
|
onPermalinkClick?: (row: LogRowModel) => Promise<void>;
|
|
|
|
|
onPinLine?: (row: LogRowModel) => void;
|
|
|
|
|
onOpenContext?: (row: LogRowModel, onClose: () => void) => void;
|
|
|
|
|
onUnpinLine?: (row: LogRowModel) => void;
|
2025-05-23 02:12:06 +08:00
|
|
|
permalinkedLogId?: string;
|
2025-04-04 20:53:12 +08:00
|
|
|
pinLineButtonTooltipTitle?: PopoverContent;
|
|
|
|
|
pinnedLogs?: string[];
|
2025-04-07 22:38:55 +08:00
|
|
|
prettifyJSON?: boolean;
|
2025-04-04 20:53:12 +08:00
|
|
|
showControls: boolean;
|
2025-04-07 22:38:55 +08:00
|
|
|
showUniqueLabels?: boolean;
|
2025-04-04 20:53:12 +08:00
|
|
|
showTime: boolean;
|
|
|
|
|
sortOrder: LogsSortOrder;
|
|
|
|
|
syntaxHighlighting?: boolean;
|
|
|
|
|
wrapLogMessage: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const LogListContextProvider = ({
|
|
|
|
|
app,
|
|
|
|
|
children,
|
2025-05-21 01:28:35 +08:00
|
|
|
containerElement,
|
|
|
|
|
enableLogDetails,
|
2025-04-04 20:53:12 +08:00
|
|
|
dedupStrategy,
|
|
|
|
|
displayedFields,
|
2025-04-11 02:47:17 +08:00
|
|
|
filterLevels,
|
|
|
|
|
forceEscape = false,
|
|
|
|
|
hasUnescapedContent,
|
2025-05-21 01:28:35 +08:00
|
|
|
isLabelFilterActive,
|
2025-04-04 20:53:12 +08:00
|
|
|
getRowContextQuery,
|
2025-04-07 22:38:55 +08:00
|
|
|
logs,
|
2025-06-01 20:29:49 +08:00
|
|
|
logLineMenuCustomItems,
|
2025-04-07 22:38:55 +08:00
|
|
|
logsMeta,
|
2025-04-04 20:53:12 +08:00
|
|
|
logOptionsStorageKey,
|
|
|
|
|
logSupportsContext,
|
2025-05-21 01:28:35 +08:00
|
|
|
onClickFilterLabel,
|
|
|
|
|
onClickFilterOutLabel,
|
|
|
|
|
onClickFilterString,
|
|
|
|
|
onClickFilterOutString,
|
|
|
|
|
onClickShowField,
|
|
|
|
|
onClickHideField,
|
2025-04-04 20:53:12 +08:00
|
|
|
onLogOptionsChange,
|
|
|
|
|
onLogLineHover,
|
|
|
|
|
onPermalinkClick,
|
|
|
|
|
onPinLine,
|
|
|
|
|
onOpenContext,
|
|
|
|
|
onUnpinLine,
|
2025-05-23 02:12:06 +08:00
|
|
|
permalinkedLogId,
|
2025-04-04 20:53:12 +08:00
|
|
|
pinLineButtonTooltipTitle,
|
|
|
|
|
pinnedLogs,
|
2025-04-07 22:38:55 +08:00
|
|
|
prettifyJSON,
|
2025-04-04 20:53:12 +08:00
|
|
|
showControls,
|
|
|
|
|
showTime,
|
2025-04-07 22:38:55 +08:00
|
|
|
showUniqueLabels,
|
2025-04-04 20:53:12 +08:00
|
|
|
sortOrder,
|
2025-04-07 22:38:55 +08:00
|
|
|
syntaxHighlighting,
|
2025-04-04 20:53:12 +08:00
|
|
|
wrapLogMessage,
|
|
|
|
|
}: Props) => {
|
|
|
|
|
const [logListState, setLogListState] = useState<LogListState>({
|
|
|
|
|
dedupStrategy,
|
|
|
|
|
filterLevels:
|
|
|
|
|
filterLevels ?? (logOptionsStorageKey ? store.getObject(`${logOptionsStorageKey}.filterLevels`, []) : []),
|
2025-04-11 02:47:17 +08:00
|
|
|
forceEscape,
|
|
|
|
|
hasUnescapedContent,
|
2025-04-04 20:53:12 +08:00
|
|
|
pinnedLogs,
|
2025-04-07 22:38:55 +08:00
|
|
|
prettifyJSON,
|
2025-04-04 20:53:12 +08:00
|
|
|
showTime,
|
2025-04-07 22:38:55 +08:00
|
|
|
showUniqueLabels,
|
2025-04-04 20:53:12 +08:00
|
|
|
sortOrder,
|
|
|
|
|
syntaxHighlighting,
|
|
|
|
|
wrapLogMessage,
|
|
|
|
|
});
|
2025-05-21 01:28:35 +08:00
|
|
|
const [showDetails, setShowDetails] = useState<LogListModel[]>([]);
|
2025-04-04 20:53:12 +08:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
// Props are updated in the context only of the panel is being externally controlled.
|
|
|
|
|
if (showControls && app !== CoreApp.PanelEditor) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const newState = {
|
|
|
|
|
...logListState,
|
|
|
|
|
dedupStrategy,
|
|
|
|
|
showTime,
|
|
|
|
|
sortOrder,
|
|
|
|
|
syntaxHighlighting,
|
|
|
|
|
wrapLogMessage,
|
|
|
|
|
};
|
|
|
|
|
if (!shallowCompare(logListState, newState)) {
|
|
|
|
|
setLogListState(newState);
|
|
|
|
|
}
|
|
|
|
|
}, [
|
|
|
|
|
app,
|
|
|
|
|
dedupStrategy,
|
|
|
|
|
logListState,
|
|
|
|
|
pinnedLogs,
|
|
|
|
|
showControls,
|
|
|
|
|
showTime,
|
|
|
|
|
sortOrder,
|
|
|
|
|
syntaxHighlighting,
|
|
|
|
|
wrapLogMessage,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (filterLevels === undefined) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!shallowCompare(logListState.filterLevels, filterLevels)) {
|
|
|
|
|
setLogListState({ ...logListState, filterLevels });
|
|
|
|
|
}
|
|
|
|
|
}, [filterLevels, logListState]);
|
|
|
|
|
|
2025-04-11 02:47:17 +08:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (logListState.hasUnescapedContent !== hasUnescapedContent) {
|
|
|
|
|
setLogListState({ ...logListState, hasUnescapedContent });
|
|
|
|
|
}
|
|
|
|
|
}, [hasUnescapedContent, logListState]);
|
|
|
|
|
|
2025-05-23 02:12:06 +08:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (!shallowCompare(logListState.pinnedLogs ?? [], pinnedLogs ?? [])) {
|
|
|
|
|
setLogListState({ ...logListState, pinnedLogs });
|
|
|
|
|
}
|
|
|
|
|
}, [logListState, pinnedLogs]);
|
|
|
|
|
|
2025-06-01 20:29:49 +08:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (!showDetails.length) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const newShowDetails = showDetails.filter(
|
|
|
|
|
(expandedLog) => logs.findIndex((log) => log.uid === expandedLog.uid) >= 0
|
|
|
|
|
);
|
|
|
|
|
if (newShowDetails.length !== showDetails.length) {
|
|
|
|
|
setShowDetails(newShowDetails);
|
|
|
|
|
}
|
|
|
|
|
}, [logs, showDetails]);
|
|
|
|
|
|
2025-05-21 01:28:35 +08:00
|
|
|
const detailsDisplayed = useCallback(
|
|
|
|
|
(log: LogListModel) => !!showDetails.find((shownLog) => shownLog.uid === log.uid),
|
|
|
|
|
[showDetails]
|
|
|
|
|
);
|
|
|
|
|
|
2025-04-04 20:53:12 +08:00
|
|
|
const setDedupStrategy = useCallback(
|
|
|
|
|
(dedupStrategy: LogsDedupStrategy) => {
|
|
|
|
|
setLogListState({ ...logListState, dedupStrategy });
|
|
|
|
|
onLogOptionsChange?.('dedupStrategy', dedupStrategy);
|
|
|
|
|
},
|
|
|
|
|
[logListState, onLogOptionsChange]
|
|
|
|
|
);
|
|
|
|
|
|
2025-04-11 02:47:17 +08:00
|
|
|
const setForceEscape = useCallback(
|
|
|
|
|
(forceEscape: boolean) => {
|
|
|
|
|
setLogListState({ ...logListState, forceEscape });
|
|
|
|
|
},
|
|
|
|
|
[logListState]
|
|
|
|
|
);
|
|
|
|
|
|
2025-04-04 20:53:12 +08:00
|
|
|
const setFilterLevels = useCallback(
|
|
|
|
|
(filterLevels: LogLevel[]) => {
|
|
|
|
|
setLogListState({ ...logListState, filterLevels });
|
|
|
|
|
onLogOptionsChange?.('filterLevels', filterLevels);
|
|
|
|
|
},
|
|
|
|
|
[logListState, onLogOptionsChange]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const setPinnedLogs = useCallback(
|
|
|
|
|
(pinnedLogs: string[]) => {
|
|
|
|
|
setLogListState({ ...logListState, pinnedLogs });
|
|
|
|
|
onLogOptionsChange?.('pinnedLogs', pinnedLogs);
|
|
|
|
|
},
|
|
|
|
|
[logListState, onLogOptionsChange]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const setShowTime = useCallback(
|
|
|
|
|
(showTime: boolean) => {
|
|
|
|
|
setLogListState({ ...logListState, showTime });
|
|
|
|
|
onLogOptionsChange?.('showTime', showTime);
|
|
|
|
|
if (logOptionsStorageKey) {
|
|
|
|
|
store.set(`${logOptionsStorageKey}.showTime`, showTime);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[logListState, logOptionsStorageKey, onLogOptionsChange]
|
|
|
|
|
);
|
|
|
|
|
|
2025-04-07 22:38:55 +08:00
|
|
|
const setShowUniqueLabels = useCallback(
|
|
|
|
|
(showUniqueLabels: boolean) => {
|
|
|
|
|
setLogListState({ ...logListState, showUniqueLabels });
|
|
|
|
|
onLogOptionsChange?.('showUniqueLabels', showUniqueLabels);
|
|
|
|
|
if (logOptionsStorageKey) {
|
|
|
|
|
store.set(`${logOptionsStorageKey}.showLabels`, showUniqueLabels);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[logListState, logOptionsStorageKey, onLogOptionsChange]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const setPrettifyJSON = useCallback(
|
|
|
|
|
(prettifyJSON: boolean) => {
|
|
|
|
|
setLogListState({ ...logListState, prettifyJSON });
|
|
|
|
|
onLogOptionsChange?.('prettifyJSON', prettifyJSON);
|
|
|
|
|
if (logOptionsStorageKey) {
|
|
|
|
|
store.set(`${logOptionsStorageKey}.prettifyLogMessage`, prettifyJSON);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[logListState, logOptionsStorageKey, onLogOptionsChange]
|
|
|
|
|
);
|
|
|
|
|
|
2025-04-04 20:53:12 +08:00
|
|
|
const setSyntaxHighlighting = useCallback(
|
|
|
|
|
(syntaxHighlighting: boolean) => {
|
|
|
|
|
setLogListState({ ...logListState, syntaxHighlighting });
|
|
|
|
|
onLogOptionsChange?.('syntaxHighlighting', syntaxHighlighting);
|
|
|
|
|
if (logOptionsStorageKey) {
|
|
|
|
|
store.set(`${logOptionsStorageKey}.syntaxHighlighting`, syntaxHighlighting);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[logListState, logOptionsStorageKey, onLogOptionsChange]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const setSortOrder = useCallback(
|
|
|
|
|
(sortOrder: LogsSortOrder) => {
|
|
|
|
|
setLogListState({ ...logListState, sortOrder });
|
|
|
|
|
onLogOptionsChange?.('sortOrder', sortOrder);
|
|
|
|
|
if (logOptionsStorageKey) {
|
|
|
|
|
store.set(`${logOptionsStorageKey}.sortOrder`, sortOrder);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[logListState, logOptionsStorageKey, onLogOptionsChange]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const setWrapLogMessage = useCallback(
|
|
|
|
|
(wrapLogMessage: boolean) => {
|
|
|
|
|
setLogListState({ ...logListState, wrapLogMessage });
|
|
|
|
|
onLogOptionsChange?.('wrapLogMessage', wrapLogMessage);
|
|
|
|
|
if (logOptionsStorageKey) {
|
|
|
|
|
store.set(`${logOptionsStorageKey}.wrapLogMessage`, wrapLogMessage);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[logListState, logOptionsStorageKey, onLogOptionsChange]
|
|
|
|
|
);
|
|
|
|
|
|
2025-04-07 22:38:55 +08:00
|
|
|
const downloadLogs = useCallback(
|
|
|
|
|
(format: DownloadFormat) => {
|
|
|
|
|
const filteredLogs =
|
|
|
|
|
logListState.filterLevels.length === 0
|
|
|
|
|
? logs
|
|
|
|
|
: logs.filter((log) => logListState.filterLevels.includes(log.logLevel));
|
|
|
|
|
download(format, filteredLogs, logsMeta);
|
|
|
|
|
},
|
|
|
|
|
[logListState.filterLevels, logs, logsMeta]
|
|
|
|
|
);
|
|
|
|
|
|
2025-05-21 01:28:35 +08:00
|
|
|
const closeDetails = useCallback(() => {
|
|
|
|
|
setShowDetails([]);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const toggleDetails = useCallback(
|
|
|
|
|
(log: LogListModel) => {
|
|
|
|
|
if (!enableLogDetails) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const found = showDetails.findIndex((stateLog) => stateLog === log || stateLog.uid === log.uid);
|
|
|
|
|
if (found >= 0) {
|
|
|
|
|
setShowDetails(showDetails.filter((stateLog) => stateLog !== log && stateLog.uid !== log.uid));
|
|
|
|
|
} else {
|
|
|
|
|
// Supporting one displayed details for now
|
|
|
|
|
setShowDetails([log]);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[enableLogDetails, showDetails]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const setDetailsWidth = useCallback(
|
|
|
|
|
(width: number) => {
|
|
|
|
|
if (!logOptionsStorageKey) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
store.set(`${logOptionsStorageKey}.detailsWidth`, width);
|
|
|
|
|
},
|
|
|
|
|
[logOptionsStorageKey]
|
|
|
|
|
);
|
|
|
|
|
|
2025-06-01 20:29:49 +08:00
|
|
|
const hasLogsWithErrors = useMemo(() => logs.some((log) => !!checkLogsError(log)), [logs]);
|
|
|
|
|
const hasSampledLogs = useMemo(() => logs.some((log) => !!checkLogsSampled(log)), [logs]);
|
|
|
|
|
|
2025-05-21 01:28:35 +08:00
|
|
|
const defaultWidth = (containerElement?.clientWidth ?? 0) * 0.4;
|
|
|
|
|
const detailsWidth = logOptionsStorageKey
|
|
|
|
|
? parseInt(store.get(`${logOptionsStorageKey}.detailsWidth`), 10)
|
|
|
|
|
: defaultWidth;
|
|
|
|
|
|
2025-04-04 20:53:12 +08:00
|
|
|
return (
|
|
|
|
|
<LogListContext.Provider
|
|
|
|
|
value={{
|
|
|
|
|
app,
|
2025-05-21 01:28:35 +08:00
|
|
|
closeDetails,
|
|
|
|
|
detailsDisplayed,
|
2025-04-04 20:53:12 +08:00
|
|
|
dedupStrategy: logListState.dedupStrategy,
|
2025-05-21 01:28:35 +08:00
|
|
|
detailsWidth: detailsWidth || defaultWidth,
|
|
|
|
|
displayedFields,
|
2025-04-07 22:38:55 +08:00
|
|
|
downloadLogs,
|
2025-05-21 01:28:35 +08:00
|
|
|
enableLogDetails,
|
2025-04-04 20:53:12 +08:00
|
|
|
filterLevels: logListState.filterLevels,
|
2025-04-11 02:47:17 +08:00
|
|
|
forceEscape: logListState.forceEscape,
|
2025-06-01 20:29:49 +08:00
|
|
|
hasLogsWithErrors,
|
|
|
|
|
hasSampledLogs,
|
2025-04-11 02:47:17 +08:00
|
|
|
hasUnescapedContent: logListState.hasUnescapedContent,
|
2025-05-21 01:28:35 +08:00
|
|
|
isLabelFilterActive,
|
2025-04-04 20:53:12 +08:00
|
|
|
getRowContextQuery,
|
|
|
|
|
logSupportsContext,
|
2025-06-01 20:29:49 +08:00
|
|
|
logLineMenuCustomItems,
|
2025-05-21 01:28:35 +08:00
|
|
|
onClickFilterLabel,
|
|
|
|
|
onClickFilterOutLabel,
|
|
|
|
|
onClickFilterString,
|
|
|
|
|
onClickFilterOutString,
|
|
|
|
|
onClickShowField,
|
|
|
|
|
onClickHideField,
|
2025-04-04 20:53:12 +08:00
|
|
|
onLogLineHover,
|
|
|
|
|
onPermalinkClick,
|
|
|
|
|
onPinLine,
|
|
|
|
|
onOpenContext,
|
|
|
|
|
onUnpinLine,
|
2025-05-23 02:12:06 +08:00
|
|
|
permalinkedLogId,
|
2025-04-04 20:53:12 +08:00
|
|
|
pinLineButtonTooltipTitle,
|
|
|
|
|
pinnedLogs: logListState.pinnedLogs,
|
2025-04-07 22:38:55 +08:00
|
|
|
prettifyJSON: logListState.prettifyJSON,
|
2025-04-04 20:53:12 +08:00
|
|
|
setDedupStrategy,
|
2025-05-21 01:28:35 +08:00
|
|
|
setDetailsWidth,
|
2025-04-04 20:53:12 +08:00
|
|
|
setFilterLevels,
|
2025-04-11 02:47:17 +08:00
|
|
|
setForceEscape,
|
2025-04-04 20:53:12 +08:00
|
|
|
setLogListState,
|
|
|
|
|
setPinnedLogs,
|
2025-04-07 22:38:55 +08:00
|
|
|
setPrettifyJSON,
|
2025-04-04 20:53:12 +08:00
|
|
|
setShowTime,
|
2025-04-07 22:38:55 +08:00
|
|
|
setShowUniqueLabels,
|
2025-04-04 20:53:12 +08:00
|
|
|
setSortOrder,
|
|
|
|
|
setSyntaxHighlighting,
|
|
|
|
|
setWrapLogMessage,
|
2025-05-21 01:28:35 +08:00
|
|
|
showDetails,
|
2025-04-04 20:53:12 +08:00
|
|
|
showTime: logListState.showTime,
|
2025-04-07 22:38:55 +08:00
|
|
|
showUniqueLabels: logListState.showUniqueLabels,
|
2025-04-04 20:53:12 +08:00
|
|
|
sortOrder: logListState.sortOrder,
|
|
|
|
|
syntaxHighlighting: logListState.syntaxHighlighting,
|
2025-05-21 01:28:35 +08:00
|
|
|
toggleDetails,
|
2025-04-04 20:53:12 +08:00
|
|
|
wrapLogMessage: logListState.wrapLogMessage,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{children}
|
|
|
|
|
</LogListContext.Provider>
|
|
|
|
|
);
|
|
|
|
|
};
|
2025-04-07 22:38:55 +08:00
|
|
|
|
|
|
|
|
export function isLogsSortOrder(value: unknown): value is LogsSortOrder {
|
|
|
|
|
return value === LogsSortOrder.Ascending || value === LogsSortOrder.Descending;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function isDedupStrategy(value: unknown): value is LogsDedupStrategy {
|
|
|
|
|
return (
|
|
|
|
|
value === LogsDedupStrategy.exact ||
|
|
|
|
|
value === LogsDedupStrategy.none ||
|
|
|
|
|
value === LogsDedupStrategy.numbers ||
|
|
|
|
|
value === LogsDedupStrategy.signature
|
|
|
|
|
);
|
|
|
|
|
}
|