0079. Node logs start from the tail and the viewer is bounded
Status: Accepted · Date: 2026-10-26 · Design section: CLI and UI
Decided: StreamNodeLogs without a resume token starts from the last tail_lines lines (default 500, cap 10000, negative for the whole history) instead of the beginning of the log; follow=false returns that snapshot and ends; the UI viewer keeps the newest 2000 lines, appends in 100 ms batches, shows at most 1000 characters of a message until expanded, follows the tail only while scrolled to the bottom, can pause, backs off between reconnects and shows stream errors inline.
Context
The proto said "omit resume_token to start from the current tail" but StreamLogs called Talos Logs with tailLines = -1 (everything), so a kubelet stream began with its startup dump and replayed the whole history. The page then did one setLines (copying a 500-element array) and one scrollIntoView per received line, and streamWithResume reconnected on Unavailable with no delay and no limit, so an unreachable node produced a hot request/re-render loop. Opening the logs froze or killed the tab, worst on a phone.
Decision
StreamNodeLogsRequest.tail_lines(0 = 500, above 10000 = 10000, negative = all) andoptional bool follow(unset = true, so existing callers are unchanged).NodeTalosClient.StreamLogstakes aLogOptions{Follow, TailLines}. Service logs pass both to TalosLogs. The kernel log (Dmesg) has only a boolean tail, so a snapshot reads the ring and keeps the lasttail_lineslines in the backend; a followed kernel stream keeps the boolean tail as before.- Resume still replays from the tail window, not from the token's exact position: lines are stamped with the time the backend sees them, so a resumed stream re-sends the window and the token only filters by that stamp. Bounded by the tail, so accepted for now.
- The viewer's numbers (2000 lines, 100 ms, 1000 characters) are constants in
NodeLogs.tsx; 2000 rows of capped text is cheap on a phone. streamWithResumewaits 500 ms doubling to 10 s between reconnects, gives up after 8 in a row with no message, and stops when its signal is aborted.
Consequences
The log viewer cannot grow without bound, and a bad node ends in an error the person can read. The 500-line default means history older than that is not shown; a caller wanting more passes tail_lines.