Skip to content

Commit 384c33f

Browse files
committed
Show service error logs in admin status
1 parent e95b591 commit 384c33f

2 files changed

Lines changed: 143 additions & 5 deletions

File tree

src/pages/admin-service-status.tsx

Lines changed: 98 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Line } from '@ant-design/charts';
22
import { ReloadOutlined } from '@ant-design/icons';
3-
import { useQueries } from '@tanstack/react-query';
3+
import { useQueries, useQuery } from '@tanstack/react-query';
44
import {
55
Button,
66
Card,
@@ -17,14 +17,16 @@ import dayjs from 'dayjs';
1717
import { useMemo, useState } from 'react';
1818
import {
1919
api,
20+
type InternalErrorLogEntry,
2021
type InternalMetricCounter,
2122
type InternalMetricDuration,
2223
type InternalMetricsResponse,
2324
} from '@/services/api';
2425
import { cn } from '@/utils/helper';
2526

26-
const { Text, Title } = Typography;
27+
const { Paragraph, Text, Title } = Typography;
2728

29+
const ERROR_LOG_PAGE_SIZE = 20;
2830
const DEFAULT_DURATION_BUCKETS_MS = [
2931
10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10_000,
3032
];
@@ -531,6 +533,34 @@ const endpointColumns: ColumnsType<EndpointRow> = [
531533
},
532534
];
533535

