-
Notifications
You must be signed in to change notification settings - Fork 106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
system logs ui changes #571
Changes from 12 commits
f2c1382
d1d83cd
e531aa7
b28a45b
98fd700
8c7e282
591602d
df905e9
0dc350e
5eddb70
c468467
26dabfa
c2c65ec
55f404e
b26aa4e
9ec9da3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { useTheme } from '@mui/material'; | ||
import { useCallback } from 'react'; | ||
|
||
import { Line } from '@/components/Text'; | ||
import { ParsedLog } from '@/constants/log-formatter'; | ||
|
||
export const FormattedLog = ({ | ||
log, | ||
index | ||
}: { | ||
log: ParsedLog; | ||
index: number; | ||
}) => { | ||
const theme = useTheme(); | ||
const getLevelColor = useCallback( | ||
(level: string) => { | ||
const colors: { [key: string]: string } = { | ||
INFO: theme.colors.success.main, | ||
MAIN_INFO: theme.colors.success.main, | ||
CHILD_INFO: theme.colors.success.main, | ||
SENTINEL_INFO: theme.colors.success.main, | ||
WARN: theme.colors.warning.main, | ||
WARNING: theme.colors.warning.main, | ||
NOTICE: theme.colors.warning.dark, | ||
ERROR: theme.colors.error.main, | ||
FATAL: theme.colors.error.main, | ||
PANIC: theme.colors.error.main, | ||
DEBUG: theme.colors.info.light, | ||
MAIN_SYSTEM: theme.colors.primary.main, | ||
CHILD_SYSTEM: theme.colors.primary.main, | ||
SENTINEL_SYSTEM: theme.colors.primary.main, | ||
LOG: theme.colors.info.main, | ||
CRITICAL: theme.colors.error.main | ||
}; | ||
|
||
return colors[level.toUpperCase()] || theme.colors.info.main; | ||
}, | ||
[theme] | ||
); | ||
|
||
const { timestamp, ip, logLevel, message } = log; | ||
return ( | ||
<Line | ||
key={index} | ||
marginBottom={'8px'} | ||
fontSize={'14px'} | ||
fontFamily={'monospace'} | ||
> | ||
<Line component="span" color="info.main"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think you can do this like |
||
{timestamp} | ||
</Line>{' '} | ||
{ip && ( | ||
<Line component="span" color="primary.main"> | ||
{ip}{' '} | ||
</Line> | ||
)} | ||
<Line component="span" color={getLevelColor(logLevel)}> | ||
[{logLevel}] | ||
</Line>{' '} | ||
{message} | ||
</Line> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { Line } from '@/components/Text'; | ||
|
||
export const PlainLog = ({ log, index }: { log: string; index: number }) => { | ||
return ( | ||
<Line | ||
key={index} | ||
marginBottom={'8px'} | ||
fontSize={'14px'} | ||
fontFamily={'monospace'} | ||
> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for fontsize use props on our line component |
||
{log} | ||
</Line> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { CopyAll } from '@mui/icons-material'; | ||
import { Button, Typography } from '@mui/material'; | ||
import { useSnackbar } from 'notistack'; | ||
import CopyToClipboard from 'react-copy-to-clipboard'; | ||
|
||
import { FlexBox } from '@/components/FlexBox'; | ||
import { Line } from '@/components/Text'; | ||
|
||
export const SystemLogErrorMessage = ({ errorBody }: { errorBody: any }) => { | ||
const { enqueueSnackbar } = useSnackbar(); | ||
return ( | ||
<FlexBox alignCenter gap={1}> | ||
<Line tiny color="warning.main" fontWeight="bold"> | ||
<Typography variant="h6"> | ||
Something went wrong displaying the logs. | ||
</Typography> | ||
An error occurred while processing the logs. Please report this issue. | ||
</Line> | ||
<CopyToClipboard | ||
text={JSON.stringify(errorBody, null, ' ')} | ||
onCopy={() => { | ||
enqueueSnackbar(`Error logs copied to clipboard`, { | ||
variant: 'success' | ||
}); | ||
}} | ||
> | ||
<Button | ||
size="small" | ||
variant="contained" | ||
startIcon={<CopyAll />} | ||
sx={{ fontWeight: 'bold' }} | ||
> | ||
Copy Logs | ||
</Button> | ||
</CopyToClipboard> | ||
<Button | ||
variant="contained" | ||
size="small" | ||
href="https://github.com/middlewarehq/middleware/issues/new/choose" | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
Report Issue | ||
</Button> | ||
</FlexBox> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { CircularProgress } from '@mui/material'; | ||
import { useEffect, useMemo, useRef } from 'react'; | ||
|
||
import { FlexBox } from '@/components/FlexBox'; | ||
import { PlainLog } from '@/components/Service/SystemLog/PlainLog'; | ||
import { SystemLogErrorMessage } from '@/components/Service/SystemLog/SystemLogErrorMessage'; | ||
import { Line } from '@/components/Text'; | ||
import { track } from '@/constants/events'; | ||
import { ServiceNames } from '@/constants/service'; | ||
import { useSystemLogs } from '@/hooks/useSystemLogs'; | ||
|
||
export const SystemLogsErrorFallback = ({ | ||
error, | ||
serviceName | ||
}: { | ||
error: Error; | ||
serviceName: ServiceNames; | ||
}) => { | ||
const { services, loading, logs } = useSystemLogs({ serviceName }); | ||
|
||
const containerRef = useRef<HTMLDivElement>(null); | ||
const errorBody = useMemo( | ||
() => ({ | ||
message: error?.message?.replace('\\n', '\n') || '', | ||
stack: error?.stack?.replace('\\n', '\n') || '' | ||
}), | ||
[error] | ||
); | ||
|
||
useEffect(() => { | ||
if (error) { | ||
track('ERR_FALLBACK_SHOWN', { err: errorBody }); | ||
console.error(error); | ||
} | ||
}, [errorBody, error]); | ||
|
||
useEffect(() => { | ||
if (containerRef.current) { | ||
containerRef.current.scrollIntoView({ behavior: 'smooth' }); | ||
} | ||
}, [logs]); | ||
|
||
return ( | ||
<FlexBox col> | ||
{loading ? ( | ||
<FlexBox alignCenter gap2> | ||
<CircularProgress size="20px" /> | ||
<Line>Loading...</Line> | ||
</FlexBox> | ||
) : ( | ||
services && | ||
logs.map((log, index) => { | ||
return <PlainLog log={log} index={index} key={index} />; | ||
}) | ||
)} | ||
<FlexBox ref={containerRef} /> | ||
<SystemLogErrorMessage errorBody={errorBody} /> | ||
</FlexBox> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
export interface ParsedLog { | ||
timestamp: string; | ||
logLevel: string; | ||
message: string; | ||
role?: string; | ||
ip?: string; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. move this interface declaration to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
|
||
export const generalLogRegex = | ||
/^\[(.*?)\] \[(\d+)\] \[(INFO|ERROR|WARN|DEBUG|WARNING|CRITICAL)\] (.+)$/; | ||
export const httpLogRegex = | ||
/^(\S+) (\S+) (\S+) \[([^\]]+)\] "([^"]*)" (\d+) (\d+) "([^"]*)" "([^"]*)"$/; | ||
export const redisLogRegex = | ||
/^(?<role>\d+:[XCMS]) (?<timestamp>\d{2} \w{3} \d{4} \d{2}:\d{2}:\d{2}\.\d{3}) (?<loglevel>[\.\-\#\*]) (?<message>.*)$/; | ||
export const postgresLogRegex = | ||
/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3} \w+) \[(\d+)\] (\w+):(.+)(?:\n(?:\s+.*)?)*$/m; | ||
export const postgresMultiLineLogRegex = | ||
/^(?<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3} UTC) \[\d+\] (?<loglevel>[A-Z]+):\s*(?<message>(.|\n)+?)$/; | ||
export const dataSyncLogRegex = /\[(\w+)\]\s(.+?)\sfor\s(\w+)\s(.+)/; | ||
export const validLogLevels = new Set([ | ||
'DEBUG', | ||
'INFO', | ||
'NOTICE', | ||
'WARNING', | ||
'ERROR', | ||
'LOG', | ||
'FATAL', | ||
'PANIC', | ||
'STATEMENT', | ||
'DETAIL' | ||
]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should be an enum in our There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added these as enums and also removed the validLogLevels logic from the parser. Previously, I was using two approaches to parse PostgreSQL logs, but during testing, I noticed that my first approach, PostgresMultilineLogMatch, can parse both types of PostgreSQL logs. Therefore, there's no need to write separate logic for each type ,refer to the official postgres documentation . and this article also for log levels . |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { useMemo } from 'react'; | ||
|
||
import { ServiceNames } from '@/constants/service'; | ||
import { useSelector } from '@/store'; | ||
export const useSystemLogs = ({ | ||
serviceName | ||
}: { | ||
serviceName: ServiceNames; | ||
}) => { | ||
const services = useSelector((state) => state.service.services); | ||
const loading = useSelector((state) => state.service.loading); | ||
|
||
const logs = useMemo(() => { | ||
return services[serviceName]?.logs || []; | ||
}, [serviceName, services]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
return { | ||
services, | ||
loading, | ||
logs | ||
}; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as other requested changes