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

feat: applaunchpad log previous #5047

Merged
merged 5 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
9 changes: 7 additions & 2 deletions frontend/providers/applaunchpad/public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -259,5 +259,10 @@
"app_store": "App Store",
"sealaf": "sealaf",
"total_price_tip": "The estimated cost does not include port fees and traffic fees, and is subject to actual usage.",
"nodeports": "NodePorts"
}
"nodeports": "NodePorts",
"streaming_logs": "Streaming logs",
"within_5_minutes": "Within 5 minutes",
"within_1_hour": "Within 1 hour",
"within_1_day": "Within 1 day",
"terminated_logs": "Terminated logs"
}
Original file line number Diff line number Diff line change
Expand Up @@ -259,5 +259,10 @@
"sealaf": "云开发",
"app_store": "应用商店",
"total_price_tip": "预估费用不包括端口费用和流量费用,以实际使用为准",
"nodeports": "外网端口"
"nodeports": "外网端口",
"streaming_logs": "实时日志",
"within_5_minutes": "五分钟内",
"within_1_hour": "一小时内",
"within_1_day": "一天内",
"terminated_logs": "中断前"
}
2 changes: 2 additions & 0 deletions frontend/providers/applaunchpad/src/api/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export const getPodLogs = (data: {
podName: string;
stream: boolean;
logSize?: number;
sinceTime?: number;
previous?: boolean;
}) => POST<string>(`/api/getPodLogs`, data);

export const getPodEvents = (podName: string) =>
Expand Down
31 changes: 27 additions & 4 deletions frontend/providers/applaunchpad/src/pages/api/getPodLogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
appName,
podName,
stream = false,
logSize
logSize,
previous,
sinceTime
} = req.body as {
appName: string;
podName: string;
stream: boolean;
logSize?: number;
previous?: boolean;
sinceTime?: number;
};

if (!podName) {
Expand All @@ -51,6 +55,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});

