11import { Line } from '@ant-design/charts' ;
22import { ReloadOutlined } from '@ant-design/icons' ;
3- import { useQueries } from '@tanstack/react-query' ;
3+ import { useQueries , useQuery } from '@tanstack/react-query' ;
44import {
55 Button ,
66 Card ,
@@ -17,14 +17,16 @@ import dayjs from 'dayjs';
1717import { useMemo , useState } from 'react' ;
1818import {
1919 api ,
20+ type InternalErrorLogEntry ,
2021 type InternalMetricCounter ,
2122 type InternalMetricDuration ,
2223 type InternalMetricsResponse ,
2324} from '@/services/api' ;
2425import { cn } from '@/utils/helper' ;
2526
26- const { Text, Title } = Typography ;
27+ const { Paragraph , Text, Title } = Typography ;
2728
29+ const ERROR_LOG_PAGE_SIZE = 20 ;
2830const 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+
534564function 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 ) ;
0 commit comments