Logs in UI (#4562)

* Log all services to RAM

* Gracefully handle shutdown

* Add logs route

* Remove tail

* Return logs for services

* Display log chooser with copy button

* show logs for specific services

* Clean up settings logs

* Add copy functionality to logs

* Add copy functionality to logs

* Fix merge

* Set archive count to 0

Co-authored-by: Felipe Santos <felipecassiors@gmail.com>
This commit is contained in:
Nicolas Mowen 2022-12-08 19:15:00 -07:00 committed by GitHub
parent 4f79ca1bf0
commit 964bcc0733
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 81 additions and 2 deletions

View File

@ -180,8 +180,8 @@ ENV S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0
# But this is not working currently because of: # But this is not working currently because of:
# https://github.com/just-containers/s6-overlay/issues/503 # https://github.com/just-containers/s6-overlay/issues/503
ENV S6_SERVICES_GRACETIME=30000 ENV S6_SERVICES_GRACETIME=30000
# Configure logging to prepend timestamps, log to stdout, keep 1 archive and rotate on 10MB # Configure logging to prepend timestamps, log to stdout, keep 0 archives and rotate on 10MB
ENV S6_LOGGING_SCRIPT="T 1 n1 s10000000 T" ENV S6_LOGGING_SCRIPT="T 1 n0 s10000000 T"
# TODO: remove after a new version of s6-overlay is released. See: # TODO: remove after a new version of s6-overlay is released. See:
# https://github.com/just-containers/s6-overlay/issues/460#issuecomment-1327127006 # https://github.com/just-containers/s6-overlay/issues/460#issuecomment-1327127006
ENV S6_SERVICES_READYTIME=50 ENV S6_SERVICES_READYTIME=50

View File

@ -1142,3 +1142,24 @@ def vainfo():
else "", else "",
} }
) )
@bp.route("/logs/<service>", methods=["GET"])
def logs(service: str):
log_locations = {
"frigate": "/dev/shm/logs/frigate/current",
"go2rtc": "/dev/shm/logs/go2rtc/current",
"nginx": "/dev/shm/logs/nginx/current",
}
service_location = log_locations.get(service)
if not service:
return f"{service} is not a valid service", 404
try:
file = open(service_location, "r")
contents = file.read()
file.close()
return contents, 200
except FileNotFoundError as e:
return f"Could not find log file: {e}", 500

View File

@ -48,6 +48,7 @@ export default function Sidebar() {
<Destination href="/storage" text="Storage" /> <Destination href="/storage" text="Storage" />
<Destination href="/system" text="System" /> <Destination href="/system" text="System" />
<Destination href="/config" text="Config" /> <Destination href="/config" text="Config" />
<Destination href="/logs" text="Logs" />
<Separator /> <Separator />
<div className="flex flex-grow" /> <div className="flex flex-grow" />
{ENV !== 'production' ? ( {ENV !== 'production' ? (

View File

@ -38,6 +38,7 @@ export default function App() {
<AsyncRoute path="/storage" getComponent={Routes.getStorage} /> <AsyncRoute path="/storage" getComponent={Routes.getStorage} />
<AsyncRoute path="/system" getComponent={Routes.getSystem} /> <AsyncRoute path="/system" getComponent={Routes.getSystem} />
<AsyncRoute path="/config" getComponent={Routes.getConfig} /> <AsyncRoute path="/config" getComponent={Routes.getConfig} />
<AsyncRoute path="/logs" getComponent={Routes.getLogs} />
<AsyncRoute path="/styleguide" getComponent={Routes.getStyleGuide} /> <AsyncRoute path="/styleguide" getComponent={Routes.getStyleGuide} />
<Cameras default path="/" /> <Cameras default path="/" />
</Router> </Router>

51
web/src/routes/Logs.jsx Normal file
View File

@ -0,0 +1,51 @@
import { h } from 'preact';
import Heading from '../components/Heading';
import { useCallback, useEffect, useState } from 'preact/hooks';
import ButtonsTabbed from '../components/ButtonsTabbed';
import useSWR from 'swr';
import Button from '../components/Button';
export default function Logs() {
const [logService, setLogService] = useState('frigate');
const [logs, setLogs] = useState('frigate');
const { data: frigateLogs } = useSWR('logs/frigate');
const { data: go2rtcLogs } = useSWR('logs/go2rtc');
const { data: nginxLogs } = useSWR('logs/nginx');
const handleCopyLogs = useCallback(() => {
async function copy() {
await window.navigator.clipboard.writeText(logs);
}
copy();
}, [logs]);
useEffect(() => {
switch (logService) {
case 'frigate':
setLogs(frigateLogs);
break;
case 'go2rtc':
setLogs(go2rtcLogs);
break;
case 'nginx':
setLogs(nginxLogs);
break;
}
}, [frigateLogs, go2rtcLogs, nginxLogs, logService, setLogs]);
return (
<div className="space-y-4 p-2 px-4">
<Heading>Logs</Heading>
<ButtonsTabbed viewModes={['frigate', 'go2rtc', 'nginx']} setViewMode={setLogService} />
<div className='overflow-auto font-mono text-sm text-gray-900 dark:text-gray-100 rounded bg-gray-100 dark:bg-gray-800 p-2 whitespace-pre-wrap'>
{logs}
</div>
<Button className="" onClick={handleCopyLogs}>
Copy to Clipboard
</Button>
</div>
);
}

View File

@ -43,6 +43,11 @@ export async function getConfig(_url, _cb, _props) {
return module.default; return module.default;
} }
export async function getLogs(_url, _cb, _props) {
const module = await import('./Logs.jsx');
return module.default;
}
export async function getStyleGuide(_url, _cb, _props) { export async function getStyleGuide(_url, _cb, _props) {
const module = await import('./StyleGuide.jsx'); const module = await import('./StyleGuide.jsx');
return module.default; return module.default;