This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new ea84f1b19075 chore: camel-jbang - the Log tab shows the newest lines
at once after a while on another tab (#29974)
ea84f1b19075 is described below
commit ea84f1b19075b82c6a7625ee6823d94a3270afc2
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Nov 14 21:57:12 2026 +0100
chore: camel-jbang - the Log tab shows the newest lines at once after a
while on another tab (#29974)
The log is read while the Log tab is on screen (or the log is pinned),
at most 64KB at a refresh. After a while on another tab, such as browsing
the Diagram, a busy log had grown well beyond that, and the tab played
the lines it had missed for many seconds while it said it followed; the
same on Space after a pause on another tab. Following, a log more than a
read behind now starts again at its end, as when the tab opens; Home reads
the lines it skipped. Paused, it reads on from where it was.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../camel/dsl/jbang/core/commands/tui/LogTab.java | 42 ++++++++++++---
.../jbang/core/commands/tui/LogTabTailTest.java | 60 ++++++++++++++++++++++
2 files changed, 95 insertions(+), 7 deletions(-)
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
index fb01ffa8c93c..6365fecaaf0c 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
@@ -82,6 +82,8 @@ class LogTab extends AbstractTab {
private volatile boolean loadAllRequested;
/** How much of the end of a log the tab reads when it opens it. */
static final int INITIAL_TAIL_BYTES = 32 * 1024;
+ /** The most of a log read at a refresh, so a refresh does not block on a
log that grew a lot. */
+ static final int MAX_READ_BYTES = 64 * 1024;
private long logFilePos = -1;
private long logFileStartPos = -1;
@@ -642,6 +644,11 @@ class LogTab extends AbstractTab {
frame.renderStatefulWidget(list, popup, logLevelListState);
}
+ /** The entries the tab shows (tests). */
+ List<LogEntry> entriesForTesting() {
+ return filteredLogEntries;
+ }
+
/** The entries of the tab, as read from the log file (tests). */
void setEntriesForTesting(List<LogEntry> entries) {
mutableFilteredEntries.clear();
@@ -680,6 +687,10 @@ class LogTab extends AbstractTab {
}
void refreshFromFile(String pid, String fileName) {
+ refreshFromFile(pid,
CommandLineHelper.getCamelDir().resolve(fileName));
+ }
+
+ void refreshFromFile(String pid, Path logFile) {
if (!pid.equals(logFilePid)) {
mutableFilteredEntries.clear();
logFilePos = -1;
@@ -688,13 +699,25 @@ class LogTab extends AbstractTab {
logLoading = true;
}
boolean changed = false;
+ if (followMode && pid.equals(logFilePid) && logFilePos >= 0 &&
unread(logFile) > MAX_READ_BYTES) {
+ // far behind, as after a while on another tab (the log is read
while it is on screen): it starts again at
+ // the end of the log, as when the tab opens, rather than play the
lines it missed while it says it follows;
+ // Home reads them
+ mutableFilteredEntries.clear();
+ logFilePos = -1;
+ logFileStartPos = -1;
+ logTotalLinesRead = 0;
+ evictedSeen = 0;
+ logLineBuffer.setLength(0);
+ changed = true;
+ }
int olderRead = 0;
boolean loadAll = loadAllRequested;
if (logFileStartPos > 0
&& (loadAll || (!followMode && scroll == 0))) {
loadAllRequested = false;
List<String> olderLines = new ArrayList<>();
- readOlderLogLines(fileName, loadAll, olderLines);
+ readOlderLogLines(logFile, loadAll, olderLines);
if (!olderLines.isEmpty()) {
changed = true;
List<LogEntry> olderEntries = new ArrayList<>();
@@ -707,7 +730,7 @@ class LogTab extends AbstractTab {
}
}
List<String> newRawLines = new ArrayList<>();
- readNewLogLinesFromFile(pid, fileName, newRawLines);
+ readNewLogLines(pid, logFile, newRawLines);
changed |= !newRawLines.isEmpty();
if (changed) {
logTotalLinesRead += newRawLines.size();
@@ -729,6 +752,15 @@ class LogTab extends AbstractTab {
logLoading = false;
}
+ /** How much of the log is written beyond what the tab read. */
+ private long unread(Path logFile) {
+ try {
+ return Files.size(logFile) - logFilePos;
+ } catch (IOException e) {
+ return 0;
+ }
+ }
+
void readNewLogLines(String pid, List<String> newLines) {
readNewLogLinesFromFile(pid, pid + ".log", newLines);
}
@@ -758,7 +790,7 @@ class LogTab extends AbstractTab {
}
raf.seek(logFilePos);
// Cap per-tick read to 64KB to avoid blocking the refresh cycle
- byte[] buf = new byte[(int) Math.min(length - logFilePos, 64 *
1024)];
+ byte[] buf = new byte[(int) Math.min(length - logFilePos,
MAX_READ_BYTES)];
raf.readFully(buf);
int from = 0;
if (skipPartialLine) {
@@ -802,10 +834,6 @@ class LogTab extends AbstractTab {
return -1;
}
- void readOlderLogLines(String fileName, boolean loadAll, List<String>
olderLines) {
- readOlderLogLines(CommandLineHelper.getCamelDir().resolve(fileName),
loadAll, olderLines);
- }
-
void readOlderLogLines(Path logFile, boolean loadAll, List<String>
olderLines) {
if (logFilePid == null || logFileStartPos <= 0) {
return;
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTabTailTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTabTailTest.java
index bd030d623ae3..30398bcb0282 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTabTailTest.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTabTailTest.java
@@ -19,10 +19,13 @@ package org.apache.camel.dsl.jbang.core.commands.tui;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
+import dev.tamboui.tui.event.KeyEvent;
+import dev.tamboui.tui.event.KeyModifiers;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
@@ -66,6 +69,63 @@ class LogTabTailTest {
assertThat(all).isEqualTo(written);
}
+ @Test
+ void
followingALogThatGrewALotWhileTheTabWasHiddenShowsItsNewestLinesAtOnce() throws
Exception {
+ Path log = dir.resolve("1234.log");
+ List<String> written = new ArrayList<>();
+ append(log, written, 100);
+ LogTab tab = newTab();
+ tab.refreshFromFile("1234", log);
+
+ // another tab was shown: the log was not read while it grew
+ append(log, written, 4 * LogTab.MAX_READ_BYTES / 100);
+ tab.refreshFromFile("1234", log);
+
+ List<LogEntry> entries = tab.entriesForTesting();
+ assertThat(entries.get(entries.size() - 1).raw).as("the newest line,
not one read since long ago")
+ .isEqualTo(written.get(written.size() - 1));
+
+ // the lines it skipped are still there for Home
+ List<String> older = new ArrayList<>();
+ tab.readOlderLogLines(log, true, older);
+ List<String> all = new ArrayList<>(older);
+ entries.forEach(e -> all.add(e.raw));
+ assertThat(all).isEqualTo(written);
+ }
+
+ @Test
+ void pausedTheLogIsReadOnFromWhereItWas() throws Exception {
+ Path log = dir.resolve("1234.log");
+ List<String> written = new ArrayList<>();
+ append(log, written, 100);
+ LogTab tab = newTab();
+ tab.refreshFromFile("1234", log);
+ tab.handleKeyEvent(KeyEvent.ofChar(' ', KeyModifiers.NONE));
+
+ append(log, written, 4 * LogTab.MAX_READ_BYTES / 100);
+ tab.refreshFromFile("1234", log);
+
+ List<LogEntry> entries = tab.entriesForTesting();
+ assertThat(entries.get(0).raw).as("the lines on screen
stay").isEqualTo(written.get(0));
+ assertThat(entries.size()).as("read on a part at a
time").isLessThan(written.size());
+ }
+
+ private static LogTab newTab() {
+ return new LogTab(new MonitorContext(new AtomicReference<>(List.of()),
new AtomicReference<>(List.of())));
+ }
+
+ /** Appends about {@code count} lines of a hundred bytes each. */
+ private static void append(Path log, List<String> written, int count)
throws Exception {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < count; i++) {
+ String line = "2026-11-14 20:08:17.646 WARN 718778 ---
[timer://orders] TimerConsumer : Error processing "
+ + written.size();
+ written.add(line);
+ sb.append(line).append('\n');
+ }
+ Files.writeString(log, sb, StandardCharsets.UTF_8,
StandardOpenOption.CREATE, StandardOpenOption.APPEND);
+ }
+
@Test
void aShortLogIsReadWhole() throws Exception {
Path log = dir.resolve("1234.log");