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

bamaer pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new e8abba4842 Issue #4296 : Add hop-run option to append to the log file 
(#8689)
e8abba4842 is described below

commit e8abba48425075cc482e96be287769e6f9f29dad
Author: Matt Casters <[email protected]>
AuthorDate: Thu Oct 1 09:13:48 2026 +0200

    Issue #4296 : Add hop-run option to append to the log file (#8689)
---
 .../modules/ROOT/pages/hop-run/index.adoc          |  12 ++-
 .../scheduling-workflows-and-pipelines.adoc        |   1 +
 .../main/java/org/apache/hop/run/HopRunBase.java   |  22 +++-
 .../apache/hop/run/HopRunLogFileOptionTest.java    | 117 +++++++++++++++++++++
 4 files changed, 148 insertions(+), 4 deletions(-)

diff --git a/docs/hop-user-manual/modules/ROOT/pages/hop-run/index.adoc 
b/docs/hop-user-manual/modules/ROOT/pages/hop-run/index.adoc
index b68acf41a5..5e64573934 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/hop-run/index.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/hop-run/index.adoc
@@ -63,7 +63,11 @@ Usage: <main class> [-ho] [-e=<environmentOption>] 
[-f=<filename>]
                           The name of the project to use
   -l, --level=<level>     The debug level, one of NOTHING, ERROR, MINIMAL, 
BASIC, DETAILED, DEBUG, ROWLEVEL
   -l, --level=<level>     The debug level, one of NOTHING, ERROR, MINIMAL, 
BASIC, DETAILED, DEBUG, ROWLEVEL
-  -lf, --logfile=<logfile-name> The complete filename where hop-run will write 
the Hop console log
+  -lf, --logfile=<logfile-name> The complete filename where hop-run will write 
the Hop console log.
+                          An existing file is replaced unless --logfile-append 
is set
+  -lfa, --logfile-append  Append to the file given by --logfile instead of 
replacing it.
+                          Without this option an existing log file is 
overwritten.
+                          Ignored when --logfile is not set
   -m, --metadata-export=<metadataExportFile>
                           A file containing exported metadata in JSON format
   -o, --printoptions      Print the used options
@@ -119,7 +123,11 @@ Not needed when `-e` is set.
 
 |```-lf```
 |```--logfile```
-|The complete filename where hop-run will write the Hop console log
+|The complete filename where hop-run will write the Hop console log. An 
existing file is replaced unless `--logfile-append` is also set.
+
+|```-lfa```
+|```--logfile-append```
+|Append to the file given by `--logfile` instead of replacing it. Without this 
option an existing log file is overwritten. Ignored when `--logfile` is not 
set. Use this when several executions should share one file, for example one 
log per day.
 
 |```-m```
 |```--metadata-export```
diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/how-to-guides/scheduling-workflows-and-pipelines.adoc
 
b/docs/hop-user-manual/modules/ROOT/pages/how-to-guides/scheduling-workflows-and-pipelines.adoc
index 8f2faa1763..2e74791757 100644
--- 
a/docs/hop-user-manual/modules/ROOT/pages/how-to-guides/scheduling-workflows-and-pipelines.adoc
+++ 
b/docs/hop-user-manual/modules/ROOT/pages/how-to-guides/scheduling-workflows-and-pipelines.adoc
@@ -160,6 +160,7 @@ LOG=/var/log/hop/nightly-$(date +%Y%m%d-%H%M%S).log
 
 `chmod +x /usr/local/bin/run-hop-nightly.sh` and run it once by hand before 
you schedule it.
 `--logfile` (`-lf`) *overwrites* the file, which is why the name includes a 
timestamp.
+Pass `--logfile-append` (`-lfa`) as well when several runs should share one 
file, for example one log per day.
 It adds a file listener *alongside* the console logger, so hop-run still 
writes to stdout unless you redirect it as above.
 cron mails on any output, not on a non-zero exit, and without that redirect a 
successful run would mail the whole log.
 
diff --git a/engine/src/main/java/org/apache/hop/run/HopRunBase.java 
b/engine/src/main/java/org/apache/hop/run/HopRunBase.java
index ef4649a381..dfcf8d22be 100644
--- a/engine/src/main/java/org/apache/hop/run/HopRunBase.java
+++ b/engine/src/main/java/org/apache/hop/run/HopRunBase.java
@@ -96,9 +96,19 @@ public abstract class HopRunBase implements Runnable, 
IHasHopMetadataProvider {
 
   @CommandLine.Option(
       names = {"-lf", "--logfile"},
-      description = "The complete filename where hop-run will write the Hop 
console log")
+      description =
+          "The complete filename where hop-run will write the Hop console log. 
"
+              + "An existing file is replaced unless --logfile-append is set")
   protected String logFile;
 
+  @CommandLine.Option(
+      names = {"-lfa", "--logfile-append"},
+      description =
+          "Append to the file given by --logfile instead of replacing it. "
+              + "Without this option an existing log file is overwritten. "
+              + "Ignored when --logfile is not set")
+  protected boolean appendLogFile = false;
+
   @CommandLine.Option(
       names = {"-p", "--parameters"},
       description =
@@ -209,7 +219,7 @@ public abstract class HopRunBase implements Runnable, 
IHasHopMetadataProvider {
       }
 
       if (!Utils.isEmpty(logFile)) {
-        fileLoggingEventListener = new FileLoggingEventListener(logFile, 
false);
+        fileLoggingEventListener = createFileLoggingEventListener();
         
HopLogStore.getAppender().addLoggingEventListener(fileLoggingEventListener);
       }
 
@@ -679,6 +689,14 @@ public abstract class HopRunBase implements Runnable, 
IHasHopMetadataProvider {
     }
   }
 
+  /**
+   * Opens the hop-run console log. An existing file is replaced unless {@link 
#appendLogFile} was
+   * set with {@code --logfile-append}.
+   */
+  protected FileLoggingEventListener createFileLoggingEventListener() throws 
HopException {
+    return new FileLoggingEventListener(logFile, appendLogFile);
+  }
+
   protected void printOptions(IExecutionConfiguration configuration) {
     if (StringUtils.isNotEmpty(realFilename)) {
       log.logMinimal("OPTION: filename : '" + realFilename + "'");
diff --git 
a/engine/src/test/java/org/apache/hop/run/HopRunLogFileOptionTest.java 
b/engine/src/test/java/org/apache/hop/run/HopRunLogFileOptionTest.java
new file mode 100644
index 0000000000..21c963be82
--- /dev/null
+++ b/engine/src/test/java/org/apache/hop/run/HopRunLogFileOptionTest.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hop.run;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.apache.hop.core.logging.FileLoggingEventListener;
+import org.apache.hop.core.logging.HopLoggingEvent;
+import org.apache.hop.core.logging.LogLevel;
+import org.apache.hop.core.logging.LogMessage;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import picocli.CommandLine;
+
+class HopRunLogFileOptionTest {
+
+  private static final String EARLIER_RUN = "earlier-hop-run";
+  private static final String SECOND_RUN = "second-hop-run";
+
+  @TempDir Path tempDir;
+
+  @Test
+  void logfileReplacesExistingContentByDefault() throws Exception {
+    Path logFile = tempDir.resolve("replace.log");
+    Files.writeString(logFile, EARLIER_RUN + System.lineSeparator());
+
+    HopRun hopRun = parse("-lf", logFile.toString());
+
+    assertFalse(hopRun.isAppendLogFile());
+    String content = writeSecondRun(hopRun);
+    assertFalse(content.contains(EARLIER_RUN), content);
+    assertTrue(content.contains(SECOND_RUN), content);
+  }
+
+  @Test
+  void logfileAppendKeepsExistingContent() throws Exception {
+    Path logFile = tempDir.resolve("append.log");
+    Files.writeString(logFile, EARLIER_RUN + System.lineSeparator());
+
+    HopRun hopRun = parse("--logfile", logFile.toString(), "--logfile-append");
+
+    assertTrue(hopRun.isAppendLogFile());
+    String content = writeSecondRun(hopRun);
+    assertTrue(content.contains(EARLIER_RUN), content);
+    assertTrue(content.contains(SECOND_RUN), content);
+    assertTrue(content.indexOf(EARLIER_RUN) < content.indexOf(SECOND_RUN), 
content);
+  }
+
+  @Test
+  void shortAppendOptionIsNotALogLevel() {
+    HopRun hopRun = parse("-l", "BASIC", "-lf", "run.log", "-lfa");
+
+    assertEquals("BASIC", hopRun.getLevel());
+    assertEquals("run.log", hopRun.getLogFile());
+    assertTrue(hopRun.isAppendLogFile());
+  }
+
+  @Test
+  void appendWithoutLogfileStaysUnset() {
+    HopRun hopRun = parse("--logfile-append");
+
+    assertTrue(hopRun.isAppendLogFile());
+    assertTrue(hopRun.getLogFile() == null || hopRun.getLogFile().isEmpty());
+  }
+
+  @Test
+  void helpDocumentsAppendOption() {
+    StringWriter writer = new StringWriter();
+    new CommandLine(new HopRun()).usage(new PrintWriter(writer, true));
+    String usage = writer.toString().replaceAll("\\s+", " ");
+
+    assertTrue(usage.contains("-lfa, --logfile-append"), usage);
+    assertTrue(usage.contains("instead of replacing"), usage);
+  }
+
+  private static HopRun parse(String... args) {
+    HopRun hopRun = new HopRun();
+    new CommandLine(hopRun).parseArgs(args);
+    return hopRun;
+  }
+
+  private static String writeSecondRun(HopRun hopRun) throws Exception {
+    FileLoggingEventListener listener = 
hopRun.createFileLoggingEventListener();
+    try {
+      listener.eventAdded(
+          new HopLoggingEvent(
+              new LogMessage(SECOND_RUN, "channel", LogLevel.BASIC),
+              System.currentTimeMillis(),
+              LogLevel.BASIC));
+      assertNull(listener.getException());
+    } finally {
+      listener.close();
+    }
+    return Files.readString(Path.of(hopRun.getLogFile()));
+  }
+}

Reply via email to