Skip to content
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

Merged
merged 16 commits into from
Oct 19, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions web-server/src/components/Service/SystemLog/FormattedLog.tsx
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'}
Copy link
Contributor

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

>
<Line component="span" color="info.main">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think you can do this like <Line color="info">

{timestamp}
</Line>{' '}
{ip && (
<Line component="span" color="primary.main">
{ip}{' '}
</Line>
)}
<Line component="span" color={getLevelColor(logLevel)}>
[{logLevel}]
</Line>{' '}
{message}
</Line>
);
};
14 changes: 14 additions & 0 deletions web-server/src/components/Service/SystemLog/PlainLog.tsx
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'}
>
Copy link
Contributor

@shivam-bit shivam-bit Oct 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for fontsize use props on our line component tiny | small | medium |bigish | big | huge.
for fontFamily directly pass mono as prop

{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>
);
};
31 changes: 31 additions & 0 deletions web-server/src/constants/log-formatter.ts
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;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move this interface declaration to resources.ts. we use that for all our type declaration

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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'
]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be an enum in our resources file

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 .

60 changes: 32 additions & 28 deletions web-server/src/content/Service/SystemLogs.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import CircularProgress from '@mui/material/CircularProgress';
import { useEffect, useRef, useMemo } from 'react';
import { CircularProgress } from '@mui/material';
import { useEffect, useRef } from 'react';
import { ErrorBoundary } from 'react-error-boundary';

import { FlexBox } from '@/components/FlexBox';
import { FormattedLog } from '@/components/Service/SystemLog/FormattedLog';
import { PlainLog } from '@/components/Service/SystemLog/PlainLog';
import { SystemLogsErrorFallback } from '@/components/Service/SystemLog/SystemLogsErrorFllback';
import { Line } from '@/components/Text';
import { ServiceNames } from '@/constants/service';
import { useSelector } from '@/store';
import { useSystemLogs } from '@/hooks/useSystemLogs';
import { parseLogLine } from '@/utils/logFormatter';

export const SystemLogs = ({ 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]);
const { services, loading, logs } = useSystemLogs({ serviceName });

const containerRef = useRef<HTMLDivElement>(null);

Expand All @@ -22,26 +23,29 @@ export const SystemLogs = ({ serviceName }: { serviceName: ServiceNames }) => {
}, [logs]);

return (
<FlexBox col>
{loading ? (
<FlexBox alignCenter gap2>
<CircularProgress size="20px" />
<Line>Loading...</Line>
</FlexBox>
) : (
services &&
logs.map((log, index) => (
<Line
key={index}
marginBottom={'8px'}
fontSize={'14px'}
fontFamily={'monospace'}
>
{log}
</Line>
))
<ErrorBoundary
FallbackComponent={({ error }) => (
<SystemLogsErrorFallback error={error} serviceName={serviceName} />
)}
<FlexBox ref={containerRef} />
</FlexBox>
>
<FlexBox col>
{loading ? (
<FlexBox alignCenter gap2>
<CircularProgress size="20px" />
<Line>Loading...</Line>
</FlexBox>
) : (
services &&
logs.map((log, index) => {
const parsedLog = parseLogLine(log);
if (!parsedLog) {
return <PlainLog log={log} index={index} key={index} />;
}
return <FormattedLog log={parsedLog} index={index} key={index} />;
})
)}
<FlexBox ref={containerRef} />
</FlexBox>
</ErrorBoundary>
);
};
22 changes: 22 additions & 0 deletions web-server/src/hooks/useSystemLogs.tsx
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]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const logs = useMemo(() => services[serviceName]?.logs || [], [serviceName, services]);


return {
services,
loading,
logs
};
};
Loading
Loading