This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch CAMEL-26911-background-log
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 1ab2efa22f0a53cf2a032709f5cc208dc966e372
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Nov 13 13:32:41 2026 +0100

    CAMEL-26911: camel-jbang - an integration in the background no longer 
writes its logging twice: <pid>-run.log gets the warnings and errors, <pid>.log 
all of it
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  5 ++-
 .../modules/ROOT/pages/camel-jbang-debugging.adoc  |  4 +-
 .../modules/ROOT/pages/camel-jbang-managing.adoc   | 13 +++---
 .../apache/camel/dsl/jbang/core/commands/Run.java  | 11 +++++-
 .../core/commands/process/RestartProcess.java      |  2 +-
 .../camel/dsl/jbang/core/common/BackgroundLog.java | 23 +++++++++++
 .../src/main/resources/log4j2-color.properties     |  4 ++
 .../src/main/resources/log4j2-no-color.properties  |  4 ++
 .../dsl/jbang/core/common/BackgroundLogTest.java   | 46 ++++++++++++++++++++++
 9 files changed, 101 insertions(+), 11 deletions(-)

diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 2ab0a298ec81..0e06ea885a53 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -2888,9 +2888,12 @@ or follows, like Enter does.
 * `camel run --background` waits up to 60 seconds for the integration to start 
(it was 20 seconds), and
   `--background-wait` takes how long, such as `--background-wait=2m` (`true` 
and `false` still work). An integration
   that is still starting after the wait exits with `124` and is left running; 
before, the command exited with `0`.
-* The output of an integration in the background goes to 
`~/.camel/<pid>-run.log`, whose path is printed. The log is
+* The output of an integration in the background goes to 
`~/.camel/<pid>-run.log`. The log is
   kept while the integration runs (it was removed once the integration had 
started), and `camel stop` removes it. With
   `--background-wait=false` the output goes to the log as well, instead of a 
pipe that nobody read.
+* The output only gets the warnings and errors of the logging, and what the 
integration prints otherwise (such as why
+  it cannot start): all of the logging is in `~/.camel/<pid>.log`, which 
`camel log` reads. Before, each line was written
+  to both files. The `Log:` line is printed once the integration has started, 
with the path of `~/.camel/<pid>.log`.
 * An integration that loads no routes exits with `1` and `No routes found in: 
<files>`, instead of running without
   doing anything, except with `--source-dir`, `--empty` or in dev mode, where 
files can be added later.
 * `camel run` and `camel dev` with `--max-seconds`, `--max-messages` or 
`--max-idle-seconds` exit at the limit also in
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-debugging.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-debugging.adoc
index cda7c972c642..59ffa79826fb 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-debugging.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-debugging.adoc
@@ -83,8 +83,8 @@ camel debug hello.yaml --background
 
 This is useful in CI or non-interactive sessions where no TTY is available.
 Unlike the interactive mode, `camel debug --background` does not require a 
terminal:
-it launches the Camel integration with the route debugger enabled, writes its 
output to
-`~/.camel/<pid>-run.log`, and waits up to `--background-wait` (60 seconds by 
default) for
+it launches the Camel integration with the route debugger enabled, writes its 
logging to
+`~/.camel/<pid>.log` (and the warnings, errors and other output to 
`~/.camel/<pid>-run.log`), and waits up to `--background-wait` (60 seconds by 
default) for
 the integration to start.
 It exits with `0` once the integration is running, with the integration's exit 
code when it
 fails to start, and with `124` when it is still starting at the end of the 
wait.
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
index 7e3f459c1f27..2152eb5df44d 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
@@ -45,11 +45,12 @@ Restart an integration (stop + re-launch):
 camel restart dude
 Stopping Camel integration: dude (PID: 62506)
 Restarted Camel integration: dude (PID: 63010)
-Log: ~/.camel/restart-62506-1234.log
+Log: ~/.camel/63010.log
 ----
 
-`camel restart` waits until the integration has stopped, and until the new one 
has started, and keeps the output of
-the new integration in the log file it prints. When the new integration stops 
right away, it prints the end of the log
+`camel restart` waits until the integration has stopped, and until the new one 
has started, and prints the log file
+of the new integration (its output, the warnings and errors and what it prints 
otherwise, is in
+`~/.camel/<pid>-run.log`). When the new integration stops right away, it 
prints the end of the log
 and exits with code `1`; when it is still starting after `--timeout`, it exits 
with code `124` and leaves it running.
 
 Stop all integrations (omit the name):
