This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch CAMEL-26733-dirty-previous in repository https://gitbox.apache.org/repos/asf/camel.git
commit 9b02c130eac2ba642ef3f0fb04ea2aa057b5da45 Author: Claus Ibsen <[email protected]> AuthorDate: Sun Nov 8 11:56:48 2026 +0100 CAMEL-26733: camel-jbang - camel dirty --previous: also clean the logs of previous runs The Camel directory keeps the log of the previous run of each integration name (<name>-previous.log and .json, for camel log --previous). camel dirty --clean leaves them on purpose, but nothing ever removed them: one pair for each name that ever ran. camel dirty now says how many integrations have one, and camel dirty --clean --previous cleans them too. A log named ${sys:pid}.log, which a logging configuration of the CLI writes when the pid property is not set yet, is an orphan file. 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 | 3 +- .../ROOT/pages/camel-jbang-configuration.adoc | 11 +++- .../pages/jbang-commands/camel-jbang-dirty.adoc | 1 + .../META-INF/camel-jbang-commands-metadata.json | 2 +- .../dsl/jbang/core/commands/process/Dirty.java | 70 +++++++++++++++++----- .../dsl/jbang/core/commands/process/DirtyTest.java | 44 ++++++++++++++ .../src/test/resources/golden/cli-help.txt | 9 ++- 7 files changed, 118 insertions(+), 22 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 6e3565a50d9d..0d2788e8ab03 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 @@ -5757,7 +5757,8 @@ When `camel run` ends, its log is no longer deleted. It is kept as `~/.camel/<na integration, with `~/.camel/<name>-previous.json`, which says when and how the run ended. The next run of that name that ends replaces both, and at most the last 10 MB of a log are kept. `camel log <name> --previous` prints the log, like `kubectl logs --previous` in Kubernetes. The log of an integration that was killed is kept the same way, the next time a -previous run is looked up. `camel dirty` does not count these files. +previous run is looked up. `camel dirty --clean` leaves these files, and `camel dirty --clean --previous` cleans them +too. === camel-jbang - the AI tools wait for Camel to start, and read the log of the previous run diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-configuration.adoc b/docs/user-manual/modules/ROOT/pages/camel-jbang-configuration.adoc index 68aee5cfb6dd..090a0c083655 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-jbang-configuration.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-configuration.adoc @@ -143,8 +143,15 @@ camel dirty --clean Only the files named after the pid of a process that is gone count, such as `<pid>-status.json`. Other files in `~/.camel`, such as the log of a Spring Boot or Quarkus app that `camel run` runs (`<name>.log`), are left alone. -The log of the previous run of an integration (`~/.camel/<name>-previous.log`, see `camel log --previous`) is kept on -purpose, so `camel dirty` leaves it. +The log of the previous run of an integration (`~/.camel/<name>-previous.log` and `<name>-previous.json`, see +`camel log --previous`) is kept on purpose, so `camel dirty --clean` leaves it. There is one for each name that ever +ran, and they are kept until you clean them: `camel dirty` says how many integrations have one, and `--previous` +cleans them too: + +[source,bash] +---- +camel dirty --clean --previous +---- An error is printed as one line that starts with `ERROR:`. To see the stack trace of every error, set the environment variable `CAMEL_CLI_DEBUG` to `true`. See xref:camel-jbang-exit-codes.adoc[Exit Codes and Output] diff --git a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dirty.adoc b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dirty.adoc index 71f9e1b42bd0..9bcf94cc8da6 100644 --- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dirty.adoc +++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dirty.adoc @@ -20,6 +20,7 @@ camel dirty [options] |=== | Option | Description | Default | Type | `--clean` | Clean dirty files which are no longer in use | false | boolean +| `--previous` | Also clean the logs of the previous runs of the integrations (camel log --previous), which are kept until then | false | boolean | `-h,--help` | Display the help and sub-commands | | boolean |=== diff --git a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json index ce4287ac149b..666177d1c8b1 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json +++ b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json @@ -9,7 +9,7 @@ { "name": "debug", "fullName": "debug", "description": "Debug local Camel integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Debug", "options": [ { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd HH:mm:ss in timestamp.", "javaType": "boolean", "type": "boolean" }, { "names": "--background", "description": "Run in the background", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--background-wait", "description": "How [...] { "name": "dependency", "fullName": "dependency", "description": "Displays all Camel dependencies required to run", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.DependencyCommand", "options": [ { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "copy", "fullName": "dependency copy", "description": "Copies all Camel dependencies required to run to a specific directory", "sourc [...] { "name": "dev", "fullName": "dev", "description": "Run in dev mode with live reload", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Dev", "options": [ { "names": "--background", "description": "Run in the background", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--background-wait", "description": "How long to wait for the integration in the background to start, before returning, in millis or as a duration such as 2m (true waits 60 secon [...] - { "name": "dirty", "fullName": "dirty", "description": "Check if there are dirty files from previous Camel runs that did not terminate gracefully", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.Dirty", "options": [ { "names": "--clean", "description": "Clean dirty files which are no longer in use", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", " [...] + { "name": "dirty", "fullName": "dirty", "description": "Check if there are dirty files from previous Camel runs that did not terminate gracefully", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.Dirty", "options": [ { "names": "--clean", "description": "Clean dirty files which are no longer in use", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--previous", "description": "Also clean the logs of the previous runs of the integration [...] { "name": "doc", "fullName": "doc", "description": "Shows documentation for kamelet, component, EIP, and other Camel resources", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.catalog.CatalogDoc", "options": [ { "names": "--camel-version", "description": "To use a different Camel version than the default version", "javaType": "java.lang.String", "type": "string" }, { "names": "--download", "description": "Whether to allow automatic downloading JAR dependencies (over the int [...] { "name": "doctor", "fullName": "doctor", "description": "Checks the environment and reports potential issues", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Doctor", "options": [ { "names": "--json", "description": "Output in JSON Format: the status (OK, WARN or FAIL) of each check", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "eval", "fullName": "eval", "description": "Evaluate Camel expressions and scripts", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.EvalCommand", "options": [ { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "expression", "fullName": "eval expression", "description": "Evaluates Camel expression", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.EvalEx [...] diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/Dirty.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/Dirty.java index 29069073f802..4e72bb822be1 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/Dirty.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/Dirty.java @@ -32,13 +32,22 @@ import static org.apache.camel.dsl.jbang.core.common.CommandLineHelper.getCamelD footer = { "%nExamples:", " camel dirty", - " camel dirty --clean" }) + " camel dirty --clean", + " camel dirty --clean --previous" }) public class Dirty extends ProcessBaseCommand { @CommandLine.Option(names = { "--clean" }, defaultValue = "false", description = "Clean dirty files which are no longer in use") boolean clean; + @CommandLine.Option(names = { "--previous" }, defaultValue = "false", + description = "Also clean the logs of the previous runs of the integrations (camel log" + + " --previous), which are kept until then") + boolean previous; + + // the log a log4j configuration of the CLI writes when the pid system property is not set yet + static final String STRAY_LOG = "${sys:pid}.log"; + public Dirty(CamelJBangMain main) { super(main); } @@ -51,9 +60,19 @@ public class Dirty extends ProcessBaseCommand { } List<File> dirty = new ArrayList<>(); + List<File> previousRuns = new ArrayList<>(); for (File f : files) { if (PreviousRun.isPreviousRunFile(f.getName())) { - // kept on purpose: the log of the previous run of an integration (camel log --previous) + // kept on purpose: the log of the previous run of an integration (camel log --previous), one for + // each name that ever ran, until --previous cleans them + if (f.isFile()) { + previousRuns.add(f); + } + continue; + } + if (STRAY_LOG.equals(f.getName())) { + // the log of a logging configuration whose ${sys:pid} was not set, which no process writes again + dirty.add(f); continue; } // only the files of a process that is gone: a file named after its pid. The files of a process that is @@ -65,31 +84,52 @@ public class Dirty extends ProcessBaseCommand { dirty.add(f); } } - if (dirty.isEmpty()) { - return 0; - } - if (clean) { - int cleaned = 0; List<String> left = new ArrayList<>(); - for (File f : dirty) { - if (FileUtil.deleteFile(f)) { - cleaned++; - } else { - left.add(f.getName()); - } + if (!dirty.isEmpty()) { + printer().println("Cleaned " + delete(dirty, left) + " orphan files"); + } + if (previous && !previousRuns.isEmpty()) { + int runs = runs(previousRuns); + int cleaned = delete(previousRuns, left); + printer().println("Cleaned " + cleaned + " files of the previous runs of " + runs + " integrations"); } - printer().println("Cleaned " + cleaned + " orphan files"); if (!left.isEmpty()) { printer().printWarn("Cannot delete: " + String.join(", ", left)); } } else { - printer().println("There are " + dirty.size() + " orphan files"); + if (!dirty.isEmpty()) { + printer().println("There are " + dirty.size() + " orphan files"); + } + if (!previousRuns.isEmpty()) { + printer().println("There are logs of the previous runs of " + runs(previousRuns) + + " integrations (camel dirty --clean --previous cleans them)"); + } } return 0; } + /** Deletes the files, and returns how many; those that cannot be deleted are added to left. */ + private static int delete(List<File> files, List<String> left) { + int cleaned = 0; + for (File f : files) { + if (FileUtil.deleteFile(f)) { + cleaned++; + } else { + left.add(f.getName()); + } + } + return cleaned; + } + + /** How many integrations the files of previous runs are of: a log and what it says about the run, per name. */ + private static int runs(List<File> previousRuns) { + return (int) previousRuns.stream() + .map(f -> f.getName().substring(0, f.getName().lastIndexOf('-'))) + .distinct().count(); + } + /** * The pid a file in the Camel directory belongs to, such as 123 for {@code 123-status.json}, {@code 123} and the * infra service file {@code infra-kafka-123.json}, or <tt>null</tt> for a file that belongs to no process. diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/DirtyTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/DirtyTest.java index 85c2629710de..7bb9e53d68fd 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/DirtyTest.java +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/DirtyTest.java @@ -125,6 +125,50 @@ class DirtyTest extends ProcessCommandTestSupport { } } + @Test + void testPreviousCleansTheLogsOfPreviousRuns() throws Exception { + Path dir = CommandLineHelper.getCamelDir(); + Files.writeString(dir.resolve("orders-previous.log"), "Started"); + Files.writeString(dir.resolve("orders-previous.json"), "{}"); + Files.writeString(dir.resolve("billing-previous.log"), "Started"); + Files.writeString(dir.resolve("billing-previous.json"), "{}"); + + try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) { + // without --clean it says how many there are, and how to clean them + assertEquals(0, new Dirty(new CamelJBangMain().withPrinter(printer)).doCall()); + assertEquals("There are logs of the previous runs of 2 integrations (camel dirty --clean --previous cleans" + + " them)", + printer.getOutput().trim()); + assertTrue(Files.exists(dir.resolve("orders-previous.log"))); + + Dirty command = new Dirty(new CamelJBangMain().withPrinter(printer)); + command.clean = true; + command.previous = true; + assertEquals(0, command.doCall()); + assertTrue(printer.getOutput().contains("Cleaned 4 files of the previous runs of 2 integrations"), + printer.getOutput()); + for (String name : new String[] { + "orders-previous.log", "orders-previous.json", "billing-previous.log", "billing-previous.json" }) { + assertFalse(Files.exists(dir.resolve(name)), name + " is cleaned"); + } + } + } + + @Test + void testTheLogOfAnUnsetPidIsAnOrphan() throws Exception { + Path stray = CommandLineHelper.getCamelDir().resolve("${sys:pid}.log"); + Files.writeString(stray, "WARN"); + + Dirty command = new Dirty(new CamelJBangMain().withPrinter(printer)); + command.clean = true; + + try (MockedStatic<ProcessHandle> mocked = mockStatic(ProcessHandle.class)) { + assertEquals(0, command.doCall()); + assertEquals("Cleaned 1 orphan files", printer.getOutput().trim()); + assertFalse(Files.exists(stray)); + } + } + @Test void testFilesThatBelongToNoProcessAreNotDirty() throws Exception { Path dir = CommandLineHelper.getCamelDir(); diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt b/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt index 51a4c91f3dc1..dc76da08c699 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt +++ b/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt @@ -3016,15 +3016,18 @@ Examples: camel dev --port=8080 == camel dirty --help -Usage: camel dirty [-h] [--clean] +Usage: camel dirty [-h] [--clean] [--previous] Check if there are dirty files from previous Camel runs that did not terminate gracefully - --clean Clean dirty files which are no longer in use - -h, --help Display the help and sub-commands + --clean Clean dirty files which are no longer in use + -h, --help Display the help and sub-commands + --previous Also clean the logs of the previous runs of the integrations + (camel log --previous), which are kept until then Examples: camel dirty camel dirty --clean + camel dirty --clean --previous == camel doc --help Usage: camel doc [-h] [--example] [--fresh] [--header] [--json] [--open-url]