if (!stream) {
const sinceSeconds =
sinceTime && !!!previous ? Math.floor((Date.now() - sinceTime) / 1000) : undefined;
// get pods
const { body: data } = await k8sCore.readNamespacedPodLog(
podName,
Expand All @@ -60,8 +66,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
undefined,
undefined,
undefined,
undefined,
undefined,
previous,
sinceSeconds,
logSize
);
return jsonRes(res, {
Expand All @@ -77,6 +83,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
res.setHeader('Cache-Control', 'no-cache, no-transform');
logStream.pipe(res);

const reqData = {
follow: true,
pretty: false,
timestamps: false,
tailLines: 1000,
previous: !!previous
} as any;
if (!reqData.previous && sinceTime) {
reqData.sinceTime = timestampToRFC3339(sinceTime);
}

res.flushHeaders();

streamResponse = await logs.log(
namespace,
podName,
Expand All @@ -86,7 +105,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
console.log('err', err);
destroyStream();
},
{ follow: true, pretty: false, timestamps: false, tailLines: 1000 }
reqData
);
} catch (err: any) {
jsonRes(res, {
Expand All @@ -95,3 +114,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
}
}

function timestampToRFC3339(timestamp: number) {
return new Date(timestamp).toISOString();
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { getPodLogs } from '@/api/app';
import {
Modal,
Expand All @@ -22,6 +22,42 @@ import { streamFetch } from '@/services/streamFetch';
import { default as AnsiUp } from 'ansi_up';
import { useTranslation } from 'next-i18next';

interface sinceItem {
key: 'streaming_logs' | 'within_5_minutes' | 'within_1_hour' | 'within_1_day' | 'terminated_logs';
since: number;
previous: boolean;
}

const newSinceItems = (baseTimestamp: number): sinceItem[] => {
return [
{
key: 'streaming_logs',
since: 0,
previous: false
},
{
key: 'within_5_minutes',
since: baseTimestamp - 5 * 60 * 1000,
previous: false
},
{
key: 'within_1_hour',
since: baseTimestamp - 60 * 60 * 1000,
previous: false
},
{
key: 'within_1_day',
since: baseTimestamp - 24 * 60 * 60 * 1000,
previous: false
},
{
key: 'terminated_logs',
since: 0,
previous: true
}
];
};

const LogsModal = ({
appName,
podName,
Expand All @@ -44,6 +80,20 @@ const LogsModal = ({
const [isLoading, setIsLoading] = useState(true);
const LogBox = useRef<HTMLDivElement>(null);
const ansi_up = useRef(new AnsiUp());
const [sinceKey, setSinceKey] = useState('streaming_logs');
const [sinceTime, setSinceTime] = useState(0);
const [previous, setPrevious] = useState(false);

const switchSince = useCallback(
(item: sinceItem) => {
setSinceKey(item.key);
setPrevious(item.previous);
setSinceTime(item.since);
},
[setSinceKey, setPrevious, setSinceTime]
);

const sinceItems = useMemo(() => newSinceItems(Date.now()), []);

const watchLogs = useCallback(() => {
// podName is empty. pod may has been deleted
Expand All @@ -56,12 +106,14 @@ const LogsModal = ({
data: {
appName,
podName,
stream: true
stream: true,
sinceTime,
previous
},
abortSignal: controller,
firstResponse() {
setIsLoading(false);
setLogs('');
setIsLoading(false);
setTimeout(() => {
if (!LogBox.current) return;

Expand Down Expand Up @@ -91,7 +143,7 @@ const LogsModal = ({
}
});
return controller;
}, [appName, closeFn, podName]);
}, [appName, closeFn, podName, sinceTime, previous]);

useEffect(() => {
const controller = watchLogs();
Expand All @@ -101,13 +153,19 @@ const LogsModal = ({
}, [watchLogs]);

const exportLogs = useCallback(async () => {
const allLogs = await getPodLogs({
appName,
podName,
stream: false
});
downLoadBold(allLogs, 'text/plain', 'log.txt');
}, [appName, podName]);
try {
const allLogs = await getPodLogs({
appName,
podName,
stream: false,
sinceTime,
previous
});
downLoadBold(allLogs, 'text/plain', 'log.txt');
} catch (e) {
console.log('download log error:', e);
}
}, [appName, podName, sinceTime, previous]);

return (
<Modal isOpen={true} onClose={closeFn} isCentered={true} lockFocusAcrossFrames={false}>
Expand Down Expand Up @@ -143,6 +201,31 @@ const LogsModal = ({
}))}
/>
</Box>
<Box px={3} zIndex={10000}>
<SealosMenu
width={200}
Button={
<MenuButton
minW={'200px'}
h={'32px'}
textAlign={'start'}
bg={'grayModern.100'}
border={theme.borders.base}
borderRadius={'md'}
>
<Flex px={4} alignItems={'center'}>
<Box flex={1}>{t(sinceKey)}</Box>
<ChevronDownIcon ml={2} />
</Flex>
</MenuButton>
}
menuList={sinceItems.map((item) => ({
isActive: item.key === sinceKey,
child: <Box>{t(item.key)}</Box>,
onClick: () => switchSince(item)
}))}
/>
</Box>
<Button size={'sm'} onClick={exportLogs}>
{t('Export')}
</Button>
Expand Down
8 changes: 6 additions & 2 deletions frontend/providers/applaunchpad/src/services/streamFetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ interface StreamFetchProps {
url: string;
data: any;
onMessage: (text: string) => void;
firstResponse?: (text: string) => void;
firstResponse?: () => void;
abortSignal: AbortController;
}
export const streamFetch = ({
Expand All @@ -25,6 +25,11 @@ export const streamFetch = ({
body: JSON.stringify(data),
signal: abortSignal.signal
});
if (res.status === 200) {
firstResponse && firstResponse();
} else {
reject('请求异常');
}
const reader = res.body?.getReader();
if (!reader) return;
abortSignal.signal.addEventListener('abort', () => reader.cancel(), { once: true });
Expand All @@ -49,7 +54,6 @@ export const streamFetch = ({
}
const text = decoder.decode(value).replace(/<br\/>/g, '\n');
if (res.status === 200) {
responseText === '' && firstResponse && firstResponse(text);
onMessage(text);
responseText += text;
}
Expand Down
Loading