@@ -106,8 +107,8 @@ Use `--background` to run an integration as a background 
process (Camel Main onl
 ----
 $ camel run chuck.yaml --background
 Running Camel Main: chuck in background with PID: 80093 (waiting to startup)
-Log: /home/user/.camel/80093-run.log
 Camel Main: chuck (state: Running)
+Log: /home/user/.camel/80093.log
 ----
 
 Use `camel ps` to list, `camel log` to tail output, and `camel stop` to shut 
down.
@@ -117,7 +118,9 @@ Set how long with `--background-wait`, such as 
`--background-wait=2m`, or do not
 An integration that fails to start prints its log and exits with `1`.
 One that is still starting after the wait exits with `124`, and is left 
running.
 
-The output of the integration goes to `~/.camel/<pid>-run.log`, which `camel 
stop` removes.
+The logging of the integration goes to `~/.camel/<pid>.log`, which `camel log` 
reads.
+Its output goes to `~/.camel/<pid>-run.log`, which `camel stop` removes: the 
warnings and errors of the logging, and
+what the integration prints otherwise, such as why it cannot start.
 
 == Getting status
 
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
index ea79a74c211b..8fe2109fad37 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
@@ -1342,7 +1342,9 @@ public class Run extends CamelCommand implements 
CommandLifecycle {
             printer().println(
                     "Running " + kind + ": " + name + " in background with 
PID: " + p.pid()
                               + (backgroundWait > 0 ? " (waiting to startup)" 
: ""));
-            printer().println("Log: " + logPath);
+            if (backgroundWait <= 0) {
+                printer().println("Log: " + logPath);
+            }
         }
         try {
             if (backgroundWait <= 0) {
@@ -1377,9 +1379,14 @@ public class Run extends CamelCommand implements 
CommandLifecycle {
                 // still starting: it is left running, and the log tells why 
it takes long
                 String waited = backgroundWait < 1000 ? backgroundWait + "ms" 
: TimeUtils.printDuration(backgroundWait);
                 throw CliException.timeout(kind + ": " + name + " (PID: " + 
p.pid() + ") is still starting after "
-                                           + waited + "; the log is in " + 
logPath);
+                                           + waited + "; the log is in "
+                                           + BackgroundLog.logOf(p.pid(), 
logPath));
             }
             printer().println(kind + ": " + name + " (state: " + 
extractState(state) + ")");
+            if (!internal) {
+                // started: its log file has all of its logging, and the 
output only the warnings and errors
+                printer().println("Log: " + BackgroundLog.logOf(p.pid(), 
logPath));
+            }
             return 0;
         } finally {
             if (internal) {
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/RestartProcess.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/RestartProcess.java
index 72f878069158..6f25dd15637d 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/RestartProcess.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/RestartProcess.java
@@ -104,7 +104,7 @@ public class RestartProcess extends ProcessBaseCommand {
 
         awaitStarted(started, displayName, log);
         printer().println("Restarted Camel integration: " + displayName + " 
(PID: " + started.pid() + ")");
-        printer().println("Log: " + log);
+        printer().println("Log: " + BackgroundLog.logOf(started.pid(), log));
         return 0;
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/BackgroundLog.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/BackgroundLog.java
index 67ce19e06625..1535b9c29a3d 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/BackgroundLog.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/BackgroundLog.java
@@ -30,9 +30,19 @@ import java.util.Deque;
  * The output of an integration that runs in the background (camel run 
--background, camel restart): the file
  * <tt>~/.camel/&lt;pid&gt;-run.log</tt>, as nobody reads the output of a 
process in the background. The log stays while
  * the integration runs, and camel stop removes it (camel dirty removes the 
log of an integration that is gone).
+ * <p/>
+ * The logging of the integration goes to its log file 
<tt>~/.camel/&lt;pid&gt;.log</tt> (which camel log reads), so the
+ * output only gets the warnings and errors of the logging (CAMEL-26911), and 
what the integration prints otherwise,
+ * such as why it could not start.
  */
 public final class BackgroundLog {
 
+    /**
+     * The environment variable with the lowest level the logging 
configurations of the CLI print to the console. The
+     * integrations in the background get WARN; a Camel that does not know it 
prints all of its logging, as before.
+     */
+    static final String CONSOLE_LOG_LEVEL = "CAMEL_CLI_CONSOLE_LOG_LEVEL";
+
     private BackgroundLog() {
     }
 
@@ -83,9 +93,22 @@ public final class BackgroundLog {
         Path log = Files.createTempFile(dir, "run-", ".log");
         pb.redirectErrorStream(true);
         pb.redirectOutput(log.toFile());
+        // the logging is in the log file of the integration already, so not 
all of it again in the output
+        pb.environment().put(CONSOLE_LOG_LEVEL, "WARN");
         return log;
     }
 
+    /**
+     * The log to show for an integration that has started: its log file, 
which has all of its logging, or its output
+     * when it has no log file (its logging is off, JSON, or a logging 
configuration of its own).
+     *
+     * @param output the output of the integration
+     */
+    public static Path logOf(long pid, Path output) {
+        Path log = CommandLineHelper.getCamelDir().resolve(pid + ".log");
+        return Files.exists(log) ? log : output;
+    }
+
     /**
      * Names the log after the pid of the process that writes to it.
      *
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/log4j2-color.properties 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/log4j2-color.properties
index cfbef5358e51..3fa9fb8e44cc 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/log4j2-color.properties
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/log4j2-color.properties
@@ -21,6 +21,10 @@ appender.stdout.name = out
 appender.stdout.layout.type = PatternLayout
 # logging style that is similar to spring boot
 appender.stdout.layout.pattern = %style{%d{yyyy-MM-dd HH:mm:ss.SSS}}{Dim} 
%highlight{%5p} %style{%pid}{Magenta} %style{---}{Dim} %style{[%15.15t]}{Dim} 
%style{%-40.40c}{Cyan} %style{:}{Dim} %m%n
+# the console of an integration in the background (its <pid>-run.log) only 
gets the warnings and errors, as the
+# log file has all of it (CAMEL-26911)
+appender.stdout.filter.threshold.type = ThresholdFilter
+appender.stdout.filter.threshold.level = 
${env:CAMEL_CLI_CONSOLE_LOG_LEVEL:-ALL}
 
 # file logger
 appender.file.type = File
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/log4j2-no-color.properties
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/log4j2-no-color.properties
index 9bdf515fd530..940849478e1e 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/log4j2-no-color.properties
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/log4j2-no-color.properties
@@ -21,6 +21,10 @@ appender.stdout.layout.type = PatternLayout
 
 # logging style that is similar to spring boot (no color)
 appender.stdout.layout.pattern = %d{yyyy-MM-dd HH:mm:ss.SSS} %5p %pid --- 
[%15.15t] %-35.35c : %m%n
+# the console of an integration in the background (its <pid>-run.log) only 
gets the warnings and errors, as the
+# log file has all of it (CAMEL-26911)
+appender.stdout.filter.threshold.type = ThresholdFilter
+appender.stdout.filter.threshold.level = 
${env:CAMEL_CLI_CONSOLE_LOG_LEVEL:-ALL}
 
 # file logger
 appender.file.type = File
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/BackgroundLogTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/BackgroundLogTest.java
index 5a2a2db0bde7..9fabe280ba2b 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/BackgroundLogTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/BackgroundLogTest.java
@@ -20,10 +20,18 @@ import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.IntStream;
 import java.util.stream.Stream;
 
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.AbstractConfiguration;
+import org.apache.logging.log4j.core.config.ConfigurationSource;
+import 
org.apache.logging.log4j.core.config.properties.PropertiesConfigurationFactory;
+import org.apache.logging.log4j.core.filter.AbstractFilterable;
+import org.apache.logging.log4j.core.filter.ThresholdFilter;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -75,6 +83,44 @@ class BackgroundLogTest {
         assertThat(named).doesNotExist();
     }
 
+    @Test
+    void processInTheBackgroundPrintsOnlyTheWarningsOfItsLogging() throws 
Exception {
+        ProcessBuilder pb = new ProcessBuilder("java", "-version");
+
+        Path log = BackgroundLog.redirect(pb);
+        Files.deleteIfExists(log);
+
+        // its logging goes to its log file, so the output gets the warnings 
and errors only, not all of it again
+        
assertThat(pb.environment()).containsEntry(BackgroundLog.CONSOLE_LOG_LEVEL, 
"WARN");
+    }
+
+    @Test
+    void consoleOfTheCliPrintsAllOfTheLoggingByDefault() throws Exception {
+        for (String name : List.of("log4j2-color.properties", 
"log4j2-no-color.properties")) {
+            ConfigurationSource source = 
ConfigurationSource.fromResource(name, getClass().getClassLoader());
+            AbstractConfiguration config = (AbstractConfiguration) new 
PropertiesConfigurationFactory()
+                    .getConfiguration(new LoggerContext("test"), source);
+            config.initialize();
+
+            // the level comes from the environment of the process, which 
camel run sets for one in the background only
+            AbstractFilterable console = config.getAppender("out");
+            
assertThat(console.getFilter()).as(name).isInstanceOfSatisfying(ThresholdFilter.class,
+                    filter -> 
assertThat(filter.getLevel()).isEqualTo(Level.ALL));
+        }
+    }
+
+    @Test
+    void logOfAnIntegrationThatHasStarted() throws Exception {
+        Path output = BackgroundLog.of(42);
+        Files.createDirectories(output.getParent());
+        Files.writeString(output, "");
+
+        // its output, until it has a log file, which has all of its logging
+        assertThat(BackgroundLog.logOf(42, output)).isEqualTo(output);
+        Path log = 
Files.writeString(CommandLineHelper.getCamelDir().resolve("42.log"), "");
+        assertThat(BackgroundLog.logOf(42, output)).isEqualTo(log);
+    }
+
     @Test
     void commandThatCannotRunLeavesNoLog() throws Exception {
         ProcessBuilder pb = new 
ProcessBuilder(home.resolve("no-such-command").toString());

Reply via email to