diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 4bad12b1..cd5c1df3 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -752,6 +752,8 @@ "loaded_lines": "Loaded: {{count}} lines", "filtered_lines": "Filtered: {{count}} lines", "hide_management_logs": "Hide {{prefix}} logs", + "show_raw_logs": "Show Raw Logs", + "show_raw_logs_hint": "Show original log text for easier multi-line copy", "search_placeholder": "Search logs by content or keyword", "search_empty_title": "No matching logs found", "search_empty_desc": "Try a different keyword or clear the filters.", diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index 469aaa27..0741e931 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -752,6 +752,8 @@ "loaded_lines": "已载入 {{count}} 行", "filtered_lines": "已过滤 {{count}} 行", "hide_management_logs": "屏蔽 {{prefix}} 日志", + "show_raw_logs": "显示原始日志", + "show_raw_logs_hint": "直接显示原始日志文本,方便多行复制", "search_placeholder": "搜索日志内容或关键字", "search_empty_title": "未找到匹配的日志", "search_empty_desc": "尝试更换关键字或清空筛选条件。", diff --git a/src/pages/LogsPage.module.scss b/src/pages/LogsPage.module.scss index 4f7a9dcf..c5f66cb9 100644 --- a/src/pages/LogsPage.module.scss +++ b/src/pages/LogsPage.module.scss @@ -267,6 +267,30 @@ flex-direction: column; } +.rawLog { + margin: 0; + padding: 10px 12px; + cursor: text; + user-select: text; + white-space: pre; + color: var(--text-primary); + font-family: + ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', + monospace; + font-size: 12.5px; + line-height: 1.45; + + @include tablet { + padding: 8px 10px; + font-size: 12px; + } + + @include mobile { + padding: 8px 10px; + font-size: 11.5px; + } +} + .logRow { display: grid; grid-template-columns: 170px 1fr; diff --git a/src/pages/LogsPage.tsx b/src/pages/LogsPage.tsx index 2953ee92..e98792e4 100644 --- a/src/pages/LogsPage.tsx +++ b/src/pages/LogsPage.tsx @@ -9,6 +9,7 @@ import { Modal } from '@/components/ui/Modal'; import { ToggleSwitch } from '@/components/ui/ToggleSwitch'; import { IconDownload, + IconCode, IconEyeOff, IconRefreshCw, IconSearch, @@ -383,6 +384,7 @@ export function LogsPage() { const [searchQuery, setSearchQuery] = useState(''); const deferredSearchQuery = useDeferredValue(searchQuery); const [hideManagementLogs, setHideManagementLogs] = useState(true); + const [showRawLogs, setShowRawLogs] = useState(false); const [errorLogs, setErrorLogs] = useState([]); const [loadingErrors, setLoadingErrors] = useState(false); const [errorLogsError, setErrorLogsError] = useState(''); @@ -632,10 +634,12 @@ export function LogsPage() { return { filteredLines: working, removedCount: removed }; }, [baseLines, hideManagementLogs, trimmedSearchQuery]); - const parsedVisibleLines = useMemo( - () => filteredLines.map((line) => parseLogLine(line)), - [filteredLines] - ); + const parsedVisibleLines = useMemo(() => { + if (showRawLogs) return []; + return filteredLines.map((line) => parseLogLine(line)); + }, [filteredLines, showRawLogs]); + + const rawVisibleText = useMemo(() => filteredLines.join('\n'), [filteredLines]); const canLoadMore = !isSearching && logState.visibleFrom > 0; @@ -817,6 +821,22 @@ export function LogsPage() { } /> + + + {t('logs.show_raw_logs', { defaultValue: 'Show raw logs' })} + + } + /> +
- ) : logState.buffer.length > 0 && parsedVisibleLines.length > 0 ? ( + ) : logState.buffer.length > 0 && filteredLines.length > 0 ? (
{canLoadMore && (
{t('logs.load_more_hint')}
- {t('logs.loaded_lines', { count: parsedVisibleLines.length })} + {t('logs.loaded_lines', { count: filteredLines.length })} {removedCount > 0 && ( @@ -890,103 +910,109 @@ export function LogsPage() {
)} -
- {parsedVisibleLines.map((line, index) => { - const rowClassNames = [styles.logRow]; - if (line.level === 'warn') rowClassNames.push(styles.rowWarn); - if (line.level === 'error' || line.level === 'fatal') - rowClassNames.push(styles.rowError); - return ( -
{ - void copyLogLine(line.raw); - }} - onPointerDown={(event) => startLongPress(event, line.requestId)} - onPointerUp={cancelLongPress} - onPointerLeave={cancelLongPress} - onPointerCancel={cancelLongPress} - onPointerMove={handleLongPressMove} - title={t('logs.double_click_copy_hint', { - defaultValue: 'Double-click to copy', - })} - > -
{line.timestamp || ''}
-
- {line.level && ( - - {line.level.toUpperCase()} - - )} + {showRawLogs ? ( +
+                    {rawVisibleText}
+                  
+ ) : ( +
+ {parsedVisibleLines.map((line, index) => { + const rowClassNames = [styles.logRow]; + if (line.level === 'warn') rowClassNames.push(styles.rowWarn); + if (line.level === 'error' || line.level === 'fatal') + rowClassNames.push(styles.rowError); + return ( +
{ + void copyLogLine(line.raw); + }} + onPointerDown={(event) => startLongPress(event, line.requestId)} + onPointerUp={cancelLongPress} + onPointerLeave={cancelLongPress} + onPointerCancel={cancelLongPress} + onPointerMove={handleLongPressMove} + title={t('logs.double_click_copy_hint', { + defaultValue: 'Double-click to copy', + })} + > +
{line.timestamp || ''}
+
+ {line.level && ( + + {line.level.toUpperCase()} + + )} - {line.source && ( - - {line.source} - - )} + {line.source && ( + + {line.source} + + )} - {line.requestId && ( - - {line.requestId} - - )} + {line.requestId && ( + + {line.requestId} + + )} - {typeof line.statusCode === 'number' && ( - = 200 && line.statusCode < 300 - ? styles.statusSuccess - : line.statusCode >= 300 && line.statusCode < 400 - ? styles.statusInfo - : line.statusCode >= 400 && line.statusCode < 500 - ? styles.statusWarn - : styles.statusError, - ].join(' ')} - > - {line.statusCode} - - )} + {typeof line.statusCode === 'number' && ( + = 200 && line.statusCode < 300 + ? styles.statusSuccess + : line.statusCode >= 300 && line.statusCode < 400 + ? styles.statusInfo + : line.statusCode >= 400 && line.statusCode < 500 + ? styles.statusWarn + : styles.statusError, + ].join(' ')} + > + {line.statusCode} + + )} - {line.latency && {line.latency}} - {line.ip && {line.ip}} + {line.latency && {line.latency}} + {line.ip && {line.ip}} - {line.method && ( - - {line.method} - - )} + {line.method && ( + + {line.method} + + )} - {line.path && ( - - {line.path} - - )} + {line.path && ( + + {line.path} + + )} - {line.message && {line.message}} + {line.message && {line.message}} +
-
- ); - })} -
+ ); + })} +
+ )}
) : logState.buffer.length > 0 ? (