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]

Reply via email to