diff --git a/web/src/pages/Logs.tsx b/web/src/pages/Logs.tsx index e8ca9002f..ec7cf45a1 100644 --- a/web/src/pages/Logs.tsx +++ b/web/src/pages/Logs.tsx @@ -1,21 +1,31 @@ import { Button } from "@/components/ui/button"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; +import { LogLine, LogSeverity } from "@/types/log"; import copy from "copy-to-clipboard"; import { useCallback, useMemo, useRef, useState } from "react"; +import { IoIosAlert } from "react-icons/io"; +import { GoAlertFill } from "react-icons/go"; import { LuCopy } from "react-icons/lu"; import useSWR from "swr"; const logTypes = ["frigate", "go2rtc", "nginx"] as const; type LogType = (typeof logTypes)[number]; +const datestamp = /\[[\d\s-:]*]/; +const severity = /(DEBUG)|(INFO)|(WARNING)|(ERROR)/; +const section = /[\w.]*/; + function Logs() { const [logService, setLogService] = useState("frigate"); - const { data: frigateLogs } = useSWR("logs/frigate", { + const { data: frigateLogs } = useSWR("logs/frigate", { refreshInterval: 1000, }); const { data: go2rtcLogs } = useSWR("logs/go2rtc", { refreshInterval: 1000 }); const { data: nginxLogs } = useSWR("logs/nginx", { refreshInterval: 1000 }); + + // convert to log data + const logs = useMemo(() => { if (logService == "frigate") { return frigateLogs; @@ -28,6 +38,54 @@ function Logs() { } }, [logService, frigateLogs, go2rtcLogs, nginxLogs]); + const logLines = useMemo(() => { + if (logService == "frigate") { + if (!frigateLogs) { + return []; + } + + return frigateLogs + .split("\n") + .map((line) => { + const match = datestamp.exec(line); + + if (!match) { + return null; + } + + const sectionMatch = section.exec( + line.substring(match.index + match[0].length).trim(), + ); + + if (!sectionMatch) { + return null; + } + + return { + dateStamp: match.toString().slice(1, -1), + severity: severity + .exec(line) + ?.at(0) + ?.toString() + ?.toLowerCase() as LogSeverity, + section: sectionMatch.toString(), + content: line + .substring(line.indexOf(":", match.index + match[0].length) + 2) + .trim(), + }; + }) + .filter((value) => value != null) as LogLine[]; + } else if (logService == "go2rtc") { + return []; + } else if (logService == "nginx") { + return []; + } else { + return []; + } + }, [logService, frigateLogs, go2rtcLogs, nginxLogs]); + + //console.log(`the logs are ${JSON.stringify(logLines)}`); + const handleCopyLogs = useCallback(() => { copy(logs); }, [logs]); @@ -104,13 +162,99 @@ function Logs() {
- {logs} +
+
+ Severity +
+
+ Timestamp +
+
+ Tag +
+
+ Message +
+
+ {logLines.map((log, idx) => ( + + ))}
); } +type LogLineDataProps = { + line: LogLine; + offset: number; +}; +function LogLineData({ line, offset }: LogLineDataProps) { + // long log message + + const contentRef = useRef(null); + const [expanded, setExpanded] = useState(false); + + const contentOverflows = useMemo(() => { + if (!contentRef.current) { + return false; + } + + return contentRef.current.scrollWidth > contentRef.current.clientWidth; + // update on ref change + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [contentRef.current]); + + // severity coloring + + const severityClassName = useMemo(() => { + switch (line.severity) { + case "info": + return "text-secondary-foreground rounded-md"; + case "warning": + return "text-yellow-400 rounded-md"; + case "error": + return "text-danger rounded-md"; + } + }, [line]); + + return ( +
+
+ {line.severity == "error" ? ( + + ) : ( + + )} + {line.severity} +
+
+ {line.dateStamp} +
+
+ {line.section} +
+
+
+ {line.content} +
+ {contentOverflows && ( + + )} +
+
+ ); +} + export default Logs; diff --git a/web/src/types/log.ts b/web/src/types/log.ts new file mode 100644 index 000000000..445e79c83 --- /dev/null +++ b/web/src/types/log.ts @@ -0,0 +1,8 @@ +export type LogSeverity = "info" | "warning" | "error" | "debug"; + +export type LogLine = { + dateStamp: string; + severity: LogSeverity; + section: string; + content: string; +};