Skip to content

Commit

Permalink
fix(logs): fix freezes (#592)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdebon authored Apr 27, 2023
1 parent f09fa2e commit e270a76
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 57 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Log } from 'qovery-typescript-axios'
import { useCallback, useContext, useEffect, useState } from 'react'
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
import { useSelector } from 'react-redux'
import { useParams } from 'react-router-dom'
import useWebSocket from 'react-use-websocket'
Expand All @@ -22,11 +22,15 @@ export function PodLogsFeature(props: PodLogsFeatureProps) {
const { updateServiceId } = useContext(ServiceStageIdsContext)

const [loadingStatus, setLoadingStatus] = useState<LoadingStatus>('not loaded')
const [messageChunks, setMessageChunks] = useState<Log[][]>([])
const [messageNGINXChunks, setMessageNGINXChunks] = useState<Log[][]>([])
const [logs, setLogs] = useState<Log[]>([])
const [pauseLogs, setPauseLogs] = useState<Log[]>([])
const [nginxLogs, setNginxLogs] = useState<Log[]>([])
const [pauseStatusLogs, setPauseStatusLogs] = useState<boolean>(false)
const [enabledNginx, setEnabledNginx] = useState<boolean>(false)
const chunkSize = 500
const [debounceTime] = useState(500)
const logCounter = useRef(0)

const application = useSelector<RootState, ApplicationEntity | undefined>((state) =>
selectApplicationById(state, serviceId)
Expand All @@ -51,34 +55,68 @@ export function PodLogsFeature(props: PodLogsFeatureProps) {
return new Promise((resolve) => resolve(url))
}, [organizationId, clusterId, projectId, environmentId, serviceId, getAccessTokenSilently])

useWebSocket(applicationLogsUrl, {
onMessage: (message) => {
setLoadingStatus('loaded')

if (pauseStatusLogs) {
setPauseLogs((prev: Log[]) => [...prev, JSON.parse(message?.data)])
const onMessageHandler = useCallback((message: MessageEvent) => {
setMessageChunks((prevChunks) => {
const lastChunk = prevChunks[prevChunks.length - 1] || []
if (lastChunk.length < chunkSize) {
return [...prevChunks.slice(0, -1), [...lastChunk, { ...JSON.parse(message?.data), id: logCounter.current++ }]]
} else {
setLogs((prev: Log[]) => [...prev, ...pauseLogs, JSON.parse(message?.data)])
setPauseLogs([])
return [...prevChunks, [{ ...JSON.parse(message?.data), id: logCounter.current++ }]]
}
},
})
}, [])

const onNginxMessageHandler = useCallback((message: MessageEvent) => {
setMessageNGINXChunks((prevChunks) => {
const lastChunk = prevChunks[prevChunks.length - 1] || []
if (lastChunk.length < chunkSize) {
return [...prevChunks.slice(0, -1), [...lastChunk, { ...JSON.parse(message?.data), id: logCounter.current++ }]]
} else {
return [...prevChunks, [{ ...JSON.parse(message?.data), id: logCounter.current++ }]]
}
})
}, [])
useWebSocket(applicationLogsUrl, {
onMessage: onMessageHandler,
})

useWebSocket(
nginxLogsUrl,
{
onMessage: (message) => {
if (pauseStatusLogs) {
setPauseLogs((prev: Log[]) => [...prev, JSON.parse(message?.data)])
} else {
setNginxLogs((prev: Log[]) => [...prev, ...pauseLogs, JSON.parse(message?.data)])
setPauseLogs([])
}
},
onMessage: onNginxMessageHandler,
},
enabledNginx
)

useEffect(() => {
if (messageChunks.length === 0 || pauseStatusLogs) return
const timerId = setTimeout(() => {
setLoadingStatus('loaded')
if (!pauseStatusLogs) {
setMessageChunks((prevChunks) => prevChunks.slice(1))
setLogs((prevLogs) => [...prevLogs, ...messageChunks[0]])
}
}, debounceTime)

return () => {
clearTimeout(timerId)
}
}, [messageChunks, pauseStatusLogs, debounceTime])

useEffect(() => {
if (messageNGINXChunks.length === 0 || pauseStatusLogs) return
const timerId = setTimeout(() => {
if (!pauseStatusLogs) {
setMessageNGINXChunks((prevChunks) => prevChunks.slice(1))
setNginxLogs((prevLogs) => [...prevLogs, ...messageNGINXChunks[0]])
}
}, debounceTime)

return () => {
clearTimeout(timerId)
}
}, [messageNGINXChunks, pauseStatusLogs, debounceTime])

// update serviceId
useEffect(() => {
updateServiceId(serviceId)
Expand All @@ -87,17 +125,19 @@ export function PodLogsFeature(props: PodLogsFeatureProps) {
// reset pod logs
useEffect(() => {
setLogs([])
setPauseLogs([])
setMessageChunks([])
setPauseStatusLogs(false)
setLoadingStatus('not loaded')
setNginxLogs([])
setMessageNGINXChunks([])
setEnabledNginx && setEnabledNginx(false)
}, [serviceId, setEnabledNginx])

const logsSorted =
enabledNginx && nginxLogs
const logsSorted = useMemo<Log[]>(() => {
return enabledNginx && nginxLogs
? [...logs, ...nginxLogs].sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime())
: logs
}, [enabledNginx, nginxLogs, logs])

return (
<PodLogs
Expand Down
47 changes: 35 additions & 12 deletions libs/pages/logs/environment/src/lib/page-environment-logs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,10 @@ export function PageEnvironmentLogs() {
},
})

const [messageChunks, setMessageChunks] = useState<EnvironmentLogs[][]>([])
const chunkSize = 500
const [debounceTime, setDebounceTime] = useState(1000)
const [logs, setLogs] = useState<EnvironmentLogs[]>([])
const [pauseLogs, setPauseLogs] = useState<EnvironmentLogs[]>([])
const [pauseStatusLogs, setPauseStatusLogs] = useState<boolean>(false)
const [loadingStatusDeploymentLogs, setLoadingStatusDeploymentLogs] = useState<LoadingStatus>('not loaded')

Expand All @@ -88,18 +90,39 @@ export function PageEnvironmentLogs() {

const newLog = JSON.parse(message?.data)

if (pauseStatusLogs) {
setPauseLogs((prev: EnvironmentLogs[]) => [...prev, ...newLog])
} else {
setLogs((prev: EnvironmentLogs[]) => {
// return unique log by timestamp
return [...new Map([...prev, ...pauseLogs, ...newLog].map((item) => [item['timestamp'], item])).values()]
})
setPauseLogs([])
}
setMessageChunks((prevChunks) => {
const lastChunk = prevChunks[prevChunks.length - 1] || []
if (lastChunk.length < chunkSize) {
return [...prevChunks.slice(0, -1), [...lastChunk, ...newLog]]
} else {
return [...prevChunks, [...newLog]]
}
})
},
})

useEffect(() => {
if (messageChunks.length === 0 || pauseStatusLogs) return

const timerId = setTimeout(() => {
if (!pauseStatusLogs) {
setMessageChunks((prevChunks) => prevChunks.slice(1))
setLogs((prevLogs) => {
const combinedLogs = [...prevLogs, ...messageChunks[0]]
return [...new Map(combinedLogs.map((item) => [item['timestamp'], item])).values()]
})

if (logs.length > 1000) {
setDebounceTime(100)
}
}
}, debounceTime)

return () => {
clearTimeout(timerId)
}
}, [messageChunks, pauseStatusLogs])

return (
<div className="flex h-full">
<ServiceStageIdsProvider>
Expand Down Expand Up @@ -129,13 +152,13 @@ export function PageEnvironmentLogs() {
<div className="flex justify-center w-[calc(100%-8px)] min-h-full bg-element-light-darker-400 m-1 rounded">
<div className="flex flex-col items-center mt-12">
<Icon name={IconAwesomeEnum.WRENCH} className="text-text-300" />
<p className="text-text-300 font-medium">
<div className="text-text-300 font-medium">
Please select a service on the left menu to access its deployment logs or live logs.
<p>
You can access the deployment logs only for the services recently deployed (
<a className="text-brand-400">in purple</a>).
</p>
</p>
</div>
</div>
</div>
)}
Expand Down
5 changes: 4 additions & 1 deletion libs/pages/logs/environment/src/lib/ui/pod-logs/pod-logs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,10 @@ export function PodLogs(props: PodLogsProps) {
]

const memoRow = useMemo(
() => logs?.map((log: Log, index: number) => <RowPod key={index} index={index} data={log} filter={filter} />),
() =>
logs?.map((log: Log, index: number) => {
return <RowPod key={log.id} index={index} data={log} filter={filter} />
}),
[logs, filter]
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,13 @@ describe('RowPod', () => {
render(<RowPod {...props} />)

const cellVersion = screen.getByTestId('cell-version')
expect(cellVersion?.textContent).toBe('53d...727')
expect(cellVersion?.textContent).toBe('53deb16')
})

it('should have function to format version', () => {
const { baseElement } = render(<div>{formatVersion('53deb16f853aef759b8be84fbeec96e9727')}</div>)

expect(baseElement.textContent).toBe('53d...727')
expect(baseElement.textContent).toBe('53deb16')
expect(formatVersion('53deb')).toBe('53deb')
})
})
18 changes: 2 additions & 16 deletions libs/pages/logs/environment/src/lib/ui/row-pod/row-pod.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
import { Log } from 'qovery-typescript-axios'
import { useContext } from 'react'
import { UpdateTimeContext } from '@qovery/shared/console-shared'
import {
CopyToClipboard,
Icon,
IconAwesomeEnum,
TableFilterProps,
TableRowFilter,
Tooltip,
convertToAnsi,
} from '@qovery/shared/ui'
import { Icon, IconAwesomeEnum, TableFilterProps, TableRowFilter, Tooltip, convertToAnsi } from '@qovery/shared/ui'
import { dateFullFormat } from '@qovery/shared/utils'

const COLORS = [
Expand Down Expand Up @@ -51,9 +43,7 @@ export const formatVersion = (version: string) => {
} else {
return (
<Tooltip content={version}>
<span>
{version.substring(0, 3)}...{version.slice(-3)}
</span>
<span>{version.substring(0, 7)}</span>
</Tooltip>
)
}
Expand Down Expand Up @@ -111,10 +101,6 @@ export function RowPod(props: RowPodProps) {
</div>
<div data-testid="cell-msg" className="select-text pr-6 pt-0.5 text-text-100 relative w-full">
<span className="whitespace-pre-wrap break-all">{convertToAnsi(data.message)}</span>
<CopyToClipboard
className="opacity-0 group-hover:opacity-100 text-white !absolute right-2 top-1"
content={data.message}
/>
</div>
</div>
</TableRowFilter>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ export function SidebarStatus(props: SidebarStatusProps) {

return (
<div className="border-b border-element-light-darker-100 p-5">
<p className="flex items-center justify-between text-text-300 text-xs mb-2">
<div className="flex items-center justify-between text-text-300 text-xs mb-2">
Pipeline deployment status:
<StatusChip status={environmentStatus?.last_deployment_state || environmentStatus?.state} />
</p>
</div>
<p className="flex items-center justify-between text-text-300 text-xs mb-2">
Deployment id:
<Tooltip content={environmentStatus?.last_deployment_id || ''}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe('LayoutLogs', () => {

const loadingScreen = screen.getByTestId('loading-screen')

expect(loadingScreen.querySelector('p')?.textContent).toBe('Logs not available')
expect(loadingScreen.querySelector('div')?.textContent).toBe('Logs not available')
})

it('should have text with error line', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export function LayoutLogs(props: LayoutLogsProps) {
src="/assets/images/event-placeholder-dark.svg"
alt="Event placeholder"
/>
<p className="mt-5 text-text-100 font-medium text-center">{placeholderDescription}</p>
<div className="mt-5 text-text-100 font-medium text-center">{placeholderDescription}</div>
</div>
)}
</div>
Expand Down

0 comments on commit e270a76

Please sign in to comment.