fix(logs): cap DOM rendering to 300 rows to prevent OOM crash

Playwright investigation revealed the Logs view (GlobalObservabilityView)
rendered 1,859+ log entries as real DOM nodes (9,616 total DOM nodes) with
no virtualization. Combined with a 5-second polling cycle replacing all
React elements each time and smooth-scroll animations stacking on every
update, the renderer process grew rapidly on a host running at 97% RAM
usage, crashing the browser tab with Out of Memory within minutes.

Fixes:
- Cap rendered DOM rows to MAX_DISPLAY_ROWS (300) via .slice(-300) so the
  browser only ever holds ~1,500 log-related DOM nodes regardless of how
  many entries are in state
- Add a truncation notice when log count exceeds the display cap
- Reduce SSE-mode in-memory log cap from 10,000 to MAX_LOG_ENTRIES (2,000)
- Switch auto-scroll from behavior:'smooth' to behavior:'instant' to stop
  stacking layout animations on every 5-second poll
- Reduce /api/logs/global response limit from 2,000 to 500 lines since the
  client renders at most 300 rows, making the extra payload wasteful
This commit is contained in:
SaelixCode
2026-03-20 11:59:24 -04:00
parent 18314119b0
commit 0db6c946e7
3 changed files with 22 additions and 6 deletions
+4 -2
View File
@@ -1072,9 +1072,11 @@ app.get('/api/logs/global', async (req: Request, res: Response) => {
}
}));
// Sort globally by timestamp ascending (newest bottom) and limit to 2000 lines
// Sort globally by timestamp ascending (newest bottom).
// Limit to 500 lines — the client renders at most 300 rows at once, so
// sending 2000 lines was wasting bandwidth and inflating JSON parse time.
allLogs.sort((a, b) => a.timestampMs - b.timestampMs);
res.json(allLogs.slice(-2000));
res.json(allLogs.slice(-500));
} catch (error) {
res.status(500).json({ error: 'Failed to fetch global logs' });
}