CEC-3577: trex logs (#285)

* CEC-3577: trex logs

add filtering
add progress bar for log fetching
always fetch all the logs
request canceling

* don't hide progress
This commit is contained in:
Eduard Voronkin
2023-03-02 11:14:39 -08:00
committed by GitHub
parent 24ac0898af
commit 54f4386a58
2 changed files with 106 additions and 27 deletions

View File

@@ -8,6 +8,13 @@ import {
TablePagination, TablePagination,
TableRow TableRow
} from "@material-ui/core"; } from "@material-ui/core";
import LinearProgress from '@mui/material/LinearProgress';
import Checkbox from '@mui/material/Checkbox';
import FormGroup from '@mui/material/FormGroup';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormControl from '@mui/material/FormControl';
import FormLabel from '@mui/material/FormLabel';
import { import {
KeyboardDatePicker, MuiPickersUtilsProvider KeyboardDatePicker, MuiPickersUtilsProvider
} from '@material-ui/pickers'; } from '@material-ui/pickers';
@@ -52,6 +59,33 @@ const tableColumns = [
}, },
]; ];
const logLevelCheckBoxes = [
{
"id": "trace",
"label": "Trace"
},
{
"id": "debug",
"label": "Debug"
},
{
"id": "info",
"label": "Info"
},
{
"id": "warning",
"label": "Warning"
},
{
"id": "error",
"label": "Error"
},
{
"id": "critical",
"label": "Critical"
},
]
const transformLogs = (logs) => const transformLogs = (logs) =>
logs.map((log) => { logs.map((log) => {
const { level, timestamp, received_timestamp, line_number, filename, msg } = log; const { level, timestamp, received_timestamp, line_number, filename, msg } = log;
@@ -75,14 +109,22 @@ const fromatDateForRequest = (date) => {
return getYear + "-" + getMonth + "-" + getDay return getYear + "-" + getMonth + "-" + getDay
}; };
//read at least 30000 bytes per one API request //read at least 50000 bytes per one API request
const ONE_READ_SIZE = 30000 const ONE_READ_SIZE = 50000
const DEFAULT_PAGE_SIZE = 25 const DEFAULT_PAGE_SIZE = 25
const TRexLogsTable = ({ vin, token, classes }) => { const TRexLogsTable = ({ vin, token, classes }) => {
const [allLogsFetched, setAllLogsFetched] = useState(false) const [allLogsFetched, setAllLogsFetched] = useState(false)
const [blobSize, setBlobSize] = useState(0) const [blobSize, setBlobSize] = useState(0)
const [currentOffset, setCurrentOffset] = useState(0) const [currentOffset, setCurrentOffset] = useState(0)
const [currectLogLevels, setCurrentLogLevels] = useState({
trace: true,
debug: true,
info: true,
warning: true,
error: true,
critical: true
})
const [logs, setLogs] = useState([]); const [logs, setLogs] = useState([]);
const [pageIndex, setPageIndex] = useState(0); const [pageIndex, setPageIndex] = useState(0);
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE); const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
@@ -90,24 +132,25 @@ const TRexLogsTable = ({ vin, token, classes }) => {
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
const { setMessage } = useStatusContext(); const { setMessage } = useStatusContext();
let controller = new AbortController()
const readBlob = async (offset, count) => { const readBlob = async (offset, count) => {
console.log(`reading from offset: ${offset}`) console.log(`reading from offset: ${offset}`)
return await api.getTRexLogs(vin, fromatDateForRequest(selectedDate), offset, count, "UP", token) return await api.getTRexLogs(vin, fromatDateForRequest(selectedDate), offset, count, "UP", token, controller)
} }
const getDesiredSize = () => { const getDesiredSize = () => {
return pageSize * pageIndex + pageSize return pageSize * pageIndex + pageSize
} }
const getReadPercentage = () => { const getReadPercentage = () => {
if (blobSize === 0) { return (currentOffset * 100 / blobSize).toFixed(2);
return `No logs for ${fromatDateForRequest(selectedDate)}`
} }
return `Read ${(currentOffset * 100 / blobSize).toFixed(2)}% of logs` const getFilteredLogs = (logs) => {
return logs.filter(log => currectLogLevels[log.level] === true)
} }
const fetchLogs = async (desiredSize) => { const fetchAllLogs = async () => {
let fetched = [] let fetched = []
let offset = currentOffset let offset = currentOffset
let readSize = ONE_READ_SIZE let readSize = ONE_READ_SIZE
do { for (; ;) {
const result = await readBlob(offset, readSize) const result = await readBlob(offset, readSize)
if (result.error) { if (result.error) {
fetched.error = result.error fetched.error = result.error
@@ -115,19 +158,18 @@ const TRexLogsTable = ({ vin, token, classes }) => {
} }
console.log(`ret.RealOffset ${result.RealOffset}\nret.bytesRead ${result.bytesRead}\ndesired offset ${offset}\ndesired read size ${readSize}\nblobsize: ${result.blobSize}`) console.log(`ret.RealOffset ${result.RealOffset}\nret.bytesRead ${result.bytesRead}\ndesired offset ${offset}\ndesired read size ${readSize}\nblobsize: ${result.blobSize}`)
setBlobSize(result.blobSize) setBlobSize(result.blobSize)
//if read two times less then neccessary, then adjust read size
if (fetched.length < desiredSize / 2) {
readSize *= 2 readSize *= 2
console.log(`new read size: ${readSize}`) console.log(`new read size: ${readSize}`)
}
offset = result.RealOffset + result.bytesRead offset = result.RealOffset + result.bytesRead
setCurrentOffset(offset)
fetched = transformLogs(result.data).concat(fetched) fetched = transformLogs(result.data).concat(fetched)
setLogs(fetched)
if (offset >= result.blobSize) { if (offset >= result.blobSize) {
setMessage(`All log for ${fromatDateForRequest(selectedDate)} fetched`) setMessage(`All log for ${fromatDateForRequest(selectedDate)} fetched`)
setAllLogsFetched(true) setAllLogsFetched(true)
break break
} }
} while (fetched.length <= desiredSize) }
if (fetched.length === 0) { if (fetched.length === 0) {
if (logs.length !== 0) { if (logs.length !== 0) {
@@ -152,25 +194,26 @@ const TRexLogsTable = ({ vin, token, classes }) => {
console.log(`desired size: ${desiredSize}, actual size: ${logs.length}`) console.log(`desired size: ${desiredSize}, actual size: ${logs.length}`)
if (desiredSize < logs.length || allLogsFetched) { if (desiredSize < logs.length || allLogsFetched) {
console.log(`enough logs in cache`) console.log(`enough logs in cache`)
setTotal(getFilteredLogs(logs).length)
return return
} }
let fetched = await fetchLogs(desiredSize) let fetched = await fetchAllLogs()
if (!fetched || fetched.length === 0) { if (!fetched || fetched.length === 0) {
return return
} }
fetched = fetched.concat(logs) setTotal(getFilteredLogs(fetched).length);
setLogs(fetched);
setTotal(fetched.length);
} catch (e) { } catch (e) {
setMessage(e.message); setMessage(e.message);
logger.warn(e.stack); logger.warn(e.stack);
} }
})(); })();
return () => controller?.abort()
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [vin, token, pageIndex, pageSize, selectedDate]); }, [vin, token, pageIndex, pageSize, selectedDate, currectLogLevels]);
const handleChangePageSize = (event) => { const handleChangePageSize = (event) => {
setPageSize(parseInt(event.target.value, 10)); setPageSize(parseInt(event.target.value, 10));
setTotal(getFilteredLogs(logs).length)
setPageIndex(0); setPageIndex(0);
}; };
@@ -183,12 +226,37 @@ const TRexLogsTable = ({ vin, token, classes }) => {
setSelectedDate(newValue) setSelectedDate(newValue)
}; };
const handleNewFilter = (event) => {
setPageIndex(0)
console.log(event)
setCurrentLogLevels({
...currectLogLevels,
[event.target.defaultValue]: event.target.checked,
});
};
return ( return (
<div className={clsx(classes.paper, classes.tableSize)}> <div className={clsx(classes.paper, classes.tableSize)}>
<Table <Table
style={{ tableLayout: "fixed", minWidth: "1400px" }} style={{ tableLayout: "fixed", width: "100%" }}
> >
<TableRow> <TableRow>
<TableCell align="center">
<FormControl component="fieldset">
<FormLabel component="legend">Log levels</FormLabel>
<FormGroup aria-label="position" row>
{logLevelCheckBoxes.map((box) => (
<FormControlLabel
value={box.id}
control={<Checkbox defaultChecked disabled={!allLogsFetched} />}
label={box.label}
labelPlacement="bottom"
onChange={handleNewFilter}
/>
))}
</FormGroup>
</FormControl>
</TableCell>
<TableCell align="center"> <TableCell align="center">
<MuiPickersUtilsProvider utils={DateFnsUtils}> <MuiPickersUtilsProvider utils={DateFnsUtils}>
<KeyboardDatePicker <KeyboardDatePicker
@@ -207,12 +275,22 @@ const TRexLogsTable = ({ vin, token, classes }) => {
</MuiPickersUtilsProvider> </MuiPickersUtilsProvider>
</TableCell> </TableCell>
<TableCell align="center"> <TableCell align="center">
{getReadPercentage()} {
blobSize === 0 ? `No logs for ${fromatDateForRequest(selectedDate)}` :
`Read ${getReadPercentage()}% of logs`
}
{
<LinearProgress
variant="determinate"
align="center"
value={getReadPercentage()} />
}
</TableCell> </TableCell>
</TableRow> </TableRow>
</Table> </Table>
<Table <Table
style={{ tableLayout: "fixed", minWidth: "1400px" }} style={{ tableLayout: "fixed", width: "100%" }}
> >
<TableHead classes={classes}> <TableHead classes={classes}>
<TableRow> <TableRow>
@@ -222,11 +300,11 @@ const TRexLogsTable = ({ vin, token, classes }) => {
</TableRow> </TableRow>
</TableHead > </TableHead >
<TableBody> <TableBody>
{logs.slice(-getDesiredSize(), (pageIndex === 0 ? undefined : -(pageSize * pageIndex))).map((log, i) => ( {getFilteredLogs(logs).slice(-getDesiredSize(), (pageIndex === 0 ? undefined : -(pageSize * pageIndex))).map((log, i) => (
<TableRow key={log.trex_timestamp + log.cloud_timestamp} > <TableRow key={log.trex_timestamp + log.cloud_timestamp} >
<TableCell align="center">{log.level}</TableCell> <TableCell align="center">{log.level}</TableCell>
<TableCell align="center">{log.trex_timestamp}</TableCell> <TableCell align="center" style={{ wordBreak: "break-all" }}>{log.trex_timestamp}</TableCell>
<TableCell align="center">{log.cloud_timestamp}</TableCell> <TableCell align="center" style={{ wordBreak: "break-all" }}>{log.cloud_timestamp}</TableCell>
<TableCell align="center">{log.line_number}</TableCell> <TableCell align="center">{log.line_number}</TableCell>
<TableCell align="center" style={{ wordBreak: "break-all" }}>{log.filename}</TableCell> <TableCell align="center" style={{ wordBreak: "break-all" }}>{log.filename}</TableCell>
<TableCell align="left" style={{ wordBreak: "break-all" }}>{log.msg}</TableCell> <TableCell align="left" style={{ wordBreak: "break-all" }}>{log.msg}</TableCell>

View File

@@ -173,13 +173,14 @@ const vehiclesAPI = {
.then(fetchRespHandler) .then(fetchRespHandler)
.catch(errorHandler), .catch(errorHandler),
getTRexLogs: async (vin, date, offset, count, direction, token) => getTRexLogs: async (vin, date, offset, count, direction, token, controller) =>
fetch(`${API_ENDPOINT}/vehicle/${vin}/trex-logs?date=${date}&offset=${offset}&count=${count}&direction=${direction}`, { fetch(`${API_ENDPOINT}/vehicle/${vin}/trex-logs?date=${date}&offset=${offset}&count=${count}&direction=${direction}`, {
method: "GET", method: "GET",
headers: Object.assign( headers: Object.assign(
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
getAuthHeaderOptions(token) getAuthHeaderOptions(token)
), ),
signal: controller.signal
}) })
.then(fetchRespHandler) .then(fetchRespHandler)
.catch(errorHandler), .catch(errorHandler),