Skip to content

Commit

Permalink
Merge pull request #863 from traPtitech/impr/log-dl
Browse files Browse the repository at this point in the history
fix dl logs: scan entire days, show progress
  • Loading branch information
motoki317 authored Jan 15, 2024
2 parents 84c428c + 36381a8 commit b8a88c7
Showing 1 changed file with 78 additions and 17 deletions.
95 changes: 78 additions & 17 deletions dashboard/src/components/templates/app/ContainerLogExport.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Timestamp } from '@bufbuild/protobuf'
import { styled } from '@macaron-css/solid'
import { Component, createSignal } from 'solid-js'
import { Component, Show, createSignal } from 'solid-js'
import toast from 'solid-toast'
import { Application, ApplicationOutput } from '/@/api/neoshowcase/protobuf/gateway_pb'
import { Button } from '/@/components/UI/Button'
import { TextField } from '/@/components/UI/TextField'
import { client, handleAPIError } from '/@/libs/api'
import { saveToFile } from '/@/libs/download'
import { sleep } from '/@/libs/sleep'
import { addTimestamp } from '/@/libs/timestamp'
import useModal from '/@/libs/useModal'
import { useApplicationData } from '/@/routes'

Expand Down Expand Up @@ -56,26 +58,43 @@ const DownloadButtonsContainer = styled('div', {
},
})

const secondsPerDay = 86400
const loadDuration = 86400n
const maxExportDays = 30
const maxBatchFetchLines = 5000
const maxExportLines = maxBatchFetchLines * 20

const getLogsBefore = async (appID: string, before: string, lines: number): Promise<ApplicationOutput[]> => {
const getLogsBefore = async (
appID: string,
before: string,
days: number,
lines: number,
setProgressMessage: (message: string) => void,
): Promise<ApplicationOutput[]> => {
let remainingLines = lines
let nextBefore = Timestamp.fromJson(before)
const firstBefore = Timestamp.fromJson(before)
let nextBefore = firstBefore
let logLines: ApplicationOutput[] = []
while (remainingLines > 0) {
console.log(`Retrieving logs before ${nextBefore.toJson()}, remaining ${remainingLines} lines ...`)
while (remainingLines > 0 && firstBefore.seconds - nextBefore.seconds < days * secondsPerDay) {
const msg = `${nextBefore.toJson()} より前のログを取得中、残り ${remainingLines} 行 ...`
setProgressMessage(msg)
console.log(msg)

const res = await client.getOutput({
applicationId: appID,
before: nextBefore,
limit: Math.min(maxBatchFetchLines, remainingLines),
})
logLines = res.outputs.concat(logLines)

if (res.outputs.length === 0) break
remainingLines -= res.outputs.length
if (!res.outputs[0].time) throw new Error('time field not found')
nextBefore = res.outputs[0].time
if (res.outputs.length === 0) {
nextBefore = addTimestamp(nextBefore, -loadDuration)
} else {
if (!res.outputs[0].time) throw new Error('time field not found')
nextBefore = res.outputs[0].time
}
await sleep(500)
}
return logLines
}
Expand Down Expand Up @@ -112,9 +131,28 @@ const filename = (app: Application, type: exportType) => {
}
}

const exportBefore = async (app: Application, beforeStr: string, lines: number, type: exportType) => {
const exportBefore = async (
app: Application,
beforeStr: string,
days: number,
lines: number,
type: exportType,
setProgressMessage: (message: string) => void,
) => {
if (Number.isNaN(days)) {
toast.error('日数に整数を指定してください')
return
}
if (days <= 0) {
toast.error('1日以上を指定してください')
return
}
if (days > maxExportDays) {
toast.error(`${maxExportDays} 日以下を指定してください`)
return
}
if (Number.isNaN(lines)) {
toast.error('整数を指定してください')
toast.error('行に整数を指定してください')
return
}
if (lines <= 0) {
Expand All @@ -132,7 +170,7 @@ const exportBefore = async (app: Application, beforeStr: string, lines: number,
return
}

const logLines = await getLogsBefore(app.id, beforeStr, lines)
const logLines = await getLogsBefore(app.id, beforeStr, days, lines, setProgressMessage)
exportLines(app, logLines, type)
}
const exportLines = (app: Application, logLines: ApplicationOutput[], type: exportType) => {
Expand All @@ -151,15 +189,19 @@ export const ContainerLogExport: Component<Props> = (props) => {
const [exporting, setExporting] = createSignal(false)
const [beforePlaceholder, setBeforePlaceholder] = createSignal('')
const [before, setBefore] = createSignal('')
const [days, setDays] = createSignal(7)
const [count, setCount] = createSignal(5000)

const [progressMessage, setProgressMessage] = createSignal('')

const doExport = async (run: () => Promise<void>) => {
setExporting(true)
try {
await run()
} catch (e) {
handleAPIError(e, 'ログのエクスポートに失敗しました')
}
setProgressMessage('エクスポート完了!')
setExporting(false)
}

Expand All @@ -182,7 +224,7 @@ export const ContainerLogExport: Component<Props> = (props) => {
<Modal.Body>
<Options>
<Option>
<h3>現在表示されているログをダウンロード</h3>
<h3>現在表示されているログをエクスポート</h3>
<DownloadButtonsContainer>
<Button
variants="primary"
Expand All @@ -203,7 +245,7 @@ export const ContainerLogExport: Component<Props> = (props) => {
</DownloadButtonsContainer>
</Option>
<Option>
<h3>時間を指定してダウンロード</h3>
<h3>時間を指定してエクスポート</h3>
<DownloadSpecContainer>
<BeforeSpec>
<TextField
Expand All @@ -216,6 +258,21 @@ export const ContainerLogExport: Component<Props> = (props) => {
<span>より前の</span>
</DownloadSpecContainer>
<DownloadSpecContainer>
<span>最大</span>
<CountSpec>
<TextField
disabled={exporting()}
placeholder="7"
type="number"
value={`${days()}`}
onInput={(e) => setDays(+e.currentTarget.value)}
/>
</CountSpec>
<span>日間</span>
<span>(最大 {maxExportDays} 日)</span>
</DownloadSpecContainer>
<DownloadSpecContainer>
<span>最大</span>
<CountSpec>
<TextField
disabled={exporting()}
Expand All @@ -225,28 +282,32 @@ export const ContainerLogExport: Component<Props> = (props) => {
onInput={(e) => setCount(+e.currentTarget.value)}
/>
</CountSpec>
<span>行をエクスポート</span>
<span>(最大 {maxExportLines.toLocaleString()} 行)</span>
<span>行 (最大 {maxExportLines.toLocaleString()} 行) をエクスポート</span>
</DownloadSpecContainer>
<DownloadButtonsContainer>
<Button
variants="primary"
size="small"
disabled={exporting()}
onClick={() => doExport(() => exportBefore(app()!, before(), count(), 'txt'))}
onClick={() =>
doExport(() => exportBefore(app()!, before(), days(), count(), 'txt', setProgressMessage))
}
>
Export as .txt
</Button>
<Button
variants="primary"
size="small"
disabled={exporting()}
onClick={() => doExport(() => exportBefore(app()!, before(), count(), 'json'))}
onClick={() =>
doExport(() => exportBefore(app()!, before(), days(), count(), 'json', setProgressMessage))
}
>
Export as .json
</Button>
</DownloadButtonsContainer>
</Option>
<Show when={progressMessage() !== ''}>{progressMessage()}</Show>
</Options>
</Modal.Body>
</Modal.Container>
Expand Down

0 comments on commit b8a88c7

Please sign in to comment.