mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-05 16:37:46 +00:00
fix(frontend): stream log lines on next paint and show ms-precision timestamps (#764)
Two compounding issues made logs feel laggy and timestamps look duplicated: 1. State updates were batched in a 250ms setInterval, so a burst of lines all rendered together every quarter second. Replace with a requestAnimationFrame scheduler: lines flush on the next paint (~16ms at 60Hz) while still collapsing a single burst into one React commit. Cleanup uses cancelAnimationFrame. 2. The timestamp formatter rendered HH:mm:ss only, dropping the sub-second precision that docker logs -t already emits. Two lines logged within the same second appeared identically. Render HH:mm:ss.SSS so successive lines remain visually distinct.
This commit is contained in:
@@ -43,7 +43,8 @@ function formatTs(iso: string | null): string {
|
||||
const hh = String(d.getHours()).padStart(2, '0');
|
||||
const mm = String(d.getMinutes()).padStart(2, '0');
|
||||
const ss = String(d.getSeconds()).padStart(2, '0');
|
||||
return `${hh}:${mm}:${ss}`;
|
||||
const ms = String(d.getMilliseconds()).padStart(3, '0');
|
||||
return `${hh}:${mm}:${ss}.${ms}`;
|
||||
}
|
||||
|
||||
export default function StructuredLogViewer({ stackName }: StructuredLogViewerProps) {
|
||||
@@ -68,6 +69,22 @@ export default function StructuredLogViewer({ stackName }: StructuredLogViewerPr
|
||||
const ws = new WebSocket(wsUrl);
|
||||
wsRef.current = ws;
|
||||
|
||||
let rafId = 0;
|
||||
const flushPending = () => {
|
||||
rafId = 0;
|
||||
if (pendingRef.current.length === 0) return;
|
||||
const incoming = pendingRef.current;
|
||||
pendingRef.current = [];
|
||||
setRows((prev) => {
|
||||
const merged = prev.concat(incoming);
|
||||
return merged.length > BUFFER_CAP ? merged.slice(merged.length - BUFFER_CAP) : merged;
|
||||
});
|
||||
};
|
||||
const scheduleFlush = () => {
|
||||
if (rafId !== 0) return;
|
||||
rafId = requestAnimationFrame(flushPending);
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
if (closed) return;
|
||||
const text = typeof event.data === 'string' ? event.data : '';
|
||||
@@ -79,24 +96,14 @@ export default function StructuredLogViewer({ stackName }: StructuredLogViewerPr
|
||||
rowIdRef.current += 1;
|
||||
pendingRef.current.push({ id: rowIdRef.current, ...parsed });
|
||||
}
|
||||
scheduleFlush();
|
||||
};
|
||||
|
||||
ws.onerror = () => { /* surface nothing; reconnection is backend's job */ };
|
||||
|
||||
// Batch incoming lines into state every 250 ms to avoid thrashing React.
|
||||
const flushInterval = window.setInterval(() => {
|
||||
if (pendingRef.current.length === 0) return;
|
||||
const incoming = pendingRef.current;
|
||||
pendingRef.current = [];
|
||||
setRows((prev) => {
|
||||
const merged = prev.concat(incoming);
|
||||
return merged.length > BUFFER_CAP ? merged.slice(merged.length - BUFFER_CAP) : merged;
|
||||
});
|
||||
}, 250);
|
||||
|
||||
return () => {
|
||||
closed = true;
|
||||
window.clearInterval(flushInterval);
|
||||
if (rafId !== 0) cancelAnimationFrame(rafId);
|
||||
try { ws.close(); } catch { /* ignore */ }
|
||||
wsRef.current = null;
|
||||
pendingRef.current = [];
|
||||
|
||||
Reference in New Issue
Block a user