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()));
+ }
+}