-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #106 from MichaelPesce/issue-99
Add logging panel
Showing
10 changed files
with
465 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import time | ||
|
||
def parse_logs(logs_path, time_since): | ||
""" | ||
Assume a log format of: | ||
"[%(levelname)s] %(asctime)s %(name)s (%(filename)s:%(lineno)s): %(message)s" | ||
""" | ||
result = [] | ||
log_entries = [] | ||
log_file = open(logs_path, 'r') | ||
all_logs = log_file.read() | ||
log_file.close() | ||
logs = all_logs.split('\n[') | ||
for line in logs: | ||
try: | ||
log_split = line.split(' ') | ||
log_time = log_split[1:3] | ||
log_time_string = f'{log_time[0]} {log_time[1]}'.split(',')[0] | ||
stripped_time = time.strptime(log_time_string, "%Y-%m-%d %H:%M:%S") | ||
asctime = time.mktime(stripped_time) | ||
if asctime > time_since: | ||
result.append(line) | ||
log_level = line.split(']')[0] | ||
log_name = log_split[3] | ||
log_file_lineno = log_split[4] | ||
log_file = log_file_lineno.split(":")[0] | ||
log_lineno = log_file_lineno.split(":")[1] | ||
log_message = line.split(log_file_lineno)[1] | ||
if len(log_file) > 0: | ||
log_file = log_file[1:] | ||
if len(log_lineno) > 0: | ||
log_lineno = log_lineno[:-1] | ||
if len(log_message) > 0: | ||
log_message = log_message[1:] | ||
log_entry = { | ||
"log_time": asctime, | ||
"log_level": log_level, | ||
"log_name": log_name, | ||
"log_file": log_file, | ||
"log_lineno": log_lineno, | ||
"log_message": log_message, | ||
} | ||
log_entries.append(log_entry) | ||
except Exception as e: | ||
print(f'unable to parse log line: {e}') | ||
return log_entries |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
290 changes: 290 additions & 0 deletions
290
electron/ui/src/components/LoggingPanel/LoggingPanel.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,290 @@ | ||
import {useEffect, useState, useRef } from 'react'; | ||
import { InputAdornment, TextField, IconButton, Tooltip, MenuItem, Checkbox, ListItemText, Menu } from '@mui/material'; | ||
import { Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, Typography } from '@mui/material'; | ||
import CloseIcon from '@mui/icons-material/Close'; | ||
import { getLogs, downloadLogs } from '../../services/flowsheet.service'; | ||
import Draggable from 'react-draggable'; | ||
import FullscreenIcon from '@mui/icons-material/Fullscreen'; | ||
import FullscreenExitIcon from '@mui/icons-material/FullscreenExit'; | ||
import DownloadIcon from '@mui/icons-material/Download'; | ||
import SearchIcon from '@mui/icons-material/Search'; | ||
import FilterListIcon from '@mui/icons-material/FilterList'; | ||
|
||
|
||
export default function LoggingPanel(props) { | ||
const { open, onClose } = props; | ||
const [ logData, setLogData ] = useState([]) | ||
const [ dialogHeight, setDialogHeight ] = useState('60vh') | ||
const [ dialogWidth, setDialogWidth ] = useState('60vw') | ||
const [ fullscreen, setFullscreen] = useState(false) | ||
const [ searchTerm, setSearchTerm ] = useState("") | ||
const [ filters, setFilters ] = useState(["DEBUG", "INFO", "WARNING", "ERROR"]) | ||
const [ showFilters, setShowFilters ] = useState(false) | ||
const [ anchorEl, setAnchorEl ] = useState(null); | ||
const divRef = useRef(null); | ||
|
||
useEffect(() => { | ||
if (open)( | ||
getLogs() | ||
.then(response => response.json()) | ||
.then((data) => { | ||
console.log('got logs: ') | ||
setLogData(data) | ||
// console.log(data) | ||
// window.scrollTo(0, document.body.scrollHeight); | ||
}) | ||
) | ||
|
||
},[props]) | ||
|
||
useEffect(() => { | ||
if (open) { | ||
divRef.current.scrollIntoView({ behavior: 'smooth' }); | ||
} | ||
|
||
},[logData]) | ||
|
||
|
||
|
||
const styles = { | ||
dialogTitle: { | ||
backgroundColor: "black", | ||
color: "white", | ||
}, | ||
dialogContent: { | ||
backgroundColor: "black", | ||
color: "white", | ||
}, | ||
dialogContentText: { | ||
backgroundColor: "black", | ||
color: "white", | ||
// overflowX: "auto" | ||
}, | ||
dialog: { | ||
// maxWidth: "80vw", | ||
}, | ||
dialogPaper: { | ||
minHeight: dialogHeight, | ||
maxHeight: dialogHeight, | ||
minWidth: dialogWidth, | ||
maxWidth: dialogWidth, | ||
}, | ||
DEBUG: { | ||
color: "#3B90FF", | ||
}, | ||
INFO: { | ||
color: "#28FF24", | ||
}, | ||
WARNING: { | ||
color: "#FFF42C", | ||
}, | ||
ERROR: { | ||
color: "#FF042E", | ||
} | ||
} | ||
|
||
const handleClose = () => { | ||
setSearchTerm("") | ||
setShowFilters(false) | ||
setFilters(["DEBUG", "INFO", "WARNING", "ERROR"]) | ||
if(fullscreen) handleFullscreen() | ||
onClose() | ||
}; | ||
|
||
const handleFullscreen = () => { | ||
if (fullscreen) { | ||
setDialogHeight('60vh') | ||
setDialogWidth('60vw') | ||
} else { | ||
setDialogHeight('100vh') | ||
setDialogWidth('100vw') | ||
} | ||
setFullscreen(!fullscreen) | ||
} | ||
|
||
const handleDownloadLogs = () => { | ||
downloadLogs().then(response => response.blob()) | ||
.then((data) => { | ||
console.log(data) | ||
let logsUrl = window.URL.createObjectURL(data); | ||
let tempLink = document.createElement('a'); | ||
tempLink.href = logsUrl; | ||
tempLink.setAttribute('download', 'watertap-ui-logs.log'); | ||
tempLink.click(); | ||
}) | ||
}; | ||
|
||
const handleShowLogFilters = (event) => { | ||
setShowFilters(!showFilters) | ||
setAnchorEl(event.currentTarget); | ||
} | ||
|
||
const handleFilter = (level) => { | ||
let tempFilters = [...filters] | ||
const index = tempFilters.indexOf(level); | ||
if (index > -1) { | ||
tempFilters.splice(index, 1); | ||
} else { | ||
tempFilters.push(level) | ||
} | ||
setFilters(tempFilters) | ||
} | ||
|
||
const getTextColor = (line) => { | ||
if (line.includes('ERROR')) return styles.ERROR.color | ||
else if (line.includes('INFO')) return styles.INFO.color | ||
else if (line.includes('DEBUG')) return styles.DEBUG.color | ||
else if (line.includes('WARNING')) return styles.WARNING.color | ||
else return "white" | ||
} | ||
|
||
const descriptionElementRef = useRef(null); | ||
useEffect(() => { | ||
if (open) { | ||
const { current: descriptionElement } = descriptionElementRef; | ||
if (descriptionElement !== null) { | ||
descriptionElement.focus(); | ||
} | ||
} | ||
}, [open]); | ||
|
||
return ( | ||
<Draggable handle="#console-dialog"> | ||
<Dialog | ||
open={open} | ||
onClose={handleClose} | ||
scroll={"paper"} | ||
aria-labelledby="console-dialog" | ||
aria-describedby="console-dialog-description" | ||
PaperProps={{ | ||
sx: styles.dialogPaper | ||
}} | ||
BackdropProps={{ | ||
sx: {backgroundColor: "transparent"} | ||
}} | ||
> | ||
<DialogTitle id="dialog-title" style={styles.dialogTitle}>Backend Logs</DialogTitle> | ||
|
||
<TextField id={'searchBar'} | ||
label={'Search'} | ||
variant="outlined" | ||
size="small" | ||
value={searchTerm} | ||
onChange={(event) => setSearchTerm(event.target.value)} | ||
sx={{ | ||
position: 'absolute', | ||
right: 160, | ||
top: 12, | ||
color: "white", | ||
backgroundColor: "#292f30", | ||
borderRadius: 10, | ||
input: { color: 'white' }, | ||
}} | ||
InputProps={{ | ||
startAdornment: ( | ||
<InputAdornment position="start" sx={{color: "white"}}> | ||
<SearchIcon /> | ||
</InputAdornment> | ||
), | ||
}} | ||
/> | ||
<IconButton | ||
aria-label="close" | ||
onClick={handleShowLogFilters} | ||
sx={{ | ||
position: 'absolute', | ||
right: 120, | ||
top: 8, | ||
color: "white", | ||
}} | ||
> | ||
<FilterListIcon/> | ||
</IconButton> | ||
<Menu | ||
id="log-filter" | ||
anchorEl={anchorEl} | ||
open={showFilters} | ||
onClose={() => setShowFilters(false)} | ||
sx={{ | ||
"& .MuiPaper-root": { | ||
backgroundColor: "#292f30" | ||
} | ||
}} | ||
> | ||
{["DEBUG", "INFO", "WARNING", "ERROR"].map((loglevel, idx) => ( | ||
<MenuItem key={loglevel} value={loglevel} onClick={() => handleFilter(loglevel)} sx={{color: "white"}}> | ||
<Checkbox | ||
checked={filters.includes(loglevel)} | ||
sx={{ | ||
color: styles[loglevel].color, | ||
'&.Mui-checked': { | ||
color: styles[loglevel].color, | ||
}, | ||
}} | ||
/> | ||
<ListItemText primary={loglevel} /> | ||
</MenuItem> | ||
))} | ||
</Menu> | ||
|
||
<Tooltip title={"Download full logs"}> | ||
<IconButton | ||
aria-label="close" | ||
onClick={handleDownloadLogs} | ||
sx={{ | ||
position: 'absolute', | ||
right: 80, | ||
top: 8, | ||
color: "white", | ||
}} | ||
> | ||
<DownloadIcon/> | ||
</IconButton> | ||
</Tooltip> | ||
<IconButton | ||
aria-label="close" | ||
onClick={handleFullscreen} | ||
sx={{ | ||
position: 'absolute', | ||
right: 40, | ||
top: 8, | ||
color: "white", | ||
}} | ||
> | ||
{fullscreen ? <FullscreenExitIcon/> : <FullscreenIcon />} | ||
</IconButton> | ||
<IconButton | ||
aria-label="close" | ||
onClick={handleClose} | ||
sx={{ | ||
position: 'absolute', | ||
right: 0, | ||
top: 8, | ||
color: "white", | ||
}} | ||
> | ||
<CloseIcon /> | ||
</IconButton> | ||
<DialogContent style={styles.dialogContent} dividers={true}> | ||
<DialogContentText | ||
id="scroll-dialog-description" | ||
ref={descriptionElementRef} | ||
tabIndex={-1} | ||
style={styles.dialogContentText} | ||
aria-labelledby="console-dialog-content-text" | ||
component="span" | ||
> | ||
{logData.map((line, idx) => { | ||
if (line.log_message.toLowerCase().includes(searchTerm.toLowerCase()) && filters.includes(line.log_level)) { | ||
return <Typography style={{color: getTextColor(line.log_level), overflowWrap: "break-word"}} key={idx}>[{line.log_level}] {line.log_name}: {line.log_message}</Typography> | ||
} | ||
|
||
})} | ||
<div id="bottom-div" ref={divRef} ></div> | ||
</DialogContentText> | ||
</DialogContent> | ||
</Dialog> | ||
</Draggable> | ||
) | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters