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 all 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
52 changes: 52 additions & 0 deletions web-server/src/components/Service/SystemLog/FormattedLog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { useTheme } from '@mui/material';
import { useCallback } from 'react';

import { Line } from '@/components/Text';
import { ParsedLog } from '@/types/resources';

export const FormattedLog = ({ log }: { 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 mono marginBottom={1}>
<Line component="span" color="info">
{timestamp}
</Line>{' '}
{ip && (
<Line component="span" color="primary">
{ip}{' '}
</Line>
)}
<Line component="span" color={getLevelColor(logLevel)}>
[{logLevel}]
</Line>{' '}
{message}
</Line>
);
};
9 changes: 9 additions & 0 deletions web-server/src/components/Service/SystemLog/PlainLog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Line } from '@/components/Text';

export const PlainLog = ({ log }: { log: string; index: number }) => {
return (
<Line mono marginBottom={1}>
{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>
);
};
9 changes: 9 additions & 0 deletions web-server/src/constants/log-formatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
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 =
/^(?<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(.+)/;
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(
() => services[serviceName]?.logs || [],
[serviceName, services]
);

return {
services,
loading,
logs
};
};
21 changes: 21 additions & 0 deletions web-server/src/types/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1039,3 +1039,24 @@ export type DB_OrgRepo = {
deployment_type: 'PR_MERGE' | 'WORKFLOW';
repo_workflows: RepoWorkflow[];
};

export enum LogLevel {
'DEBUG' = 'DEBUG',
'INFO' = 'INFO',
'NOTICE' = 'NOTICE',
'WARNING' = 'WARNING',
'ERROR' = 'ERROR',
'LOG' = 'LOG',
'FATAL' = 'FATAL',
'PANIC' = 'PANIC',
'STATEMENT' = 'STATEMENT',
'DETAIL' = 'DETAIL'
}

export interface ParsedLog {
timestamp: string;
logLevel: string;
message: string;
role?: string;
ip?: string;
}
Loading
Loading