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/<pid>-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/<pid>.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());
