This is an automated email from the ASF dual-hosted git repository.
hansva 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 db51796d70 fix custom logging in pipelines and workflows, fixes #2621
(#7836)
db51796d70 is described below
commit db51796d70fae3e43e8429d7231e177741d87943
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Fri Aug 7 20:34:09 2026 +0200
fix custom logging in pipelines and workflows, fixes #2621 (#7836)
---
.../hop/debug/action/ActionDebugGuiPlugin.java | 10 +-
.../action/EditActionDebugLevelExtensionPoint.java | 3 +-
.../action/ModifyActionLogLevelExtensionPoint.java | 156 +++++++++++------
.../SetTransformDebugLevelExtensionPoint.java | 90 +++-------
.../debug/transform/TransformDebugGuiPlugin.java | 18 +-
.../transform/TransformDebugLevelRowListener.java | 125 +++++++++++++
.../org/apache/hop/debug/util/DebugLevelUtil.java | 68 +++++++
.../ModifyActionLogLevelExtensionPointTest.java | 131 ++++++++++++++
.../SetTransformDebugLevelExtensionPointTest.java | 195 +++++++++++++++++++++
.../TransformDebugLevelRowListenerTest.java | 188 ++++++++++++++++++++
.../apache/hop/debug/util/DebugLevelUtilTest.java | 23 +++
.../transforms/addsequence/AddSequence.java | 8 +-
.../pipeline/transforms/calculator/Calculator.java | 3 +-
.../transforms/columnexists/ColumnExists.java | 2 +-
.../hop/pipeline/transforms/constant/Constant.java | 2 +-
.../pipeline/transforms/constant/ConstantTest.java | 34 ++++
.../creditcardvalidator/CreditCardValidator.java | 2 +-
.../transforms/databaselookup/DatabaseLookup.java | 2 +-
.../transforms/execprocess/ExecProcess.java | 2 +-
.../fieldschangesequence/FieldsChangeSequence.java | 2 +-
.../pipeline/transforms/fileexists/FileExists.java | 2 +-
.../pipeline/transforms/filelocked/FileLocked.java | 7 +-
.../hop/pipeline/transforms/formula/Formula.java | 6 +-
.../pipeline/transforms/samplerows/SampleRows.java | 2 +-
.../transforms/tableexists/TableExists.java | 2 +-
.../webserviceavailable/WebServiceAvailable.java | 6 +-
26 files changed, 928 insertions(+), 161 deletions(-)
diff --git
a/plugins/misc/debug/src/main/java/org/apache/hop/debug/action/ActionDebugGuiPlugin.java
b/plugins/misc/debug/src/main/java/org/apache/hop/debug/action/ActionDebugGuiPlugin.java
index a90bc7fe80..f7fb7809ad 100644
---
a/plugins/misc/debug/src/main/java/org/apache/hop/debug/action/ActionDebugGuiPlugin.java
+++
b/plugins/misc/debug/src/main/java/org/apache/hop/debug/action/ActionDebugGuiPlugin.java
@@ -17,7 +17,6 @@
package org.apache.hop.debug.action;
-import java.util.HashMap;
import java.util.Map;
import org.apache.hop.core.action.GuiContextAction;
import org.apache.hop.core.gui.plugin.GuiPlugin;
@@ -70,13 +69,8 @@ public class ActionDebugGuiPlugin {
ActionMeta action = context.getActionMeta();
IVariables variables = context.getWorkflowGraph().getVariables();
- Map<String, Map<String, String>> attributesMap =
workflowMeta.getAttributesMap();
- Map<String, String> debugGroupAttributesMap =
attributesMap.get(Defaults.DEBUG_GROUP);
-
- if (debugGroupAttributesMap == null) {
- debugGroupAttributesMap = new HashMap<>();
- attributesMap.put(Defaults.DEBUG_GROUP, debugGroupAttributesMap);
- }
+ Map<String, String> debugGroupAttributesMap =
+
DebugLevelUtil.getOrCreateDebugGroup(workflowMeta.getAttributesMap());
ActionDebugLevel debugLevel =
DebugLevelUtil.getActionDebugLevel(debugGroupAttributesMap,
action.toString());
diff --git
a/plugins/misc/debug/src/main/java/org/apache/hop/debug/action/EditActionDebugLevelExtensionPoint.java
b/plugins/misc/debug/src/main/java/org/apache/hop/debug/action/EditActionDebugLevelExtensionPoint.java
index ccf644d451..1f3ac3f797 100644
---
a/plugins/misc/debug/src/main/java/org/apache/hop/debug/action/EditActionDebugLevelExtensionPoint.java
+++
b/plugins/misc/debug/src/main/java/org/apache/hop/debug/action/EditActionDebugLevelExtensionPoint.java
@@ -23,7 +23,6 @@ import org.apache.hop.core.extension.IExtensionPoint;
import org.apache.hop.core.logging.ILogChannel;
import org.apache.hop.core.variables.IVariables;
import org.apache.hop.debug.util.DebugLevelUtil;
-import org.apache.hop.debug.util.Defaults;
import org.apache.hop.ui.core.dialog.ErrorDialog;
import org.apache.hop.ui.hopgui.HopGui;
import
org.apache.hop.ui.hopgui.file.workflow.extension.HopGuiWorkflowGraphExtension;
@@ -54,7 +53,7 @@ public class EditActionDebugLevelExtensionPoint
ActionMeta actionCopy = (ActionMeta) ext.getAreaOwner().getParent();
DebugLevelUtil.storeActionDebugLevel(
- workflowMeta.getAttributesMap().get(Defaults.DEBUG_GROUP),
+
DebugLevelUtil.getOrCreateDebugGroup(workflowMeta.getAttributesMap()),
actionCopy.getName(),
debugLevel);
ext.getWorkflowGraph().redraw();
diff --git
a/plugins/misc/debug/src/main/java/org/apache/hop/debug/action/ModifyActionLogLevelExtensionPoint.java
b/plugins/misc/debug/src/main/java/org/apache/hop/debug/action/ModifyActionLogLevelExtensionPoint.java
index 483b57d4e6..87c372c24b 100644
---
a/plugins/misc/debug/src/main/java/org/apache/hop/debug/action/ModifyActionLogLevelExtensionPoint.java
+++
b/plugins/misc/debug/src/main/java/org/apache/hop/debug/action/ModifyActionLogLevelExtensionPoint.java
@@ -173,64 +173,14 @@ public class ModifyActionLogLevelExtensionPoint
final ActionDebugLevel debugLevel =
DebugLevelUtil.getActionDebugLevel(entryLevelMap,
actionCopy.toString());
if (debugLevel != null) {
- // Set the debug level for this one...
- //
- log.setLogLevel(jobLogLevel);
- workflow.setLogLevel(jobLogLevel);
-
- // Set the debug level back to normal...
- //
-
- if (debugLevel.isLoggingResult()) {
- log.logMinimal("Action results: ");
- log.logMinimal(" - result=" + result.isResult());
- log.logMinimal(" - stopped=" + result.isStopped());
- log.logMinimal(" - linesRead=" +
result.getNrLinesRead());
- log.logMinimal(" - linesWritten=" +
result.getNrLinesWritten());
- log.logMinimal(" - linesInput=" +
result.getNrLinesInput());
- log.logMinimal(" - linesOutput=" +
result.getNrLinesOutput());
- log.logMinimal(" - linesRejected=" +
result.getNrLinesRejected());
- log.logMinimal(" - result row count=" +
result.getRows().size());
- log.logMinimal(
- " - result files count=" +
result.getResultFilesList().size());
- }
- if (debugLevel.isLoggingResultRows()) {
- log.logMinimal("Action result rows: ");
- for (RowMetaAndData rmad : result.getRows()) {
- log.logMinimal(" - " + rmad.toString());
- }
- }
- if (debugLevel.isLoggingResultFiles()) {
- log.logMinimal("Action result files: ");
- for (ResultFile resultFile :
result.getResultFilesList()) {
- log.logMinimal(
- " - "
- + resultFile.getFile().toString()
- + " from "
- + resultFile.getOrigin()
- + " : "
- + resultFile.getComment()
- + " / "
- + resultFile.getTypeCode());
- }
- }
- if (debugLevel.isLoggingVariables() && action instanceof
IVariables) {
- log.logMinimal("Action notable variables: ");
-
- IVariables variables = (IVariables) action;
- // See the variables set differently from the parent
workflow
- for (String var : variables.getVariableNames()) {
- if (!variablesToIgnore.contains(var)) {
- String value = variables.getVariable(var);
- String refValue = referenceSpace.getVariable(var);
-
- if (refValue == null || !refValue.equals(value)) {
- // Something different!
- //
- log.logMinimal(" - " + var + "=" + value);
- }
- }
- }
+ try {
+ logRequestedInformation(
+ log, debugLevel, action, result, referenceSpace,
variablesToIgnore);
+ } finally {
+ // Set the debug level back to normal...
+ //
+ log.setLogLevel(jobLogLevel);
+ workflow.setLogLevel(jobLogLevel);
}
}
}
@@ -243,4 +193,94 @@ public class ModifyActionLogLevelExtensionPoint
jobLog.logError("Unable to handle specific debug level for workflow", e);
}
}
+
+ /**
+ * Log the extra information that was asked for on this action: its result,
its result rows, its
+ * result files and the variables it changed.
+ *
+ * <p>This has to happen before the log level of the action is put back to
the level of the
+ * workflow. Doing it the other way around silently swallowed all of it
whenever the workflow
+ * itself ran at a level that hides these messages, which is exactly the
case in which someone
+ * sets a custom log level on a single action.
+ *
+ * <p>Everything here is logged with {@link ILogChannel#logMinimal(String)},
so the level is
+ * raised to at least {@link LogLevel#MINIMAL} first: the user explicitly
ticked these options, so
+ * a workflow or custom log level of "Nothing" or "Error" should not hide
them either.
+ *
+ * @param log the log channel of the action
+ * @param debugLevel the custom logging configuration of the action
+ * @param action the action that was executed
+ * @param result the result of the action
+ * @param referenceSpace the variables as they were at the start of the root
workflow
+ * @param variablesToIgnore the variables that are never worth reporting
+ */
+ private static void logRequestedInformation(
+ ILogChannel log,
+ ActionDebugLevel debugLevel,
+ IAction action,
+ Result result,
+ IVariables referenceSpace,
+ Set<String> variablesToIgnore) {
+
+ if (!debugLevel.isLoggingResult()
+ && !debugLevel.isLoggingResultRows()
+ && !debugLevel.isLoggingResultFiles()
+ && !debugLevel.isLoggingVariables()) {
+ return;
+ }
+
+ if (!LogLevel.MINIMAL.isVisible(log.getLogLevel())) {
+ log.setLogLevel(LogLevel.MINIMAL);
+ }
+
+ if (debugLevel.isLoggingResult()) {
+ log.logMinimal("Action results: ");
+ log.logMinimal(" - result=" + result.isResult());
+ log.logMinimal(" - stopped=" + result.isStopped());
+ log.logMinimal(" - linesRead=" + result.getNrLinesRead());
+ log.logMinimal(" - linesWritten=" + result.getNrLinesWritten());
+ log.logMinimal(" - linesInput=" + result.getNrLinesInput());
+ log.logMinimal(" - linesOutput=" + result.getNrLinesOutput());
+ log.logMinimal(" - linesRejected=" + result.getNrLinesRejected());
+ log.logMinimal(" - result row count=" + result.getRows().size());
+ log.logMinimal(" - result files count=" +
result.getResultFilesList().size());
+ }
+ if (debugLevel.isLoggingResultRows()) {
+ log.logMinimal("Action result rows: ");
+ for (RowMetaAndData rmad : result.getRows()) {
+ log.logMinimal(" - " + rmad.toString());
+ }
+ }
+ if (debugLevel.isLoggingResultFiles()) {
+ log.logMinimal("Action result files: ");
+ for (ResultFile resultFile : result.getResultFilesList()) {
+ log.logMinimal(
+ " - "
+ + resultFile.getFile().toString()
+ + " from "
+ + resultFile.getOrigin()
+ + " : "
+ + resultFile.getComment()
+ + " / "
+ + resultFile.getTypeCode());
+ }
+ }
+ if (debugLevel.isLoggingVariables() && action instanceof IVariables
actionVariables) {
+ log.logMinimal("Action notable variables: ");
+
+ // See the variables set differently from the parent workflow
+ for (String var : actionVariables.getVariableNames()) {
+ if (!variablesToIgnore.contains(var)) {
+ String value = actionVariables.getVariable(var);
+ String refValue = referenceSpace.getVariable(var);
+
+ if (refValue == null || !refValue.equals(value)) {
+ // Something different!
+ //
+ log.logMinimal(" - " + var + "=" + value);
+ }
+ }
+ }
+ }
+ }
}
diff --git
a/plugins/misc/debug/src/main/java/org/apache/hop/debug/transform/SetTransformDebugLevelExtensionPoint.java
b/plugins/misc/debug/src/main/java/org/apache/hop/debug/transform/SetTransformDebugLevelExtensionPoint.java
index 50525e7005..c646755517 100644
---
a/plugins/misc/debug/src/main/java/org/apache/hop/debug/transform/SetTransformDebugLevelExtensionPoint.java
+++
b/plugins/misc/debug/src/main/java/org/apache/hop/debug/transform/SetTransformDebugLevelExtensionPoint.java
@@ -20,22 +20,17 @@ package org.apache.hop.debug.transform;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.atomic.AtomicLong;
-import org.apache.hop.core.Condition;
import org.apache.hop.core.exception.HopException;
-import org.apache.hop.core.exception.HopTransformException;
import org.apache.hop.core.extension.ExtensionPoint;
import org.apache.hop.core.extension.IExtensionPoint;
import org.apache.hop.core.logging.ILogChannel;
import org.apache.hop.core.logging.LogLevel;
-import org.apache.hop.core.row.IRowMeta;
import org.apache.hop.core.variables.IVariables;
import org.apache.hop.debug.util.DebugLevelUtil;
import org.apache.hop.debug.util.Defaults;
import org.apache.hop.pipeline.PipelineMeta;
import org.apache.hop.pipeline.engine.IEngineComponent;
import org.apache.hop.pipeline.engine.IPipelineEngine;
-import org.apache.hop.pipeline.transform.IRowListener;
@ExtensionPoint(
id = "SetTransformDebugLevelExtensionPoint",
@@ -84,9 +79,7 @@ public class SetTransformDebugLevelExtensionPoint
final LogLevel resolvedLogLevel =
DebugLevelUtil.resolveLogLevel(variables,
debugLevel.getLogLevel());
- if (debugLevel.getStartRow() < 0
- && debugLevel.getEndRow() < 0
- && debugLevel.getCondition().isEmpty()) {
+ if (!hasRowSelection(debugLevel)) {
log.logDetailed(
"Set logging level for transform "
+ transformName
@@ -106,63 +99,25 @@ public class SetTransformDebugLevelExtensionPoint
+ transformCopy.getCopyNr());
}
} else {
- // We need to look at every row
+ // We need to look at every row to see whether the custom log
level applies to it.
//
- for (IEngineComponent transformCopy : transformCopies) {
+ boolean readingRows =
+ DebugLevelUtil.isReadingRows(pipeline.getPipelineMeta(),
transformName);
- final LogLevel baseLogLevel =
transformCopy.getLogChannel().getLogLevel();
- final AtomicLong rowCounter = new AtomicLong(0L);
+ log.logDetailed(
+ "Set logging level for the selected rows of transform "
+ + transformName
+ + " to "
+ + resolvedLogLevel.getDescription());
+ for (IEngineComponent transformCopy : transformCopies) {
transformCopy.addRowListener(
- new IRowListener() {
- @Override
- public void rowReadEvent(IRowMeta rowMeta, Object[] row)
{
- rowCounter.incrementAndGet();
- boolean enabled = false;
-
- Condition condition = debugLevel.getCondition();
-
- if (debugLevel.getStartRow() > 0
- && rowCounter.get() >= debugLevel.getStartRow()
- && debugLevel.getEndRow() >= 0
- && debugLevel.getEndRow() >= rowCounter.get()) {
- // If we have a start and an end, we want to stay
between start and end
- enabled = true;
- } else if (debugLevel.getStartRow() <= 0
- && debugLevel.getEndRow() >= 0
- && rowCounter.get() <= debugLevel.getEndRow()) {
- // If don't have a start row, just and end...
- enabled = true;
- } else if (debugLevel.getEndRow() <= 0
- && debugLevel.getStartRow() >= 0
- && rowCounter.get() >= debugLevel.getStartRow()) {
- enabled = true;
- }
-
- if ((debugLevel.getStartRow() <= 0 &&
debugLevel.getEndRow() <= 0
- || enabled)
- && !condition.isEmpty()) {
- enabled = condition.evaluate(rowMeta, row);
- }
-
- if (enabled) {
- transformCopy.setLogLevel(resolvedLogLevel);
- }
- }
-
- @Override
- public void rowWrittenEvent(IRowMeta rowMeta, Object[]
row)
- throws HopTransformException {
- // Set the log level back to the original value.
- //
-
transformCopy.getLogChannel().setLogLevel(baseLogLevel);
- }
-
- @Override
- public void errorRowWrittenEvent(IRowMeta rowMeta,
Object[] row) {
- // Do nothing
- }
- });
+ new TransformDebugLevelRowListener(
+ transformCopy,
+ debugLevel,
+ resolvedLogLevel,
+ transformCopy.getLogChannel().getLogLevel(),
+ readingRows));
}
}
}
@@ -172,4 +127,17 @@ public class SetTransformDebugLevelExtensionPoint
}
}
}
+
+ /**
+ * Does this configuration only apply the custom log level to a selection of
the rows?
+ *
+ * @param debugLevel the custom logging configuration
+ * @return true if a start row, an end row or a condition narrows the
configuration down to a
+ * selection of rows
+ */
+ private static boolean hasRowSelection(TransformDebugLevel debugLevel) {
+ return debugLevel.getStartRow() > 0
+ || debugLevel.getEndRow() > 0
+ || (debugLevel.getCondition() != null &&
!debugLevel.getCondition().isEmpty());
+ }
}
diff --git
a/plugins/misc/debug/src/main/java/org/apache/hop/debug/transform/TransformDebugGuiPlugin.java
b/plugins/misc/debug/src/main/java/org/apache/hop/debug/transform/TransformDebugGuiPlugin.java
index b2c8507b66..d1151d9157 100644
---
a/plugins/misc/debug/src/main/java/org/apache/hop/debug/transform/TransformDebugGuiPlugin.java
+++
b/plugins/misc/debug/src/main/java/org/apache/hop/debug/transform/TransformDebugGuiPlugin.java
@@ -17,7 +17,6 @@
package org.apache.hop.debug.transform;
-import java.util.HashMap;
import java.util.Map;
import org.apache.hop.core.action.GuiContextAction;
import org.apache.hop.core.gui.plugin.GuiPlugin;
@@ -71,13 +70,8 @@ public class TransformDebugGuiPlugin {
TransformMeta transformMeta = context.getTransformMeta();
IVariables variables = context.getPipelineGraph().getVariables();
- Map<String, Map<String, String>> attributesMap =
pipelineMeta.getAttributesMap();
- Map<String, String> debugGroupAttributesMap =
attributesMap.get(Defaults.DEBUG_GROUP);
-
- if (debugGroupAttributesMap == null) {
- debugGroupAttributesMap = new HashMap<>();
- attributesMap.put(Defaults.DEBUG_GROUP, debugGroupAttributesMap);
- }
+ Map<String, String> debugGroupAttributesMap =
+
DebugLevelUtil.getOrCreateDebugGroup(pipelineMeta.getAttributesMap());
TransformDebugLevel debugLevel =
DebugLevelUtil.getTransformDebugLevel(debugGroupAttributesMap,
transformMeta.getName());
@@ -85,10 +79,14 @@ public class TransformDebugGuiPlugin {
debugLevel = new TransformDebugLevel();
}
- IRowMeta inputRowMeta = pipelineMeta.getPrevTransformFields(variables,
transformMeta);
+ // The fields a condition can be built on are the fields the condition
is evaluated against
+ // at runtime: the incoming fields, or the transform's own fields if it
generates rows.
+ //
+ IRowMeta conditionRowMeta =
+ DebugLevelUtil.getConditionFields(variables, pipelineMeta,
transformMeta);
TransformDebugLevelDialog dialog =
new TransformDebugLevelDialog(
- hopGui.getActiveShell(), debugLevel, inputRowMeta, variables);
+ hopGui.getActiveShell(), debugLevel, conditionRowMeta,
variables);
if (dialog.open()) {
DebugLevelUtil.storeTransformDebugLevel(
debugGroupAttributesMap, transformMeta.getName(), debugLevel);
diff --git
a/plugins/misc/debug/src/main/java/org/apache/hop/debug/transform/TransformDebugLevelRowListener.java
b/plugins/misc/debug/src/main/java/org/apache/hop/debug/transform/TransformDebugLevelRowListener.java
new file mode 100644
index 0000000000..d3526d166c
--- /dev/null
+++
b/plugins/misc/debug/src/main/java/org/apache/hop/debug/transform/TransformDebugLevelRowListener.java
@@ -0,0 +1,125 @@
+/*
+ * 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.debug.transform;
+
+import java.util.concurrent.atomic.AtomicLong;
+import org.apache.hop.core.Condition;
+import org.apache.hop.core.logging.LogLevel;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.pipeline.engine.IEngineComponent;
+import org.apache.hop.pipeline.transform.IRowListener;
+
+/**
+ * Switches the configured custom log level on for the rows selected by a
{@link
+ * TransformDebugLevel} start/end row range and/or condition, and back off for
all other rows.
+ *
+ * <p>The level is evaluated exactly once per row and then left alone for the
rest of that row's
+ * processing. That matters because transforms log their row <em>after</em>
they called {@code
+ * putRow()}: switching the level back off on the write event (as this used to
do) killed every
+ * single row-level line the transform was supposed to produce.
+ *
+ * <p>Which event drives the evaluation depends on the transform: transforms
that read rows are
+ * evaluated on the read event (the row is then available for the condition
before the transform
+ * does any work with it), transforms that only generate rows are evaluated on
the write event,
+ * since they never fire a read event at all.
+ */
+public class TransformDebugLevelRowListener implements IRowListener {
+
+ private final IEngineComponent component;
+ private final TransformDebugLevel debugLevel;
+ private final LogLevel customLogLevel;
+ private final LogLevel baseLogLevel;
+ private final boolean readingRows;
+
+ private final AtomicLong rowCounter = new AtomicLong(0L);
+
+ /**
+ * @param component the transform copy to change the log level on
+ * @param debugLevel the custom logging configuration to apply
+ * @param customLogLevel the resolved log level to switch on for the
selected rows
+ * @param baseLogLevel the log level to fall back to for all other rows
+ * @param readingRows true if the transform reads rows from other
transforms, false if it only
+ * generates them
+ */
+ public TransformDebugLevelRowListener(
+ IEngineComponent component,
+ TransformDebugLevel debugLevel,
+ LogLevel customLogLevel,
+ LogLevel baseLogLevel,
+ boolean readingRows) {
+ this.component = component;
+ this.debugLevel = debugLevel;
+ this.customLogLevel = customLogLevel;
+ this.baseLogLevel = baseLogLevel;
+ this.readingRows = readingRows;
+ }
+
+ @Override
+ public void rowReadEvent(IRowMeta rowMeta, Object[] row) {
+ if (readingRows) {
+ applyLogLevel(rowMeta, row);
+ }
+ }
+
+ @Override
+ public void rowWrittenEvent(IRowMeta rowMeta, Object[] row) {
+ if (!readingRows) {
+ applyLogLevel(rowMeta, row);
+ }
+ }
+
+ @Override
+ public void errorRowWrittenEvent(IRowMeta rowMeta, Object[] row) {
+ // Error rows are handled by the error handling of the transform, leave
the level alone.
+ }
+
+ private void applyLogLevel(IRowMeta rowMeta, Object[] row) {
+ long rowNumber = rowCounter.incrementAndGet();
+ component
+ .getLogChannel()
+ .setLogLevel(isSelected(rowNumber, rowMeta, row) ? customLogLevel :
baseLogLevel);
+ }
+
+ /**
+ * Is the given row one of the rows we want the custom log level for?
+ *
+ * @param rowNumber the 1-based number of the row in this transform copy
+ * @param rowMeta the metadata of the row
+ * @param row the row itself
+ * @return true if the custom log level applies to this row
+ */
+ boolean isSelected(long rowNumber, IRowMeta rowMeta, Object[] row) {
+ int startRow = debugLevel.getStartRow();
+ int endRow = debugLevel.getEndRow();
+
+ // A start or end row of 0 or less simply means: no boundary on that side.
+ //
+ if (startRow > 0 && rowNumber < startRow) {
+ return false;
+ }
+ if (endRow > 0 && rowNumber > endRow) {
+ return false;
+ }
+
+ Condition condition = debugLevel.getCondition();
+ if (condition == null || condition.isEmpty()) {
+ return true;
+ }
+ return condition.evaluate(rowMeta, row);
+ }
+}
diff --git
a/plugins/misc/debug/src/main/java/org/apache/hop/debug/util/DebugLevelUtil.java
b/plugins/misc/debug/src/main/java/org/apache/hop/debug/util/DebugLevelUtil.java
index b8ef3c5332..5d51e9f0f5 100644
---
a/plugins/misc/debug/src/main/java/org/apache/hop/debug/util/DebugLevelUtil.java
+++
b/plugins/misc/debug/src/main/java/org/apache/hop/debug/util/DebugLevelUtil.java
@@ -20,17 +20,23 @@ package org.apache.hop.debug.util;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
+import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.apache.hop.core.Condition;
import org.apache.hop.core.Const;
+import org.apache.hop.core.exception.HopTransformException;
import org.apache.hop.core.exception.HopValueException;
import org.apache.hop.core.exception.HopXmlException;
import org.apache.hop.core.logging.LogLevel;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.RowMeta;
import org.apache.hop.core.variables.IVariables;
import org.apache.hop.debug.action.ActionDebugLevel;
import org.apache.hop.debug.transform.TransformDebugLevel;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.transform.TransformMeta;
public class DebugLevelUtil {
@@ -87,8 +93,70 @@ public class DebugLevelUtil {
return debugLevel;
}
+ /**
+ * Does the given transform read rows from other transforms, or does it only
generate them? Custom
+ * logging hooks into the read event of the former and into the write event
of the latter, so both
+ * the runtime and the dialog use this to know which rows the start/end row
range counts and which
+ * fields a condition can be built on.
+ *
+ * @param pipelineMeta the pipeline the transform lives in
+ * @param transformName the name of the transform
+ * @return true if the transform has one or more (info) input transforms
+ */
+ public static boolean isReadingRows(PipelineMeta pipelineMeta, String
transformName) {
+ return isReadingRows(pipelineMeta,
pipelineMeta.findTransform(transformName));
+ }
+
+ /**
+ * @see #isReadingRows(PipelineMeta, String)
+ * @param pipelineMeta the pipeline the transform lives in
+ * @param transformMeta the transform
+ * @return true if the transform has one or more (info) input transforms
+ */
+ public static boolean isReadingRows(PipelineMeta pipelineMeta, TransformMeta
transformMeta) {
+ return transformMeta != null
+ && !pipelineMeta.findPreviousTransforms(transformMeta, true).isEmpty();
+ }
+
+ /**
+ * Get the fields a custom logging condition is evaluated against for the
given transform: the
+ * incoming fields for a transform that reads rows, its own output fields
for a transform that
+ * only generates rows.
+ *
+ * @param variables the variables to resolve the metadata with
+ * @param pipelineMeta the pipeline the transform lives in
+ * @param transformMeta the transform
+ * @return the fields to build a condition on, never null
+ */
+ public static IRowMeta getConditionFields(
+ IVariables variables, PipelineMeta pipelineMeta, TransformMeta
transformMeta)
+ throws HopTransformException {
+ IRowMeta rowMeta =
+ isReadingRows(pipelineMeta, transformMeta)
+ ? pipelineMeta.getPrevTransformFields(variables, transformMeta)
+ : pipelineMeta.getTransformFields(variables, transformMeta);
+ return rowMeta == null ? new RowMeta() : rowMeta;
+ }
+
+ /**
+ * Get the attributes group custom logging is stored in, creating it if the
pipeline or workflow
+ * doesn't have one yet.
+ *
+ * @param attributesMap the attributes map of a pipeline or workflow
+ * @return the debug group attributes, never null
+ */
+ public static Map<String, String> getOrCreateDebugGroup(
+ Map<String, Map<String, String>> attributesMap) {
+ return attributesMap.computeIfAbsent(Defaults.DEBUG_GROUP, key -> new
HashMap<>());
+ }
+
public static void clearDebugLevel(
Map<String, String> debugGroupAttributesMap, String transformName) {
+ if (debugGroupAttributesMap == null) {
+ // Nothing was ever configured on this pipeline or workflow, so nothing
to clear.
+ //
+ return;
+ }
debugGroupAttributesMap.remove(transformName + " : " +
Defaults.TRANSFORM_ATTR_LOGLEVEL);
debugGroupAttributesMap.remove(transformName + " : " +
Defaults.TRANSFORM_ATTR_START_ROW);
debugGroupAttributesMap.remove(transformName + " : " +
Defaults.TRANSFORM_ATTR_END_ROW);
diff --git
a/plugins/misc/debug/src/test/java/org/apache/hop/debug/action/ModifyActionLogLevelExtensionPointTest.java
b/plugins/misc/debug/src/test/java/org/apache/hop/debug/action/ModifyActionLogLevelExtensionPointTest.java
new file mode 100644
index 0000000000..78c34a1686
--- /dev/null
+++
b/plugins/misc/debug/src/test/java/org/apache/hop/debug/action/ModifyActionLogLevelExtensionPointTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.debug.action;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.logging.DefaultLogLevel;
+import org.apache.hop.core.logging.HopLogStore;
+import org.apache.hop.core.logging.HopLoggingEvent;
+import org.apache.hop.core.logging.IHopLoggingEventListener;
+import org.apache.hop.core.logging.LogLevel;
+import org.apache.hop.core.variables.Variables;
+import org.apache.hop.debug.util.DebugLevelUtil;
+import org.apache.hop.debug.util.Defaults;
+import org.apache.hop.workflow.WorkflowMeta;
+import org.apache.hop.workflow.action.ActionMeta;
+import org.apache.hop.workflow.actions.dummy.ActionDummy;
+import org.apache.hop.workflow.actions.start.ActionStart;
+import org.apache.hop.workflow.engines.local.LocalWorkflowEngine;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Custom logging on an action offers to log the result, the result rows, the
result files and the
+ * changed variables of that action. That output has to survive a workflow
that itself logs nothing,
+ * which is the whole point of setting a custom level on a single action.
+ */
+class ModifyActionLogLevelExtensionPointTest {
+
+ private static final String ACTION = "dummy";
+
+ @BeforeAll
+ static void beforeAll() throws Exception {
+ HopEnvironment.init();
+ }
+
+ @Test
+ void theActionResultIsLoggedWhenTheWorkflowItselfLogsNothing() throws
Exception {
+ ActionDebugLevel debugLevel = new ActionDebugLevel(LogLevel.DETAILED);
+ debugLevel.setLoggingResult(true);
+
+ List<String> logged = runAndCollectLog(debugLevel, LogLevel.NOTHING);
+
+ assertTrue(
+ logged.stream().anyMatch(line -> line.contains("Action results:")),
+ "the action result was not logged, log was: " + logged);
+ }
+
+ /**
+ * The custom level can also be lower than the workflow's own level. Asking
for the result of an
+ * action still has to log it - the checkbox is more specific than the level.
+ */
+ @Test
+ void theActionResultIsLoggedWhenTheCustomLevelIsLowerThanTheWorkflowLevel()
throws Exception {
+ ActionDebugLevel debugLevel = new ActionDebugLevel(LogLevel.ERROR);
+ debugLevel.setLoggingResult(true);
+
+ List<String> logged = runAndCollectLog(debugLevel, LogLevel.BASIC);
+
+ assertTrue(
+ logged.stream().anyMatch(line -> line.contains("Action results:")),
+ "the action result was not logged, log was: " + logged);
+ }
+
+ /** Run a "start -> dummy" workflow with a custom logging configuration on
the dummy action. */
+ private List<String> runAndCollectLog(ActionDebugLevel debugLevel, LogLevel
workflowLogLevel)
+ throws Exception {
+ WorkflowMeta workflowMeta = new WorkflowMeta();
+ workflowMeta.setName("custom action logging");
+
+ ActionMeta start = new ActionMeta(new ActionStart("start"));
+ ActionMeta dummy = new ActionMeta(new ActionDummy());
+ dummy.setName(ACTION);
+ workflowMeta.addAction(start);
+ workflowMeta.addAction(dummy);
+ workflowMeta.addWorkflowHop(new
org.apache.hop.workflow.WorkflowHopMeta(start, dummy));
+
+ Map<String, String> debugGroup = new HashMap<>();
+ DebugLevelUtil.storeActionDebugLevel(debugGroup, ACTION, debugLevel);
+ workflowMeta.getAttributesMap().put(Defaults.DEBUG_GROUP, debugGroup);
+
+ // The workflow creates its log channel when it starts, and without a
parent logging object
+ // that channel takes its level from the default log level - the same knob
the GUI and hop-run
+ // turn when you pick a log level for a run.
+ //
+ LogLevel previousDefault = DefaultLogLevel.getLogLevel();
+ DefaultLogLevel.setLogLevel(workflowLogLevel);
+
+ List<String> logged = new ArrayList<>();
+ IHopLoggingEventListener listener =
+ (HopLoggingEvent event) -> {
+ synchronized (logged) {
+ logged.add(event.getMessage().toString());
+ }
+ };
+ HopLogStore.getAppender().addLoggingEventListener(listener);
+ try {
+ LocalWorkflowEngine workflow = new LocalWorkflowEngine(workflowMeta);
+ workflow.initializeFrom(new Variables());
+ workflow.setLogLevel(workflowLogLevel);
+ workflow.startExecution();
+ } finally {
+ HopLogStore.getAppender().removeLoggingEventListener(listener);
+ DefaultLogLevel.setLogLevel(previousDefault);
+ }
+
+ synchronized (logged) {
+ return new ArrayList<>(logged);
+ }
+ }
+}
diff --git
a/plugins/misc/debug/src/test/java/org/apache/hop/debug/transform/SetTransformDebugLevelExtensionPointTest.java
b/plugins/misc/debug/src/test/java/org/apache/hop/debug/transform/SetTransformDebugLevelExtensionPointTest.java
new file mode 100644
index 0000000000..2cf33edc32
--- /dev/null
+++
b/plugins/misc/debug/src/test/java/org/apache/hop/debug/transform/SetTransformDebugLevelExtensionPointTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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.debug.transform;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.hop.core.Condition;
+import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.exception.HopTransformException;
+import org.apache.hop.core.exception.HopValueException;
+import org.apache.hop.core.logging.HopLogStore;
+import org.apache.hop.core.logging.LogLevel;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.ValueMetaAndData;
+import org.apache.hop.core.row.value.ValueMetaInteger;
+import org.apache.hop.core.variables.Variables;
+import org.apache.hop.debug.util.DebugLevelUtil;
+import org.apache.hop.debug.util.Defaults;
+import org.apache.hop.pipeline.PipelineHopMeta;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.RowProducer;
+import org.apache.hop.pipeline.engine.IEngineComponent;
+import org.apache.hop.pipeline.engines.local.LocalPipelineEngine;
+import org.apache.hop.pipeline.transform.IRowListener;
+import org.apache.hop.pipeline.transform.TransformMeta;
+import org.apache.hop.pipeline.transforms.dummy.DummyMeta;
+import org.apache.hop.pipeline.transforms.injector.InjectorMeta;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Runs a real pipeline with a pipeline log level that logs nothing and custom
logging configured on
+ * one transform, to verify that the row level lines of the selected rows --
and only those -- end
+ * up in the log.
+ */
+class SetTransformDebugLevelExtensionPointTest {
+
+ private static final String INPUT = "input";
+ private static final String DUMMY = "dummy";
+ private static final int NR_ROWS = 5;
+ private static final String WROTE_ROW = "Wrote row #";
+
+ @BeforeAll
+ static void beforeAll() throws Exception {
+ HopEnvironment.init();
+ }
+
+ @Test
+ void onlyTheRowsInTheRangeAreLogged() throws Exception {
+ TransformDebugLevel debugLevel = new
TransformDebugLevel(LogLevel.ROWLEVEL);
+ debugLevel.setStartRow(2);
+ debugLevel.setEndRow(4);
+
+ assertEquals(List.of(2L, 3L, 4L), runAndCollectLoggedRows(debugLevel));
+ }
+
+ @Test
+ void onlyTheRowsMatchingTheConditionAreLogged() throws Exception {
+ TransformDebugLevel debugLevel = new
TransformDebugLevel(LogLevel.ROWLEVEL);
+ debugLevel.setCondition(
+ new Condition(
+ "seq",
+ Condition.Function.LARGER,
+ null,
+ new ValueMetaAndData(new ValueMetaInteger("constant"), 3L)));
+
+ assertEquals(List.of(4L, 5L), runAndCollectLoggedRows(debugLevel));
+ }
+
+ @Test
+ void withoutARangeOrConditionEveryRowIsLogged() throws Exception {
+ assertEquals(
+ List.of(1L, 2L, 3L, 4L, 5L),
+ runAndCollectLoggedRows(new TransformDebugLevel(LogLevel.ROWLEVEL)));
+ }
+
+ /**
+ * Run an "injector -> dummy" pipeline with the pipeline log level set to
nothing and the given
+ * custom logging configuration on the dummy transform.
+ *
+ * @param debugLevel the custom logging configuration for the dummy transform
+ * @return the numbers of the rows the dummy transform logged, in order
+ */
+ private List<Long> runAndCollectLoggedRows(TransformDebugLevel debugLevel)
throws Exception {
+ PipelineMeta pipelineMeta = new PipelineMeta();
+ pipelineMeta.setName("custom logging");
+
+ TransformMeta input = new TransformMeta(INPUT, new InjectorMeta());
+ TransformMeta dummy = new TransformMeta(DUMMY, new DummyMeta());
+ pipelineMeta.addTransform(input);
+ pipelineMeta.addTransform(dummy);
+ pipelineMeta.addPipelineHop(new PipelineHopMeta(input, dummy));
+
+ Map<String, String> debugGroup = new HashMap<>();
+ DebugLevelUtil.storeTransformDebugLevel(debugGroup, DUMMY, debugLevel);
+ pipelineMeta.getAttributesMap().put(Defaults.DEBUG_GROUP, debugGroup);
+
+ LocalPipelineEngine pipeline = new LocalPipelineEngine(pipelineMeta, new
Variables(), null);
+ pipeline.setLogLevel(LogLevel.NOTHING);
+ pipeline.prepareExecution();
+
+ IEngineComponent dummyCopy = pipeline.getComponentCopies(DUMMY).get(0);
+ String dummyLogChannelId = dummyCopy.getLogChannelId();
+
+ // A dummy transform doesn't log anything per row itself, so stand in for
a transform that does.
+ //
+ dummyCopy.addRowListener(new RowLevelLogger(dummyCopy));
+
+ RowProducer rowProducer = pipeline.addRowProducer(INPUT, 0);
+ pipeline.startThreads();
+
+ IRowMeta rowMeta = new RowMeta();
+ rowMeta.addValueMeta(new ValueMetaInteger("seq"));
+ for (long i = 1; i <= NR_ROWS; i++) {
+ rowProducer.putRow(rowMeta, new Object[] {i});
+ }
+ rowProducer.finished();
+ pipeline.waitUntilFinished();
+
+ assertTrue(pipeline.getErrors() == 0, "The pipeline reported errors");
+
+ return
loggedRowNumbers(HopLogStore.getAppender().getBuffer(dummyLogChannelId, false));
+ }
+
+ /** Pick the row numbers out of the "Wrote row #N" lines that reached the
log. */
+ private static List<Long> loggedRowNumbers(CharSequence logText) {
+ List<Long> rowNumbers = new ArrayList<>();
+ for (String line : logText.toString().split("\n")) {
+ int index = line.indexOf(WROTE_ROW);
+ if (index >= 0) {
+ String number = line.substring(index + WROTE_ROW.length()).split("
")[0];
+ rowNumbers.add(Long.valueOf(number));
+ }
+ }
+ return rowNumbers;
+ }
+
+ /**
+ * Logs one row level line per written row, the way the transforms that
support custom logging do.
+ * Row level lines only make it into the log while the custom log level is
switched on, so the
+ * lines this produces are exactly the rows the configuration selected.
+ *
+ * <p>Logging on the write event -- like the real transforms, which log
after they called {@code
+ * putRow()} -- keeps this independent of the order the row listeners were
added in: the custom
+ * log level is switched on when the row is read, well before this listener
is called for it.
+ */
+ private static class RowLevelLogger implements IRowListener {
+
+ private final IEngineComponent component;
+
+ private RowLevelLogger(IEngineComponent component) {
+ this.component = component;
+ }
+
+ @Override
+ public void rowWrittenEvent(IRowMeta rowMeta, Object[] row) throws
HopTransformException {
+ try {
+ component.getLogChannel().logRowlevel(WROTE_ROW +
rowMeta.getInteger(row, 0));
+ } catch (HopValueException e) {
+ throw new HopTransformException(e);
+ }
+ }
+
+ @Override
+ public void rowReadEvent(IRowMeta rowMeta, Object[] row) {
+ // Nothing to log here, see rowWrittenEvent()
+ }
+
+ @Override
+ public void errorRowWrittenEvent(IRowMeta rowMeta, Object[] row) {
+ // Nothing to log here, see rowWrittenEvent()
+ }
+ }
+}
diff --git
a/plugins/misc/debug/src/test/java/org/apache/hop/debug/transform/TransformDebugLevelRowListenerTest.java
b/plugins/misc/debug/src/test/java/org/apache/hop/debug/transform/TransformDebugLevelRowListenerTest.java
new file mode 100644
index 0000000000..c5e7f7c6e5
--- /dev/null
+++
b/plugins/misc/debug/src/test/java/org/apache/hop/debug/transform/TransformDebugLevelRowListenerTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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.debug.transform;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.hop.core.Condition;
+import org.apache.hop.core.HopClientEnvironment;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.logging.ILogChannel;
+import org.apache.hop.core.logging.LogLevel;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.ValueMetaAndData;
+import org.apache.hop.core.row.value.ValueMetaInteger;
+import org.apache.hop.pipeline.engine.IEngineComponent;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+/**
+ * The custom log level has to be switched on for exactly the selected rows,
and it has to still be
+ * switched on by the time the transform logs the row, which transforms do
after they called {@code
+ * putRow()}.
+ */
+class TransformDebugLevelRowListenerTest {
+
+ private static final LogLevel BASE = LogLevel.NOTHING;
+ private static final LogLevel CUSTOM = LogLevel.ROWLEVEL;
+
+ private final AtomicReference<LogLevel> currentLevel = new
AtomicReference<>(BASE);
+
+ @BeforeAll
+ static void beforeAll() throws HopException {
+ HopClientEnvironment.init();
+ }
+
+ // ------------------------------------------------------------------ helpers
+
+ private IEngineComponent component() {
+ currentLevel.set(BASE);
+ ILogChannel logChannel = mock(ILogChannel.class);
+ doAnswer(
+ invocation -> {
+ currentLevel.set(invocation.getArgument(0));
+ return null;
+ })
+ .when(logChannel)
+ .setLogLevel(any(LogLevel.class));
+ when(logChannel.getLogLevel()).thenAnswer(invocation ->
currentLevel.get());
+
+ IEngineComponent component = mock(IEngineComponent.class);
+ when(component.getLogChannel()).thenReturn(logChannel);
+ return component;
+ }
+
+ private static TransformDebugLevel debugLevel(int startRow, int endRow,
Condition condition) {
+ TransformDebugLevel debugLevel = new TransformDebugLevel(CUSTOM);
+ debugLevel.setStartRow(startRow);
+ debugLevel.setEndRow(endRow);
+ if (condition != null) {
+ debugLevel.setCondition(condition);
+ }
+ return debugLevel;
+ }
+
+ private static IRowMeta rowMeta() {
+ IRowMeta rowMeta = new RowMeta();
+ rowMeta.addValueMeta(new ValueMetaInteger("seq"));
+ return rowMeta;
+ }
+
+ /**
+ * Push a number of rows through the listener the way a transform does and
collect, per row, the
+ * log level that is active at the moment the transform logs its row: right
after {@code putRow()}
+ * returned.
+ */
+ private List<LogLevel> levelsWhenLogging(
+ TransformDebugLevelRowListener listener, boolean readingRows, int nrRows)
+ throws HopException {
+ IRowMeta rowMeta = rowMeta();
+ List<LogLevel> levels = new ArrayList<>();
+ for (int i = 1; i <= nrRows; i++) {
+ Object[] row = new Object[] {(long) i};
+ if (readingRows) {
+ listener.rowReadEvent(rowMeta, row);
+ }
+ listener.rowWrittenEvent(rowMeta, row);
+ levels.add(currentLevel.get());
+ }
+ return levels;
+ }
+
+ // -------------------------------------------------------------------- tests
+
+ @Test
+ void rangeIsAppliedWhileTheTransformLogsTheRow() throws Exception {
+ TransformDebugLevelRowListener listener =
+ new TransformDebugLevelRowListener(component(), debugLevel(2, 4,
null), CUSTOM, BASE, true);
+
+ assertEquals(List.of(BASE, CUSTOM, CUSTOM, CUSTOM, BASE),
levelsWhenLogging(listener, true, 5));
+ }
+
+ @Test
+ void rangeIsAppliedForTransformsThatOnlyGenerateRows() throws Exception {
+ // No read events at all: the row counter has to run on the written rows
instead.
+ //
+ TransformDebugLevelRowListener listener =
+ new TransformDebugLevelRowListener(
+ component(), debugLevel(2, 4, null), CUSTOM, BASE, false);
+
+ assertEquals(
+ List.of(BASE, CUSTOM, CUSTOM, CUSTOM, BASE),
levelsWhenLogging(listener, false, 5));
+ }
+
+ @Test
+ void startRowWithoutEndRowRunsUntilTheEnd() throws Exception {
+ TransformDebugLevelRowListener listener =
+ new TransformDebugLevelRowListener(
+ component(), debugLevel(3, -1, null), CUSTOM, BASE, true);
+
+ assertEquals(List.of(BASE, BASE, CUSTOM, CUSTOM, CUSTOM),
levelsWhenLogging(listener, true, 5));
+ }
+
+ @Test
+ void endRowWithoutStartRowStartsAtTheFirstRow() throws Exception {
+ TransformDebugLevelRowListener listener =
+ new TransformDebugLevelRowListener(
+ component(), debugLevel(-1, 2, null), CUSTOM, BASE, true);
+
+ assertEquals(List.of(CUSTOM, CUSTOM, BASE, BASE, BASE),
levelsWhenLogging(listener, true, 5));
+ }
+
+ @Test
+ void conditionSelectsTheMatchingRows() throws Exception {
+ Condition condition =
+ new Condition(
+ "seq",
+ Condition.Function.EQUAL,
+ null,
+ new ValueMetaAndData(new ValueMetaInteger("constant"), 3L));
+
+ TransformDebugLevelRowListener listener =
+ new TransformDebugLevelRowListener(
+ component(), debugLevel(-1, -1, condition), CUSTOM, BASE, true);
+
+ assertEquals(List.of(BASE, BASE, CUSTOM, BASE, BASE),
levelsWhenLogging(listener, true, 5));
+ }
+
+ @Test
+ void conditionIsCombinedWithTheRange() throws Exception {
+ Condition condition =
+ new Condition(
+ "seq",
+ Condition.Function.LARGER,
+ null,
+ new ValueMetaAndData(new ValueMetaInteger("constant"), 2L));
+
+ TransformDebugLevelRowListener listener =
+ new TransformDebugLevelRowListener(
+ component(), debugLevel(-1, 4, condition), CUSTOM, BASE, true);
+
+ // Row 1 and 2 fail the condition, row 5 falls outside the range.
+ //
+ assertEquals(List.of(BASE, BASE, CUSTOM, CUSTOM, BASE),
levelsWhenLogging(listener, true, 5));
+ }
+}
diff --git
a/plugins/misc/debug/src/test/java/org/apache/hop/debug/util/DebugLevelUtilTest.java
b/plugins/misc/debug/src/test/java/org/apache/hop/debug/util/DebugLevelUtilTest.java
index 733a15968b..36a0e03f8b 100644
---
a/plugins/misc/debug/src/test/java/org/apache/hop/debug/util/DebugLevelUtilTest.java
+++
b/plugins/misc/debug/src/test/java/org/apache/hop/debug/util/DebugLevelUtilTest.java
@@ -17,9 +17,11 @@
package org.apache.hop.debug.util;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
import java.util.HashMap;
import java.util.Map;
@@ -31,6 +33,27 @@ import org.junit.jupiter.api.Test;
class DebugLevelUtilTest {
+ /**
+ * "Clear custom logging" is offered on every transform and action, also on
the ones in a pipeline
+ * or workflow that never had any custom logging - and those have no debug
attributes group at
+ * all.
+ */
+ @Test
+ void clearingDebugLevelWithoutADebugGroupDoesNothing() {
+ assertDoesNotThrow(() -> DebugLevelUtil.clearDebugLevel(null,
"MyTransform"));
+ }
+
+ @Test
+ void theDebugGroupIsCreatedOnDemand() {
+ Map<String, Map<String, String>> attributesMap = new HashMap<>();
+
+ Map<String, String> group =
DebugLevelUtil.getOrCreateDebugGroup(attributesMap);
+ group.put("key", "value");
+
+ assertSame(group, DebugLevelUtil.getOrCreateDebugGroup(attributesMap));
+ assertEquals("value", attributesMap.get(Defaults.DEBUG_GROUP).get("key"));
+ }
+
@Test
void storeAndLoadTransformDebugLevelWithCode() throws Exception {
Map<String, String> map = new HashMap<>();
diff --git
a/plugins/transforms/addsequence/src/main/java/org/apache/hop/pipeline/transforms/addsequence/AddSequence.java
b/plugins/transforms/addsequence/src/main/java/org/apache/hop/pipeline/transforms/addsequence/AddSequence.java
index 701c1959b2..55a6c3fd49 100644
---
a/plugins/transforms/addsequence/src/main/java/org/apache/hop/pipeline/transforms/addsequence/AddSequence.java
+++
b/plugins/transforms/addsequence/src/main/java/org/apache/hop/pipeline/transforms/addsequence/AddSequence.java
@@ -116,14 +116,18 @@ public class AddSequence extends
BaseTransform<AddSequenceMeta, AddSequenceData>
}
try {
- putRow(data.outputRowMeta, addSequence(getInputRowMeta(), r));
+ // The row we wrote is the row with the sequence value in it, so render
it with the output
+ // metadata: the incoming metadata doesn't know about the value we just
added.
+ //
+ Object[] outputRowData = addSequence(getInputRowMeta(), r);
+ putRow(data.outputRowMeta, outputRowData);
if (isRowLevel()) {
logRowlevel(
BaseMessages.getString(PKG, "AddSequence.Log.WriteRow")
+ getLinesWritten()
+ " : "
- + getInputRowMeta().getString(r));
+ + data.outputRowMeta.getString(outputRowData));
}
if (checkFeedback(getLinesRead()) && isBasic()) {
logBasic(BaseMessages.getString(PKG, "AddSequence.Log.LineNumber") +
getLinesRead());
diff --git
a/plugins/transforms/calculator/src/main/java/org/apache/hop/pipeline/transforms/calculator/Calculator.java
b/plugins/transforms/calculator/src/main/java/org/apache/hop/pipeline/transforms/calculator/Calculator.java
index 34f94e5fcc..e3658443a1 100644
---
a/plugins/transforms/calculator/src/main/java/org/apache/hop/pipeline/transforms/calculator/Calculator.java
+++
b/plugins/transforms/calculator/src/main/java/org/apache/hop/pipeline/transforms/calculator/Calculator.java
@@ -174,7 +174,8 @@ public class Calculator extends
BaseTransform<CalculatorMeta, CalculatorData> {
putRow(data.getOutputRowMeta(), row); // copy row to possible alternate
rowset(s).
if (isRowLevel()) {
- logRowlevel("Wrote row #" + getLinesWritten() + " : " +
getInputRowMeta().getString(r));
+ logRowlevel(
+ "Wrote row #" + getLinesWritten() + " : " +
data.getOutputRowMeta().getString(row));
}
if (checkFeedback(getLinesRead()) && isBasic()) {
logBasic(BaseMessages.getString(PKG, "Calculator.Log.Linenr", "" +
getLinesRead()));
diff --git
a/plugins/transforms/columnexists/src/main/java/org/apache/hop/pipeline/transforms/columnexists/ColumnExists.java
b/plugins/transforms/columnexists/src/main/java/org/apache/hop/pipeline/transforms/columnexists/ColumnExists.java
index 50240da44f..0f8bff667b 100644
---
a/plugins/transforms/columnexists/src/main/java/org/apache/hop/pipeline/transforms/columnexists/ColumnExists.java
+++
b/plugins/transforms/columnexists/src/main/java/org/apache/hop/pipeline/transforms/columnexists/ColumnExists.java
@@ -146,7 +146,7 @@ public class ColumnExists extends
BaseTransform<ColumnExistsMeta, ColumnExistsDa
BaseMessages.getString(
PKG,
"ColumnExists.LineNumber",
- getLinesRead() + " : " + getInputRowMeta().getString(r)));
+ getLinesRead() + " : " +
data.outputRowMeta.getString(outputRowData)));
}
} catch (HopException e) {
if (getTransformMeta().isDoingErrorHandling()) {
diff --git
a/plugins/transforms/constant/src/main/java/org/apache/hop/pipeline/transforms/constant/Constant.java
b/plugins/transforms/constant/src/main/java/org/apache/hop/pipeline/transforms/constant/Constant.java
index ed56864a4e..56cfc743d4 100644
---
a/plugins/transforms/constant/src/main/java/org/apache/hop/pipeline/transforms/constant/Constant.java
+++
b/plugins/transforms/constant/src/main/java/org/apache/hop/pipeline/transforms/constant/Constant.java
@@ -312,7 +312,7 @@ public class Constant extends BaseTransform<ConstantMeta,
ConstantData> {
PKG,
"Constant.Log.Wrote.Row",
Long.toString(getLinesWritten()),
- getInputRowMeta().getString(r)));
+ data.outputMeta.getString(r)));
}
if (checkFeedback(getLinesWritten()) && isBasic()) {
diff --git
a/plugins/transforms/constant/src/test/java/org/apache/hop/pipeline/transforms/constant/ConstantTest.java
b/plugins/transforms/constant/src/test/java/org/apache/hop/pipeline/transforms/constant/ConstantTest.java
index 231bdba821..66e3a713ff 100644
---
a/plugins/transforms/constant/src/test/java/org/apache/hop/pipeline/transforms/constant/ConstantTest.java
+++
b/plugins/transforms/constant/src/test/java/org/apache/hop/pipeline/transforms/constant/ConstantTest.java
@@ -120,6 +120,40 @@ class ConstantTest {
assertFalse(success);
}
+ /**
+ * The row that is logged at row level is the row this transform wrote, so
it has to be rendered
+ * with the output row metadata. Using the incoming metadata silently
dropped the constants that
+ * were just added (issue #2621).
+ */
+ @Test
+ void testRowLevelLoggingShowsTheAddedConstants() throws Exception {
+ IRowMeta inputRowMeta = new RowMeta();
+ inputRowMeta.addValueMeta(new ValueMetaString("field_01"));
+
+ doReturn(new Object[] {"START"}).when(constantSpy).getRow();
+ doReturn(inputRowMeta).when(constantSpy).getInputRowMeta();
+ doReturn(new Object[] {"ALL"}).when(rowMetaAndData).getData();
+ mockHelper.iTransformData.firstRow = true;
+ // The transform meta is a mock, so play the part of getFields() and add
the constant field.
+ Mockito.doAnswer(
+ invocation -> {
+ IRowMeta outputRowMeta = invocation.getArgument(0);
+ outputRowMeta.addValueMeta(new ValueMetaString("constant_01"));
+ return null;
+ })
+ .when(mockHelper.iTransformMeta)
+ .getFields(any(), any(), any(), any(), any(), any());
+ when(mockHelper.iLogChannel.isRowLevel()).thenReturn(true);
+
+ assertTrue(constantSpy.processRow());
+
+ ArgumentCaptor<String> logged = ArgumentCaptor.forClass(String.class);
+ Mockito.verify(mockHelper.iLogChannel).logRowlevel(logged.capture());
+ assertTrue(
+ logged.getValue().contains("START") &&
logged.getValue().contains("ALL"),
+ "the logged row should contain the added constant, but was: " +
logged.getValue());
+ }
+
/**
* The dialog offers every registered value type, so buildRow() has to cope
with types it has no
* dedicated case for. Types the value meta plugin can build from a string
have to work.
diff --git
a/plugins/transforms/creditcardvalidator/src/main/java/org/apache/hop/pipeline/transforms/creditcardvalidator/CreditCardValidator.java
b/plugins/transforms/creditcardvalidator/src/main/java/org/apache/hop/pipeline/transforms/creditcardvalidator/CreditCardValidator.java
index 801d28014e..9526c33422 100644
---
a/plugins/transforms/creditcardvalidator/src/main/java/org/apache/hop/pipeline/transforms/creditcardvalidator/CreditCardValidator.java
+++
b/plugins/transforms/creditcardvalidator/src/main/java/org/apache/hop/pipeline/transforms/creditcardvalidator/CreditCardValidator.java
@@ -136,7 +136,7 @@ public class CreditCardValidator
BaseMessages.getString(
PKG,
"CreditCardValidator.LineNumber",
- getLinesRead() + " : " + getInputRowMeta().getString(row)));
+ getLinesRead() + " : " +
data.outputRowMeta.getString(outputRow)));
}
} catch (Exception e) {
diff --git
a/plugins/transforms/databaselookup/src/main/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookup.java
b/plugins/transforms/databaselookup/src/main/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookup.java
index 7ff1682500..80d1b0044e 100644
---
a/plugins/transforms/databaselookup/src/main/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookup.java
+++
b/plugins/transforms/databaselookup/src/main/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookup.java
@@ -457,7 +457,7 @@ public class DatabaseLookup extends
BaseTransform<DatabaseLookupMeta, DatabaseLo
if (isRowLevel()) {
logRowlevel(
BaseMessages.getString(PKG,
"DatabaseLookup.Log.WroteRowToNextTransform")
- + getInputRowMeta().getString(r));
+ + data.outputRowMeta.getString(outputRow));
}
if (checkFeedback(getLinesRead()) && isBasic()) {
logBasic("linenr " + getLinesRead());
diff --git
a/plugins/transforms/execprocess/src/main/java/org/apache/hop/pipeline/transforms/execprocess/ExecProcess.java
b/plugins/transforms/execprocess/src/main/java/org/apache/hop/pipeline/transforms/execprocess/ExecProcess.java
index 922ba64803..05dfd5b292 100644
---
a/plugins/transforms/execprocess/src/main/java/org/apache/hop/pipeline/transforms/execprocess/ExecProcess.java
+++
b/plugins/transforms/execprocess/src/main/java/org/apache/hop/pipeline/transforms/execprocess/ExecProcess.java
@@ -127,7 +127,7 @@ public class ExecProcess extends
BaseTransform<ExecProcessMeta, ExecProcessData>
BaseMessages.getString(
PKG,
"ExecProcess.LineNumber",
- getLinesRead() + " : " + getInputRowMeta().getString(r)));
+ getLinesRead() + " : " +
data.outputRowMeta.getString(outputRow)));
}
} catch (HopException e) {
if (getTransformMeta().isDoingErrorHandling()) {
diff --git
a/plugins/transforms/fieldschangesequence/src/main/java/org/apache/hop/pipeline/transforms/fieldschangesequence/FieldsChangeSequence.java
b/plugins/transforms/fieldschangesequence/src/main/java/org/apache/hop/pipeline/transforms/fieldschangesequence/FieldsChangeSequence.java
index 8c27585aae..a7ab6ff78c 100644
---
a/plugins/transforms/fieldschangesequence/src/main/java/org/apache/hop/pipeline/transforms/fieldschangesequence/FieldsChangeSequence.java
+++
b/plugins/transforms/fieldschangesequence/src/main/java/org/apache/hop/pipeline/transforms/fieldschangesequence/FieldsChangeSequence.java
@@ -131,7 +131,7 @@ public class FieldsChangeSequence
BaseMessages.getString(PKG, "FieldsChangeSequence.Log.WriteRow")
+ getLinesWritten()
+ " : "
- + getInputRowMeta().getString(r));
+ + data.outputRowMeta.getString(outputRowData));
}
if (checkFeedback(getLinesRead()) && isBasic()) {
diff --git
a/plugins/transforms/fileexists/src/main/java/org/apache/hop/pipeline/transforms/fileexists/FileExists.java
b/plugins/transforms/fileexists/src/main/java/org/apache/hop/pipeline/transforms/fileexists/FileExists.java
index ea94998d7e..6db30dc618 100644
---
a/plugins/transforms/fileexists/src/main/java/org/apache/hop/pipeline/transforms/fileexists/FileExists.java
+++
b/plugins/transforms/fileexists/src/main/java/org/apache/hop/pipeline/transforms/fileexists/FileExists.java
@@ -147,7 +147,7 @@ public class FileExists extends
BaseTransform<FileExistsMeta, FileExistsData> {
BaseMessages.getString(
PKG,
"FileExists.LineNumber",
- getLinesRead() + " : " + getInputRowMeta().getString(r)));
+ getLinesRead() + " : " +
data.outputRowMeta.getString(outputRow)));
}
} catch (Exception e) {
if (getTransformMeta().isDoingErrorHandling()) {
diff --git
a/plugins/transforms/filelocked/src/main/java/org/apache/hop/pipeline/transforms/filelocked/FileLocked.java
b/plugins/transforms/filelocked/src/main/java/org/apache/hop/pipeline/transforms/filelocked/FileLocked.java
index ba72636d1a..2790a63437 100644
---
a/plugins/transforms/filelocked/src/main/java/org/apache/hop/pipeline/transforms/filelocked/FileLocked.java
+++
b/plugins/transforms/filelocked/src/main/java/org/apache/hop/pipeline/transforms/filelocked/FileLocked.java
@@ -115,16 +115,15 @@ public class FileLocked extends
BaseTransform<FileLockedMeta, FileLockedData> {
}
// add file locked
- putRow(
- data.outputRowMeta,
- RowDataUtil.addValueData(r, data.NrPrevFields, fileLocked)); // copy
row to output
+ Object[] outputRow = RowDataUtil.addValueData(r, data.NrPrevFields,
fileLocked);
+ putRow(data.outputRowMeta, outputRow); // copy row to output
if (isRowLevel()) {
logRowlevel(
BaseMessages.getString(
PKG,
"FileLocked.LineNumber",
- getLinesRead() + " : " + getInputRowMeta().getString(r)));
+ getLinesRead() + " : " +
data.outputRowMeta.getString(outputRow)));
}
} catch (Exception e) {
boolean sendToErrorRow = false;
diff --git
a/plugins/transforms/formula/src/main/java/org/apache/hop/pipeline/transforms/formula/Formula.java
b/plugins/transforms/formula/src/main/java/org/apache/hop/pipeline/transforms/formula/Formula.java
index a92b3e8bca..938861aa25 100644
---
a/plugins/transforms/formula/src/main/java/org/apache/hop/pipeline/transforms/formula/Formula.java
+++
b/plugins/transforms/formula/src/main/java/org/apache/hop/pipeline/transforms/formula/Formula.java
@@ -21,7 +21,6 @@ import static
org.apache.hop.pipeline.transforms.formula.util.FormulaFieldsExtra
import java.io.IOException;
import java.sql.Timestamp;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.stream.IntStream;
@@ -136,7 +135,7 @@ public class Formula extends BaseTransform<FormulaMeta,
FormulaData> {
int tempIndex = getInputRowMeta().size();
if (isRowLevel()) {
- logRowlevel("Read row #" + getLinesRead() + " : " + Arrays.toString(r));
+ logRowlevel("Read row #" + getLinesRead() + " : " +
getInputRowMeta().getString(r));
}
Object[] outputRowData = RowDataUtil.resizeArray(r,
data.outputRowMeta.size());
@@ -219,7 +218,8 @@ public class Formula extends BaseTransform<FormulaMeta,
FormulaData> {
putRow(data.outputRowMeta, outputRowData);
if (isRowLevel()) {
- logRowlevel("Wrote row #" + getLinesWritten() + " : " +
Arrays.toString(r));
+ logRowlevel(
+ "Wrote row #" + getLinesWritten() + " : " +
data.outputRowMeta.getString(outputRowData));
}
if (checkFeedback(getLinesRead()) && isBasic()) {
logBasic("Linenr " + getLinesRead());
diff --git
a/plugins/transforms/samplerows/src/main/java/org/apache/hop/pipeline/transforms/samplerows/SampleRows.java
b/plugins/transforms/samplerows/src/main/java/org/apache/hop/pipeline/transforms/samplerows/SampleRows.java
index c16d860550..537458388f 100644
---
a/plugins/transforms/samplerows/src/main/java/org/apache/hop/pipeline/transforms/samplerows/SampleRows.java
+++
b/plugins/transforms/samplerows/src/main/java/org/apache/hop/pipeline/transforms/samplerows/SampleRows.java
@@ -115,7 +115,7 @@ public class SampleRows extends
BaseTransform<SampleRowsMeta, SampleRowsData> {
BaseMessages.getString(
PKG,
"SampleRows.Log.LineNumber",
- linesRead + " : " + getInputRowMeta().getString(r)));
+ linesRead + " : " +
data.outputRowMeta.getString(data.outputRow)));
}
}
diff --git
a/plugins/transforms/tableexists/src/main/java/org/apache/hop/pipeline/transforms/tableexists/TableExists.java
b/plugins/transforms/tableexists/src/main/java/org/apache/hop/pipeline/transforms/tableexists/TableExists.java
index 2448395107..3c02295d4d 100644
---
a/plugins/transforms/tableexists/src/main/java/org/apache/hop/pipeline/transforms/tableexists/TableExists.java
+++
b/plugins/transforms/tableexists/src/main/java/org/apache/hop/pipeline/transforms/tableexists/TableExists.java
@@ -103,7 +103,7 @@ public class TableExists extends
BaseTransform<TableExistsMeta, TableExistsData>
BaseMessages.getString(
PKG,
"TableExists.LineNumber",
- getLinesRead() + " : " + getInputRowMeta().getString(r)));
+ getLinesRead() + " : " +
data.outputRowMeta.getString(outputRowData)));
}
} catch (HopException e) {
if (getTransformMeta().isDoingErrorHandling()) {
diff --git
a/plugins/transforms/webserviceavailable/src/main/java/org/apache/hop/pipeline/transforms/webserviceavailable/WebServiceAvailable.java
b/plugins/transforms/webserviceavailable/src/main/java/org/apache/hop/pipeline/transforms/webserviceavailable/WebServiceAvailable.java
index a148558533..c1b4502a8d 100644
---
a/plugins/transforms/webserviceavailable/src/main/java/org/apache/hop/pipeline/transforms/webserviceavailable/WebServiceAvailable.java
+++
b/plugins/transforms/webserviceavailable/src/main/java/org/apache/hop/pipeline/transforms/webserviceavailable/WebServiceAvailable.java
@@ -151,15 +151,15 @@ public class WebServiceAvailable
// addwebservice available to the row
// copy row to output rowset(s)
- putRow(
- data.outputRowMeta, RowDataUtil.addValueData(r, data.nrPrevFields,
webServiceAvailable));
+ Object[] outputRow = RowDataUtil.addValueData(r, data.nrPrevFields,
webServiceAvailable);
+ putRow(data.outputRowMeta, outputRow);
if (isRowLevel()) {
logRowlevel(
BaseMessages.getString(
PKG,
"FileExists.LineNumber",
- getLinesRead() + " : " + getInputRowMeta().getString(r)));
+ getLinesRead() + " : " +
data.outputRowMeta.getString(outputRow)));
}
} catch (Exception e) {
boolean sendToErrorRow = false;