536+
const errorLogColumns: ColumnsType<InternalErrorLogEntry> = [
537+
{
538+
dataIndex: 'time',
539+
render: (time: string | undefined) =>
540+
time ? dayjs(time).format('YYYY-MM-DD HH:mm:ss') : '-',
541+
title: '时间',
542+
width: 180,
543+
},
544+
{
545+
align: 'right',
546+
dataIndex: 'index',
547+
title: '行',
548+
width: 80,
549+
},
550+
{
551+
dataIndex: 'line',
552+
render: (line: string, entry) => (
553+
<Paragraph className="m-0! whitespace-pre-wrap break-all font-mono text-xs">
554+
{line}
555+
{entry.lineTruncated ? (
556+
<Text type="secondary"> line truncated</Text>
557+
) : null}
558+
</Paragraph>
559+
),
560+
title: '内容',
561+
},
562+
];
563+
534564
function ServiceStatusPanel({
535565
error,
536566
isFetching,
@@ -544,6 +574,19 @@ function ServiceStatusPanel({
544574
snapshot?: InternalMetricsResponse;
545575
target: ServiceStatusTarget;
546576
}) {
577+
const [errorLogPage, setErrorLogPage] = useState(1);
578+
const errorLogOffset = (errorLogPage - 1) * ERROR_LOG_PAGE_SIZE;
579+
const errorLogsQuery = useQuery({
580+
queryFn: () =>
581+
api.getInternalErrorLogs({
582+
baseUrl: target.baseUrl,
583+
limit: ERROR_LOG_PAGE_SIZE,
584+
offset: errorLogOffset,
585+
suppressErrorToast: true,
586+
}),
587+
queryKey: ['internalErrorLogs', target.key, errorLogOffset],
588+
refetchInterval: 30_000,
589+
});
547590
const apiDuration = useMemo(
548591
() =>
549592
aggregateDurations(
@@ -606,8 +649,11 @@ function ServiceStatusPanel({
606649
</div>
607650
<Button
608651
icon={<ReloadOutlined />}
609-
loading={isFetching}
610-
onClick={() => refetch()}
652+
loading={isFetching || errorLogsQuery.isFetching}
653+
onClick={() => {
654+
refetch();
655+
errorLogsQuery.refetch();
656+
}}
611657
>
612658
刷新
613659
</Button>
@@ -711,7 +757,7 @@ function ServiceStatusPanel({
711757
/>
712758
</div>
713759

714-
<Card title="Top API 路径">
760+
<Card className="mb-4" title="Top API 路径">
715761
<Table
716762
columns={endpointColumns}
717763
dataSource={endpointRows.slice(0, 12)}
@@ -721,6 +767,53 @@ function ServiceStatusPanel({
721767
size="small"
722768
/>
723769
</Card>
770+
771+
<Card
772+
extra={
773+
errorLogsQuery.data ? (
774+
<Text type="secondary">
775+
{errorLogsQuery.data.logFile}
776+
{errorLogsQuery.data.truncated
777+
? ` · 最近 ${formatBytes(errorLogsQuery.data.windowBytes)}`
778+
: ''}
779+
</Text>
780+
) : null
781+
}
782+
title="错误日志"
783+
>
784+
{errorLogsQuery.error && (
785+
<div className="mb-3">
786+
<Text type="danger">
787+
{(errorLogsQuery.error as Error).message || '请求失败'}
788+
</Text>
789+
</div>
790+
)}
791+
{errorLogsQuery.data?.message && (
792+
<div className="mb-3">
793+
<Text type="secondary">{errorLogsQuery.data.message}</Text>
794+
</div>
795+
)}
796+
<Table
797+
columns={errorLogColumns}
798+
dataSource={errorLogsQuery.data?.data ?? []}
799+
loading={errorLogsQuery.isFetching}
800+
locale={{
801+
emptyText: errorLogsQuery.error ? '请求失败' : '暂无错误日志',
802+
}}
803+
pagination={{
804+
current: errorLogPage,
805+
hideOnSinglePage: false,
806+
onChange: setErrorLogPage,
807+
pageSize: ERROR_LOG_PAGE_SIZE,
808+
showSizeChanger: false,
809+
showTotal: (total) => `共 ${formatCount(total)} 行`,
810+
total: errorLogsQuery.data?.total ?? 0,
811+
}}
812+
rowKey="index"
813+
scroll={{ x: 900 }}
814+
size="small"
815+
/>
816+
</Card>
724817
</Spin>
725818
</>
726819
);

src/services/api.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,28 @@ export type InternalMetricsResponse = {
146146
};
147147
};
148148

149+
export type InternalErrorLogEntry = {
150+
index: number;
151+
line: string;
152+
lineTruncated?: boolean;
153+
time?: string;
154+
};
155+
156+
export type InternalErrorLogsResponse = {
157+
data: InternalErrorLogEntry[];
158+
fileExists: boolean;
159+
fileSizeBytes: number;
160+
generatedAt: string;
161+
hasMore: boolean;
162+
limit: number;
163+
logFile: string;
164+
message?: string;
165+
offset: number;
166+
total: number;
167+
truncated: boolean;
168+
windowBytes: number;
169+
};
170+
149171
export const api = {
150172
login: (params: { email: string; pwd: string }) =>
151173
request<{ token: string }>('post', '/user/login', params, {
@@ -449,6 +471,29 @@ export const api = {
449471
baseUrl: params?.baseUrl,
450472
suppressErrorToast: params?.suppressErrorToast,
451473
}),
474+
getInternalErrorLogs: (params?: {
475+
baseUrl?: string;
476+
limit?: number;
477+
offset?: number;
478+
suppressErrorToast?: boolean;
479+
}) => {
480+
const query: Record<string, number> = {};
481+
if (params?.offset !== undefined) {
482+
query.offset = params.offset;
483+
}
484+
if (params?.limit !== undefined) {
485+
query.limit = params.limit;
486+
}
487+
return request<InternalErrorLogsResponse>(
488+
'get',
489+
'/metrics/internal/error-logs',
490+
Object.keys(query).length > 0 ? query : undefined,
491+
{
492+
baseUrl: params?.baseUrl,
493+
suppressErrorToast: params?.suppressErrorToast,
494+
},
495+
);
496+
},
452497
// API Token
453498
createApiToken: (params: {
454499
name: string;

0 commit comments

Comments
 (0)