From 986f9458d1dcbede2de16469637761319d911e80 Mon Sep 17 00:00:00 2001 From: Edvard Fonsell Date: Mon, 1 Feb 2021 16:00:47 +0200 Subject: [PATCH 1/9] allow overriding log level for retryable exception logging --- .../executor/WorkflowStateProcessor.java | 30 +++++++++++++++---- .../definition/ExceptionSeverity.java | 5 ++++ .../workflow/definition/WorkflowState.java | 12 ++++++++ 3 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionSeverity.java diff --git a/nflow-engine/src/main/java/io/nflow/engine/internal/executor/WorkflowStateProcessor.java b/nflow-engine/src/main/java/io/nflow/engine/internal/executor/WorkflowStateProcessor.java index 87c73f942..f873ebe46 100644 --- a/nflow-engine/src/main/java/io/nflow/engine/internal/executor/WorkflowStateProcessor.java +++ b/nflow-engine/src/main/java/io/nflow/engine/internal/executor/WorkflowStateProcessor.java @@ -87,8 +87,9 @@ class WorkflowStateProcessor implements Runnable { private Thread thread; private ListenerContext listenerContext; - WorkflowStateProcessor(long instanceId, Supplier shutdownRequested, ObjectStringMapper objectMapper, WorkflowDefinitionService workflowDefinitions, - WorkflowInstanceService workflowInstances, WorkflowInstanceDao workflowInstanceDao, MaintenanceDao maintenanceDao, + WorkflowStateProcessor(long instanceId, Supplier shutdownRequested, ObjectStringMapper objectMapper, + WorkflowDefinitionService workflowDefinitions, WorkflowInstanceService workflowInstances, + WorkflowInstanceDao workflowInstanceDao, MaintenanceDao maintenanceDao, WorkflowInstancePreProcessor workflowInstancePreProcessor, Environment env, Map processingInstances, WorkflowExecutorListener... executorListeners) { this.instanceId = instanceId; @@ -125,7 +126,8 @@ public void run() { logger.error("Failed to process workflow instance and shutdown requested", ex); break; } - logger.error("Failed to process workflow instance {}, retrying after {} seconds", instanceId, stateProcessingRetryDelay, ex); + logger.error("Failed to process workflow instance {}, retrying after {} seconds", instanceId, stateProcessingRetryDelay, + ex); sleepIgnoreInterrupted(stateProcessingRetryDelay); } } @@ -170,7 +172,7 @@ private void runImpl() { } execution.setFailed(t); if (state.isRetryAllowed(t)) { - logger.error("Handler threw a retryable exception, trying again later.", t); + logRetryableException(state, t); execution.setRetry(true); execution.setNextState(state); execution.setNextStateReason(getStackTrace(t)); @@ -195,6 +197,21 @@ private void runImpl() { logger.debug("Finished."); } + private void logRetryableException(WorkflowState state, Throwable t) { + switch (state.getExceptionSeverity(t)) { + case INFO: + logger.info("Handler threw a retryable exception, trying again later. Message: ", t.getMessage()); + return; + case WARNING: + logger.warn("Handler threw a retryable exception, trying again later.", t); + return; + case ERROR: + default: + logger.error("Handler threw a retryable exception, trying again later.", t); + return; + } + } + void logIfLagging(WorkflowInstance instance) { Duration executionLag = new Duration(instance.nextActivation, now()); if (executionLag.isLongerThan(standardMinutes(1))) { @@ -275,8 +292,9 @@ private WorkflowInstance saveWorkflowInstanceState(StateExecutionImpl execution, return persistWorkflowInstanceState(execution, instance.stateVariables, actionBuilder, instanceBuilder); } catch (Exception ex) { if (shutdownRequested.get()) { - logger.error("Failed to save workflow instance {} new state, not retrying due to shutdown request. The state will be rerun on recovery.", - instance.id, ex); + logger.error( + "Failed to save workflow instance {} new state, not retrying due to shutdown request. The state will be rerun on recovery.", + instance.id, ex); // return the original instance since persisting failed return instance; } diff --git a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionSeverity.java b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionSeverity.java new file mode 100644 index 000000000..2178d9b10 --- /dev/null +++ b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionSeverity.java @@ -0,0 +1,5 @@ +package io.nflow.engine.workflow.definition; + +public enum ExceptionSeverity { + ERROR, WARNING, INFO +} diff --git a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowState.java b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowState.java index 9a7c82d53..39539ca4f 100644 --- a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowState.java +++ b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowState.java @@ -39,4 +39,16 @@ default String getDescription() { default boolean isRetryAllowed(Throwable thrown) { return !thrown.getClass().isAnnotationPresent(NonRetryable.class); } + + /** + * Return the severity of the exception thrown by the state execution. Default is ERROR. Stack trace will not be logged for + * INFO. + * + * @param thrown + * The thrown exception. + * @return Exception severity. + */ + default ExceptionSeverity getExceptionSeverity(Throwable thrown) { + return ExceptionSeverity.ERROR; + } } From c0420cffbd8a5c223a337109f8698f956848ed03 Mon Sep 17 00:00:00 2001 From: Edvard Fonsell Date: Mon, 1 Feb 2021 17:23:59 +0200 Subject: [PATCH 2/9] refactor --- .../executor/WorkflowStateProcessor.java | 30 ++++++++++++----- .../definition/ExceptionSeverity.java | 32 +++++++++++++++++-- .../workflow/definition/WorkflowState.java | 5 ++- 3 files changed, 54 insertions(+), 13 deletions(-) diff --git a/nflow-engine/src/main/java/io/nflow/engine/internal/executor/WorkflowStateProcessor.java b/nflow-engine/src/main/java/io/nflow/engine/internal/executor/WorkflowStateProcessor.java index f873ebe46..382fcb6d8 100644 --- a/nflow-engine/src/main/java/io/nflow/engine/internal/executor/WorkflowStateProcessor.java +++ b/nflow-engine/src/main/java/io/nflow/engine/internal/executor/WorkflowStateProcessor.java @@ -24,12 +24,14 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.function.BiConsumer; import java.util.function.Supplier; import org.joda.time.DateTime; import org.joda.time.Duration; import org.slf4j.Logger; import org.slf4j.MDC; +import org.slf4j.event.Level; import org.springframework.core.env.Environment; import org.springframework.dao.DataAccessException; import org.springframework.dao.EmptyResultDataAccessException; @@ -49,6 +51,7 @@ import io.nflow.engine.service.WorkflowDefinitionService; import io.nflow.engine.service.WorkflowInstanceService; import io.nflow.engine.workflow.definition.AbstractWorkflowDefinition; +import io.nflow.engine.workflow.definition.ExceptionSeverity; import io.nflow.engine.workflow.definition.NextAction; import io.nflow.engine.workflow.definition.WorkflowSettings; import io.nflow.engine.workflow.definition.WorkflowState; @@ -198,17 +201,28 @@ private void runImpl() { } private void logRetryableException(WorkflowState state, Throwable t) { - switch (state.getExceptionSeverity(t)) { + ExceptionSeverity exceptionSeverity = state.getExceptionSeverity(t); + BiConsumer logMethod = getLogMethod(exceptionSeverity.logLevel); + if (exceptionSeverity.logStackTrace) { + logMethod.accept("Handler threw a retryable exception, trying again later.", t); + } else { + logMethod.accept("Handler threw a retryable exception, trying again later. Message: {}", t.getMessage()); + } + } + + private BiConsumer getLogMethod(Level logLevel) { + switch (logLevel) { + case TRACE: + return logger::trace; + case DEBUG: + return logger::debug; case INFO: - logger.info("Handler threw a retryable exception, trying again later. Message: ", t.getMessage()); - return; - case WARNING: - logger.warn("Handler threw a retryable exception, trying again later.", t); - return; + return logger::info; + case WARN: + return logger::warn; case ERROR: default: - logger.error("Handler threw a retryable exception, trying again later.", t); - return; + return logger::error; } } diff --git a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionSeverity.java b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionSeverity.java index 2178d9b10..01cc2b849 100644 --- a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionSeverity.java +++ b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionSeverity.java @@ -1,5 +1,33 @@ package io.nflow.engine.workflow.definition; -public enum ExceptionSeverity { - ERROR, WARNING, INFO +import static org.slf4j.event.Level.ERROR; + +import org.slf4j.event.Level; + +public class ExceptionSeverity { + public static final ExceptionSeverity DEFAULT = new ExceptionSeverity(ERROR, true); + public final Level logLevel; + public final boolean logStackTrace; + + ExceptionSeverity(Level logLevel, boolean logStackTrace) { + this.logLevel = logLevel; + this.logStackTrace = logStackTrace; + } + + public static class Builder { + private Level logLevel = ERROR; + private boolean logStackTrace = true; + + public void setLogLevel(Level logLevel) { + this.logLevel = logLevel; + } + + public void setLogStackTrace(boolean logStackTrace) { + this.logStackTrace = logStackTrace; + } + + public ExceptionSeverity build() { + return new ExceptionSeverity(logLevel, logStackTrace); + } + } } diff --git a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowState.java b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowState.java index 39539ca4f..8fdc18ce8 100644 --- a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowState.java +++ b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowState.java @@ -41,14 +41,13 @@ default boolean isRetryAllowed(Throwable thrown) { } /** - * Return the severity of the exception thrown by the state execution. Default is ERROR. Stack trace will not be logged for - * INFO. + * Return the severity of the exception thrown by the state execution. Using default means ERROR level logging with stack trace. * * @param thrown * The thrown exception. * @return Exception severity. */ default ExceptionSeverity getExceptionSeverity(Throwable thrown) { - return ExceptionSeverity.ERROR; + return ExceptionSeverity.DEFAULT; } } From c28b3e26b2850ffdffa22c1776b319a77a11dfdf Mon Sep 17 00:00:00 2001 From: Edvard Fonsell Date: Mon, 1 Feb 2021 18:27:15 +0200 Subject: [PATCH 3/9] log state name that threw exception --- .../engine/internal/executor/WorkflowStateProcessor.java | 9 +++++---- .../engine/workflow/definition/ExceptionSeverity.java | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/nflow-engine/src/main/java/io/nflow/engine/internal/executor/WorkflowStateProcessor.java b/nflow-engine/src/main/java/io/nflow/engine/internal/executor/WorkflowStateProcessor.java index 382fcb6d8..3ae124c59 100644 --- a/nflow-engine/src/main/java/io/nflow/engine/internal/executor/WorkflowStateProcessor.java +++ b/nflow-engine/src/main/java/io/nflow/engine/internal/executor/WorkflowStateProcessor.java @@ -202,15 +202,16 @@ private void runImpl() { private void logRetryableException(WorkflowState state, Throwable t) { ExceptionSeverity exceptionSeverity = state.getExceptionSeverity(t); - BiConsumer logMethod = getLogMethod(exceptionSeverity.logLevel); + BiConsumer logMethod = getLogMethod(exceptionSeverity.logLevel); if (exceptionSeverity.logStackTrace) { - logMethod.accept("Handler threw a retryable exception, trying again later.", t); + logMethod.accept("Handling state '{}' threw a retryable exception, trying again later.", new Object[] { state.name(), t }); } else { - logMethod.accept("Handler threw a retryable exception, trying again later. Message: {}", t.getMessage()); + logMethod.accept("Handling state '{}' threw a retryable exception, trying again later. Message: {}", + new Object[] { state.name(), t.getMessage() }); } } - private BiConsumer getLogMethod(Level logLevel) { + private BiConsumer getLogMethod(Level logLevel) { switch (logLevel) { case TRACE: return logger::trace; diff --git a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionSeverity.java b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionSeverity.java index 01cc2b849..101888088 100644 --- a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionSeverity.java +++ b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionSeverity.java @@ -5,7 +5,7 @@ import org.slf4j.event.Level; public class ExceptionSeverity { - public static final ExceptionSeverity DEFAULT = new ExceptionSeverity(ERROR, true); + public static final ExceptionSeverity DEFAULT = new ExceptionSeverity.Builder().build(); public final Level logLevel; public final boolean logStackTrace; From 2bd9356d8be26498da830215a6e2238dd2177d70 Mon Sep 17 00:00:00 2001 From: Edvard Fonsell Date: Mon, 1 Feb 2021 19:16:33 +0200 Subject: [PATCH 4/9] v2 --- .../internal/executor/WorkflowStateProcessor.java | 6 +++--- .../workflow/definition/WorkflowSettings.java | 13 +++++++++++++ .../engine/workflow/definition/WorkflowState.java | 11 ----------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/nflow-engine/src/main/java/io/nflow/engine/internal/executor/WorkflowStateProcessor.java b/nflow-engine/src/main/java/io/nflow/engine/internal/executor/WorkflowStateProcessor.java index 3ae124c59..0dba1d15b 100644 --- a/nflow-engine/src/main/java/io/nflow/engine/internal/executor/WorkflowStateProcessor.java +++ b/nflow-engine/src/main/java/io/nflow/engine/internal/executor/WorkflowStateProcessor.java @@ -175,7 +175,7 @@ private void runImpl() { } execution.setFailed(t); if (state.isRetryAllowed(t)) { - logRetryableException(state, t); + logRetryableException(settings, state, t); execution.setRetry(true); execution.setNextState(state); execution.setNextStateReason(getStackTrace(t)); @@ -200,8 +200,8 @@ private void runImpl() { logger.debug("Finished."); } - private void logRetryableException(WorkflowState state, Throwable t) { - ExceptionSeverity exceptionSeverity = state.getExceptionSeverity(t); + private void logRetryableException(WorkflowSettings settings, WorkflowState state, Throwable t) { + ExceptionSeverity exceptionSeverity = settings.getExceptionSeverity(state, t); BiConsumer logMethod = getLogMethod(exceptionSeverity.logLevel); if (exceptionSeverity.logStackTrace) { logMethod.accept("Handling state '{}' threw a retryable exception, trying again later.", new Object[] { state.name(), t }); diff --git a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowSettings.java b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowSettings.java index 0d3861860..36a8c5861 100644 --- a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowSettings.java +++ b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowSettings.java @@ -13,6 +13,7 @@ import java.util.Map; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BiFunction; import java.util.function.BooleanSupplier; import org.joda.time.DateTime; @@ -68,6 +69,7 @@ public class WorkflowSettings extends ModelObject { * Default priority for new workflow instances. */ public final short defaultPriority; + public final BiFunction exceptionSeveritySupplier; WorkflowSettings(Builder builder) { this.minErrorTransitionDelay = builder.minErrorTransitionDelay; @@ -80,6 +82,7 @@ public class WorkflowSettings extends ModelObject { this.historyDeletableAfter = builder.historyDeletableAfter; this.deleteHistoryCondition = builder.deleteHistoryCondition; this.defaultPriority = builder.defaultPriority; + this.exceptionSeveritySupplier = builder.exceptionSeveritySupplier; } /** @@ -98,6 +101,7 @@ public static class Builder { ReadablePeriod historyDeletableAfter; short defaultPriority = 0; BooleanSupplier deleteHistoryCondition = onAverageEveryNthExecution(100); + BiFunction exceptionSeveritySupplier = (s, t) -> ExceptionSeverity.DEFAULT; /** * Returns true randomly every n:th time. @@ -251,6 +255,12 @@ public Builder setDefaultPriority(short defaultPriority) { return this; } + public Builder setExceptionSeveritySupplier( + BiFunction exceptionSeveritySupplier) { + this.exceptionSeveritySupplier = exceptionSeveritySupplier; + return this; + } + /** * Create workflow settings object. * @@ -331,4 +341,7 @@ public Short getDefaultPriority() { return defaultPriority; } + public ExceptionSeverity getExceptionSeverity(WorkflowState state, Throwable t) { + return exceptionSeveritySupplier.apply(state, t); + } } diff --git a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowState.java b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowState.java index 8fdc18ce8..9a7c82d53 100644 --- a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowState.java +++ b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowState.java @@ -39,15 +39,4 @@ default String getDescription() { default boolean isRetryAllowed(Throwable thrown) { return !thrown.getClass().isAnnotationPresent(NonRetryable.class); } - - /** - * Return the severity of the exception thrown by the state execution. Using default means ERROR level logging with stack trace. - * - * @param thrown - * The thrown exception. - * @return Exception severity. - */ - default ExceptionSeverity getExceptionSeverity(Throwable thrown) { - return ExceptionSeverity.DEFAULT; - } } From 584d7aaab0aacbaa1e18a5d2077f0fd3bbc86bf1 Mon Sep 17 00:00:00 2001 From: Edvard Fonsell Date: Mon, 1 Feb 2021 19:28:23 +0200 Subject: [PATCH 5/9] refactor --- .../executor/WorkflowStateProcessor.java | 9 +++++---- .../workflow/definition/WorkflowSettings.java | 17 ++++++----------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/nflow-engine/src/main/java/io/nflow/engine/internal/executor/WorkflowStateProcessor.java b/nflow-engine/src/main/java/io/nflow/engine/internal/executor/WorkflowStateProcessor.java index 0dba1d15b..ffb0fe1ee 100644 --- a/nflow-engine/src/main/java/io/nflow/engine/internal/executor/WorkflowStateProcessor.java +++ b/nflow-engine/src/main/java/io/nflow/engine/internal/executor/WorkflowStateProcessor.java @@ -200,14 +200,15 @@ private void runImpl() { logger.debug("Finished."); } - private void logRetryableException(WorkflowSettings settings, WorkflowState state, Throwable t) { - ExceptionSeverity exceptionSeverity = settings.getExceptionSeverity(state, t); + private void logRetryableException(WorkflowSettings settings, WorkflowState state, Throwable thrown) { + ExceptionSeverity exceptionSeverity = settings.exceptionSeverityResolver.apply(thrown); BiConsumer logMethod = getLogMethod(exceptionSeverity.logLevel); if (exceptionSeverity.logStackTrace) { - logMethod.accept("Handling state '{}' threw a retryable exception, trying again later.", new Object[] { state.name(), t }); + logMethod.accept("Handling state '{}' threw a retryable exception, trying again later.", + new Object[] { state.name(), thrown }); } else { logMethod.accept("Handling state '{}' threw a retryable exception, trying again later. Message: {}", - new Object[] { state.name(), t.getMessage() }); + new Object[] { state.name(), thrown.getMessage() }); } } diff --git a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowSettings.java b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowSettings.java index 36a8c5861..c5b9a5be5 100644 --- a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowSettings.java +++ b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowSettings.java @@ -13,8 +13,8 @@ import java.util.Map; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicLong; -import java.util.function.BiFunction; import java.util.function.BooleanSupplier; +import java.util.function.Function; import org.joda.time.DateTime; import org.joda.time.LocalDateTime; @@ -69,7 +69,7 @@ public class WorkflowSettings extends ModelObject { * Default priority for new workflow instances. */ public final short defaultPriority; - public final BiFunction exceptionSeveritySupplier; + public final Function exceptionSeverityResolver; WorkflowSettings(Builder builder) { this.minErrorTransitionDelay = builder.minErrorTransitionDelay; @@ -82,7 +82,7 @@ public class WorkflowSettings extends ModelObject { this.historyDeletableAfter = builder.historyDeletableAfter; this.deleteHistoryCondition = builder.deleteHistoryCondition; this.defaultPriority = builder.defaultPriority; - this.exceptionSeveritySupplier = builder.exceptionSeveritySupplier; + this.exceptionSeverityResolver = builder.exceptionSeverityResolver; } /** @@ -101,7 +101,7 @@ public static class Builder { ReadablePeriod historyDeletableAfter; short defaultPriority = 0; BooleanSupplier deleteHistoryCondition = onAverageEveryNthExecution(100); - BiFunction exceptionSeveritySupplier = (s, t) -> ExceptionSeverity.DEFAULT; + Function exceptionSeverityResolver = thrown -> ExceptionSeverity.DEFAULT; /** * Returns true randomly every n:th time. @@ -255,9 +255,8 @@ public Builder setDefaultPriority(short defaultPriority) { return this; } - public Builder setExceptionSeveritySupplier( - BiFunction exceptionSeveritySupplier) { - this.exceptionSeveritySupplier = exceptionSeveritySupplier; + public Builder setExceptionSeverityResolver(Function exceptionSeverityResolver) { + this.exceptionSeverityResolver = exceptionSeverityResolver; return this; } @@ -340,8 +339,4 @@ public boolean deleteWorkflowInstanceHistory() { public Short getDefaultPriority() { return defaultPriority; } - - public ExceptionSeverity getExceptionSeverity(WorkflowState state, Throwable t) { - return exceptionSeveritySupplier.apply(state, t); - } } From 8bf4ab492aa82481713d475288b7e6f195bc5bdd Mon Sep 17 00:00:00 2001 From: Edvard Fonsell Date: Mon, 1 Feb 2021 21:07:10 +0200 Subject: [PATCH 6/9] v3 --- .../executor/WorkflowStateProcessor.java | 31 +++++++------- .../definition/ExceptionHandling.java | 42 +++++++++++++++++++ .../definition/ExceptionSeverity.java | 33 --------------- .../workflow/definition/WorkflowSettings.java | 16 ++++--- .../workflow/definition/WorkflowState.java | 3 ++ .../executor/WorkflowStateProcessorTest.java | 9 ++-- .../definition/WorkflowStateTest.java | 17 -------- .../tests/demo/workflow/NoRetryWorkflow.java | 24 +++++------ 8 files changed, 84 insertions(+), 91 deletions(-) create mode 100644 nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionHandling.java delete mode 100644 nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionSeverity.java diff --git a/nflow-engine/src/main/java/io/nflow/engine/internal/executor/WorkflowStateProcessor.java b/nflow-engine/src/main/java/io/nflow/engine/internal/executor/WorkflowStateProcessor.java index ffb0fe1ee..c10b4adaa 100644 --- a/nflow-engine/src/main/java/io/nflow/engine/internal/executor/WorkflowStateProcessor.java +++ b/nflow-engine/src/main/java/io/nflow/engine/internal/executor/WorkflowStateProcessor.java @@ -51,7 +51,7 @@ import io.nflow.engine.service.WorkflowDefinitionService; import io.nflow.engine.service.WorkflowInstanceService; import io.nflow.engine.workflow.definition.AbstractWorkflowDefinition; -import io.nflow.engine.workflow.definition.ExceptionSeverity; +import io.nflow.engine.workflow.definition.ExceptionHandling; import io.nflow.engine.workflow.definition.NextAction; import io.nflow.engine.workflow.definition.WorkflowSettings; import io.nflow.engine.workflow.definition.WorkflowState; @@ -169,19 +169,20 @@ private void runImpl() { } catch (StateVariableValueTooLongException e) { instance = rescheduleStateVariableValueTooLong(e, instance); saveInstanceState = false; - } catch (Throwable t) { - if (t instanceof UndeclaredThrowableException) { - t = t.getCause(); + } catch (Throwable thrown) { + if (thrown instanceof UndeclaredThrowableException) { + thrown = thrown.getCause(); } - execution.setFailed(t); - if (state.isRetryAllowed(t)) { - logRetryableException(settings, state, t); + execution.setFailed(thrown); + ExceptionHandling exceptionHandling = settings.exceptionAnalyzer.apply(state, thrown); + if (exceptionHandling.isRetryable) { + logRetryableException(exceptionHandling, state.name(), thrown); execution.setRetry(true); execution.setNextState(state); - execution.setNextStateReason(getStackTrace(t)); + execution.setNextStateReason(getStackTrace(thrown)); execution.handleRetryAfter(definition.getSettings().getErrorTransitionActivation(execution.getRetries()), definition); } else { - logger.error("Handler threw an exception and retrying is not allowed, going to failure state.", t); + logger.error("Handler threw an exception and retrying is not allowed, going to failure state.", thrown); execution.handleFailure(definition, "Handler threw an exception and retrying is not allowed"); } } finally { @@ -200,15 +201,13 @@ private void runImpl() { logger.debug("Finished."); } - private void logRetryableException(WorkflowSettings settings, WorkflowState state, Throwable thrown) { - ExceptionSeverity exceptionSeverity = settings.exceptionSeverityResolver.apply(thrown); - BiConsumer logMethod = getLogMethod(exceptionSeverity.logLevel); - if (exceptionSeverity.logStackTrace) { - logMethod.accept("Handling state '{}' threw a retryable exception, trying again later.", - new Object[] { state.name(), thrown }); + private void logRetryableException(ExceptionHandling exceptionHandling, String state, Throwable thrown) { + BiConsumer logMethod = getLogMethod(exceptionHandling.logLevel); + if (exceptionHandling.logStackTrace) { + logMethod.accept("Handling state '{}' threw a retryable exception, trying again later.", new Object[] { state, thrown }); } else { logMethod.accept("Handling state '{}' threw a retryable exception, trying again later. Message: {}", - new Object[] { state.name(), thrown.getMessage() }); + new Object[] { state, thrown.getMessage() }); } } diff --git a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionHandling.java b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionHandling.java new file mode 100644 index 000000000..5dd48470a --- /dev/null +++ b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionHandling.java @@ -0,0 +1,42 @@ +package io.nflow.engine.workflow.definition; + +import static org.slf4j.event.Level.ERROR; + +import org.slf4j.event.Level; + +public class ExceptionHandling { + public final boolean isRetryable; + public final Level logLevel; + public final boolean logStackTrace; + + ExceptionHandling(boolean isRetryable, Level logLevel, boolean logStackTrace) { + this.isRetryable = isRetryable; + this.logLevel = logLevel; + this.logStackTrace = logStackTrace; + } + + public static class Builder { + private boolean isRetryable = false; + private Level logLevel = ERROR; + private boolean logStackTrace = true; + + public Builder setRetryable(boolean isRetryable) { + this.isRetryable = isRetryable; + return this; + } + + public Builder setLogLevel(Level logLevel) { + this.logLevel = logLevel; + return this; + } + + public Builder setLogStackTrace(boolean logStackTrace) { + this.logStackTrace = logStackTrace; + return this; + } + + public ExceptionHandling build() { + return new ExceptionHandling(isRetryable, logLevel, logStackTrace); + } + } +} diff --git a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionSeverity.java b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionSeverity.java deleted file mode 100644 index 101888088..000000000 --- a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionSeverity.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.nflow.engine.workflow.definition; - -import static org.slf4j.event.Level.ERROR; - -import org.slf4j.event.Level; - -public class ExceptionSeverity { - public static final ExceptionSeverity DEFAULT = new ExceptionSeverity.Builder().build(); - public final Level logLevel; - public final boolean logStackTrace; - - ExceptionSeverity(Level logLevel, boolean logStackTrace) { - this.logLevel = logLevel; - this.logStackTrace = logStackTrace; - } - - public static class Builder { - private Level logLevel = ERROR; - private boolean logStackTrace = true; - - public void setLogLevel(Level logLevel) { - this.logLevel = logLevel; - } - - public void setLogStackTrace(boolean logStackTrace) { - this.logStackTrace = logStackTrace; - } - - public ExceptionSeverity build() { - return new ExceptionSeverity(logLevel, logStackTrace); - } - } -} diff --git a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowSettings.java b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowSettings.java index c5b9a5be5..f9c84e4f2 100644 --- a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowSettings.java +++ b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowSettings.java @@ -13,8 +13,8 @@ import java.util.Map; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BiFunction; import java.util.function.BooleanSupplier; -import java.util.function.Function; import org.joda.time.DateTime; import org.joda.time.LocalDateTime; @@ -69,7 +69,7 @@ public class WorkflowSettings extends ModelObject { * Default priority for new workflow instances. */ public final short defaultPriority; - public final Function exceptionSeverityResolver; + public final BiFunction exceptionAnalyzer; WorkflowSettings(Builder builder) { this.minErrorTransitionDelay = builder.minErrorTransitionDelay; @@ -82,7 +82,7 @@ public class WorkflowSettings extends ModelObject { this.historyDeletableAfter = builder.historyDeletableAfter; this.deleteHistoryCondition = builder.deleteHistoryCondition; this.defaultPriority = builder.defaultPriority; - this.exceptionSeverityResolver = builder.exceptionSeverityResolver; + this.exceptionAnalyzer = builder.exceptionAnalyzer; } /** @@ -101,7 +101,11 @@ public static class Builder { ReadablePeriod historyDeletableAfter; short defaultPriority = 0; BooleanSupplier deleteHistoryCondition = onAverageEveryNthExecution(100); - Function exceptionSeverityResolver = thrown -> ExceptionSeverity.DEFAULT; + // TODO: replace state.isRetryAllowed(thrown) with !thrown.getClass().isAnnotationPresent(NonRetryable.class) in the next + // major release + @SuppressWarnings("deprecation") + BiFunction exceptionAnalyzer = (state, thrown) -> new ExceptionHandling.Builder() + .setRetryable(state.isRetryAllowed(thrown)).build(); /** * Returns true randomly every n:th time. @@ -255,8 +259,8 @@ public Builder setDefaultPriority(short defaultPriority) { return this; } - public Builder setExceptionSeverityResolver(Function exceptionSeverityResolver) { - this.exceptionSeverityResolver = exceptionSeverityResolver; + public Builder setExceptionAnalyzer(BiFunction exceptionAnalyzer) { + this.exceptionAnalyzer = exceptionAnalyzer; return this; } diff --git a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowState.java b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowState.java index 9a7c82d53..56d4d1236 100644 --- a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowState.java +++ b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowState.java @@ -35,7 +35,10 @@ default String getDescription() { * @param thrown * The thrown exception. * @return True if the state can be retried. + * @deprecated This will be removed in the next major release. Use new WorkflowSettings.Builder().setExceptionAnalyzer(...) + * instead. */ + @Deprecated default boolean isRetryAllowed(Throwable thrown) { return !thrown.getClass().isAnnotationPresent(NonRetryable.class); } diff --git a/nflow-engine/src/test/java/io/nflow/engine/internal/executor/WorkflowStateProcessorTest.java b/nflow-engine/src/test/java/io/nflow/engine/internal/executor/WorkflowStateProcessorTest.java index fee18fab8..ee169844b 100644 --- a/nflow-engine/src/test/java/io/nflow/engine/internal/executor/WorkflowStateProcessorTest.java +++ b/nflow-engine/src/test/java/io/nflow/engine/internal/executor/WorkflowStateProcessorTest.java @@ -96,6 +96,7 @@ import io.nflow.engine.service.WorkflowInstanceInclude; import io.nflow.engine.service.WorkflowInstanceService; import io.nflow.engine.workflow.curated.BulkWorkflow; +import io.nflow.engine.workflow.definition.ExceptionHandling; import io.nflow.engine.workflow.definition.Mutable; import io.nflow.engine.workflow.definition.NextAction; import io.nflow.engine.workflow.definition.StateExecution; @@ -1290,7 +1291,8 @@ public NextAction start(StateExecution execution) { public static class NonRetryableWorkflow extends WorkflowDefinition { protected NonRetryableWorkflow() { - super("non-retryable", State.start, State.end); + super("non-retryable", State.start, State.end, new WorkflowSettings.Builder() + .setExceptionAnalyzer((s, t) -> new ExceptionHandling.Builder().setRetryable(false).build()).build()); } public static enum State implements WorkflowState { @@ -1306,11 +1308,6 @@ private State(WorkflowStateType stateType) { public WorkflowStateType getType() { return stateType; } - - @Override - public boolean isRetryAllowed(Throwable thrown) { - return false; - } } public NextAction start(@SuppressWarnings("unused") StateExecution execution) { diff --git a/nflow-engine/src/test/java/io/nflow/engine/workflow/definition/WorkflowStateTest.java b/nflow-engine/src/test/java/io/nflow/engine/workflow/definition/WorkflowStateTest.java index 93c2c0f80..7da649f06 100644 --- a/nflow-engine/src/test/java/io/nflow/engine/workflow/definition/WorkflowStateTest.java +++ b/nflow-engine/src/test/java/io/nflow/engine/workflow/definition/WorkflowStateTest.java @@ -2,8 +2,6 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import org.junit.jupiter.api.Test; @@ -16,16 +14,6 @@ public void getDescriptionReturnsNameByDefault() { assertThat(state.getDescription(), is(state.name())); } - @Test - public void isRetryAllowedReturnsFalseWhenThrowableIsAnnotatedWithNonRetryable() { - assertFalse(state.isRetryAllowed(new NonRetryableException())); - } - - @Test - public void isRetryAllowedReturnsTrueWhenThrowableIsNotAnnotatedWithNonRetryable() { - assertTrue(state.isRetryAllowed(new RuntimeException())); - } - static class TestWorkflowState implements WorkflowState { @Override @@ -38,9 +26,4 @@ public WorkflowStateType getType() { return WorkflowStateType.normal; } } - - @NonRetryable - static class NonRetryableException extends RuntimeException { - private static final long serialVersionUID = 1L; - } } diff --git a/nflow-tests/src/main/java/io/nflow/tests/demo/workflow/NoRetryWorkflow.java b/nflow-tests/src/main/java/io/nflow/tests/demo/workflow/NoRetryWorkflow.java index ca011729e..59f890313 100644 --- a/nflow-tests/src/main/java/io/nflow/tests/demo/workflow/NoRetryWorkflow.java +++ b/nflow-tests/src/main/java/io/nflow/tests/demo/workflow/NoRetryWorkflow.java @@ -5,12 +5,16 @@ import static io.nflow.engine.workflow.definition.WorkflowStateType.normal; import static io.nflow.engine.workflow.definition.WorkflowStateType.start; +import java.util.function.BiFunction; + import org.springframework.stereotype.Component; +import io.nflow.engine.workflow.definition.ExceptionHandling; import io.nflow.engine.workflow.definition.NextAction; import io.nflow.engine.workflow.definition.NonRetryable; import io.nflow.engine.workflow.definition.StateExecution; import io.nflow.engine.workflow.definition.WorkflowDefinition; +import io.nflow.engine.workflow.definition.WorkflowSettings; import io.nflow.engine.workflow.definition.WorkflowState; import io.nflow.engine.workflow.definition.WorkflowStateType; import io.nflow.tests.demo.workflow.NoRetryWorkflow.State; @@ -21,23 +25,17 @@ public class NoRetryWorkflow extends WorkflowDefinition { public static final String TYPE = "noRetry"; public static enum State implements WorkflowState { - begin(start, "Retry always disabled for this state", false), // + begin(start, "Retry always disabled for this state"), // process(normal, "Retry disabled for exceptions annotated with @NonRetryable"), // done(end, "End state"), // error(manual, "Error state"); private WorkflowStateType type; private String description; - private boolean isRetryAllowed; private State(WorkflowStateType type, String description) { - this(type, description, true); - } - - private State(WorkflowStateType type, String description, boolean isRetryAllowed) { this.type = type; this.description = description; - this.isRetryAllowed = isRetryAllowed; } @Override @@ -49,20 +47,20 @@ public WorkflowStateType getType() { public String getDescription() { return description; } - - @Override - public boolean isRetryAllowed(Throwable thrown) { - return isRetryAllowed && WorkflowState.super.isRetryAllowed(thrown); - } } public NoRetryWorkflow() { - super(TYPE, State.begin, State.error); + super(TYPE, State.begin, State.error, new WorkflowSettings.Builder().setExceptionAnalyzer(exceptionAnalyzer()).build()); setDescription("Workflow demonstrating how to avoid automatic retry"); permit(State.begin, State.process); permit(State.process, State.done); } + private static BiFunction exceptionAnalyzer() { + return (s, t) -> new ExceptionHandling.Builder() + .setRetryable(s != State.begin && !t.getClass().isAnnotationPresent(NonRetryable.class)).build(); + } + public NextAction begin(@SuppressWarnings("unused") StateExecution execution) { throw new RuntimeException(); } From 9dfda8cc54a660299deed7e7195ab903f34b9b8c Mon Sep 17 00:00:00 2001 From: Edvard Fonsell Date: Mon, 1 Feb 2021 21:22:24 +0200 Subject: [PATCH 7/9] exceptions are retryable by default, add tests --- CHANGELOG.md | 5 +++ .../definition/ExceptionHandling.java | 2 +- .../workflow/definition/WorkflowSettings.java | 2 +- .../definition/WorkflowSettingsTest.java | 43 ++++++++++++++++--- 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf74846fc..51a4aa74a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,11 +3,16 @@ **Highlights** - Support updating workflow instance business key. - Support for searching workflow instances by state variable key and value. +- Control retryable exception handling via `WorkflowSettings` (replaces deprecated `WorkflowState.isRetryAllowed(...)`) **Details** - `nflow-engine` - `WorkflowInstanceService.updateWorkflowInstance` can now be used to update business key of the workflow instance. - Use `QueryWorkflowInstances.setStateVariable` to limit search query by state variable name and key. Only the latest value of the state variable of the workflow instance is used. + - Control retryable exception handling via `WorkflowSettings.Builder.setExceptionAnalyzer(...)` / `ExceptionHandling`: + - Allow setting the log entry level of the retryable exception thrown by state processing. + - Allow controlling whether the stack trace of the retryable exception is logged or not. + - Allow controlling whether the exception is retryable or not (replaces deprecated `WorkflowState.isRetryAllowed(...)`). - `nflow-rest-api-common`, `nflow-rest-api-jax-rs`, `nflow-rest-api-spring-web` - `UpdateWorkflowInstanceRequest.businessKey` field was added to support updating workflow instance business key via REST API. - Added support for new query parameters `stateVariableKey` and `stateVariableValue` to `GET /v1/workflow-instance` to limit search query by state variable name and key. Only the latest value of the state variable of the workflow instance is used. diff --git a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionHandling.java b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionHandling.java index 5dd48470a..e6d6743fc 100644 --- a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionHandling.java +++ b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionHandling.java @@ -16,7 +16,7 @@ public class ExceptionHandling { } public static class Builder { - private boolean isRetryable = false; + private boolean isRetryable = true; private Level logLevel = ERROR; private boolean logStackTrace = true; diff --git a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowSettings.java b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowSettings.java index f9c84e4f2..a99af937a 100644 --- a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowSettings.java +++ b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowSettings.java @@ -88,7 +88,6 @@ public class WorkflowSettings extends ModelObject { /** * Builder for workflow settings. */ - @SuppressFBWarnings(value = "MDM_RANDOM_SEED", justification = "Random does not need to be secure") public static class Builder { int maxErrorTransitionDelay = (int) DAYS.toMillis(1); @@ -113,6 +112,7 @@ public static class Builder { * @param n The frequency of returning true. * @return Producer of boolean values */ + @SuppressFBWarnings(value = "MDM_RANDOM_SEED", justification = "Random does not need to be secure here") public static BooleanSupplier onAverageEveryNthExecution(int n) { return () -> ThreadLocalRandom.current().nextInt(n) == 0; } diff --git a/nflow-engine/src/test/java/io/nflow/engine/workflow/definition/WorkflowSettingsTest.java b/nflow-engine/src/test/java/io/nflow/engine/workflow/definition/WorkflowSettingsTest.java index d4995e0d5..8879e3517 100644 --- a/nflow-engine/src/test/java/io/nflow/engine/workflow/definition/WorkflowSettingsTest.java +++ b/nflow-engine/src/test/java/io/nflow/engine/workflow/definition/WorkflowSettingsTest.java @@ -16,6 +16,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.slf4j.event.Level; + public class WorkflowSettingsTest { DateTime now = new DateTime(2014, 10, 22, 20, 44, 0); @@ -46,12 +48,13 @@ public void verifyConstantDefaultValues() { public void errorTransitionDelayIsBetweenMinAndMaxDelay() { int maxDelay = 1_000_000; int minDelay = 1000; - WorkflowSettings s = new WorkflowSettings.Builder().setMinErrorTransitionDelay(minDelay).setMaxErrorTransitionDelay(maxDelay).build(); + WorkflowSettings s = new WorkflowSettings.Builder().setMinErrorTransitionDelay(minDelay).setMaxErrorTransitionDelay(maxDelay) + .build(); long prevDelay = 0; - for(int retryCount = 0 ; retryCount < 100 ; retryCount++) { - long delay = s.getErrorTransitionActivation(retryCount).getMillis() - now.getMillis(); - assertThat(delay, greaterThanOrEqualTo((long)minDelay)); - assertThat(delay, lessThanOrEqualTo((long)maxDelay)); + for (int retryCount = 0; retryCount < 100; retryCount++) { + long delay = s.getErrorTransitionActivation(retryCount).getMillis() - now.getMillis(); + assertThat(delay, greaterThanOrEqualTo((long) minDelay)); + assertThat(delay, lessThanOrEqualTo((long) maxDelay)); assertThat(delay, greaterThanOrEqualTo(prevDelay)); prevDelay = delay; } @@ -101,4 +104,34 @@ public void oncePerDaySupplierWorks() { assertThat(supplier.getAsBoolean(), is(false)); } + @Test + public void defaultExceptionAnalyzer() { + WorkflowSettings s = new WorkflowSettings.Builder().build(); + + ExceptionHandling exceptionHandling = s.exceptionAnalyzer.apply(TestWorkflow.State.begin, new Throwable()); + assertThat(exceptionHandling.isRetryable, is(true)); + assertThat(exceptionHandling.logLevel, is(Level.ERROR)); + assertThat(exceptionHandling.logStackTrace, is(true)); + + exceptionHandling = s.exceptionAnalyzer.apply(TestWorkflow.State.begin, new NonRetryableException()); + assertThat(exceptionHandling.isRetryable, is(false)); + assertThat(exceptionHandling.logLevel, is(Level.ERROR)); + assertThat(exceptionHandling.logStackTrace, is(true)); + } + + @Test + public void customExceptionAnalyzer() { + WorkflowSettings s = new WorkflowSettings.Builder().setExceptionAnalyzer((state, thrown) -> new ExceptionHandling.Builder() + .setLogLevel(Level.INFO).setRetryable(true).setLogStackTrace(false).build()).build(); + + ExceptionHandling exceptionHandling = s.exceptionAnalyzer.apply(TestWorkflow.State.begin, new NonRetryableException()); + assertThat(exceptionHandling.isRetryable, is(true)); + assertThat(exceptionHandling.logLevel, is(Level.INFO)); + assertThat(exceptionHandling.logStackTrace, is(false)); + } + + @NonRetryable + static class NonRetryableException extends RuntimeException { + private static final long serialVersionUID = 1L; + } } From 0af374d19665de39dbcc09cf4752c7a8e1c77365 Mon Sep 17 00:00:00 2001 From: Edvard Fonsell Date: Mon, 1 Feb 2021 23:49:54 +0200 Subject: [PATCH 8/9] add javadocs --- .../definition/ExceptionHandling.java | 41 +++++++++++++++++++ .../workflow/definition/WorkflowSettings.java | 10 +++++ 2 files changed, 51 insertions(+) diff --git a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionHandling.java b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionHandling.java index e6d6743fc..11eeacd3d 100644 --- a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionHandling.java +++ b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/ExceptionHandling.java @@ -4,9 +4,21 @@ import org.slf4j.event.Level; +/** + * Controls how an exception thrown by a state method should be handled by the workflow state processor. + */ public class ExceptionHandling { + /** + * True when the state method processing should be retried. + */ public final boolean isRetryable; + /** + * The log entry level for logging the exception. + */ public final Level logLevel; + /** + * True when the exception stack trace of the exception should be logged. False to log only exception message. + */ public final boolean logStackTrace; ExceptionHandling(boolean isRetryable, Level logLevel, boolean logStackTrace) { @@ -15,26 +27,55 @@ public class ExceptionHandling { this.logStackTrace = logStackTrace; } + /** + * Builder for exception handling settings. + */ public static class Builder { private boolean isRetryable = true; private Level logLevel = ERROR; private boolean logStackTrace = true; + /** + * Set if state method processing is retryable or not. + * + * @param isRetryable + * True is state method processing should be retried. + * @return This. + */ public Builder setRetryable(boolean isRetryable) { this.isRetryable = isRetryable; return this; } + /** + * Set the log entry level. + * + * @param logLevel + * The log entry level. + * @return This. + */ public Builder setLogLevel(Level logLevel) { this.logLevel = logLevel; return this; } + /** + * Set if exception stack trace should be logged or not. + * + * @param logStackTrace + * True to log the exception stack trace, false to log the exception message only. + * @return This. + */ public Builder setLogStackTrace(boolean logStackTrace) { this.logStackTrace = logStackTrace; return this; } + /** + * Create the exception handling object. + * + * @return Exception handling. + */ public ExceptionHandling build() { return new ExceptionHandling(isRetryable, logLevel, logStackTrace); } diff --git a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowSettings.java b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowSettings.java index a99af937a..d5e2cb9d5 100644 --- a/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowSettings.java +++ b/nflow-engine/src/main/java/io/nflow/engine/workflow/definition/WorkflowSettings.java @@ -69,6 +69,9 @@ public class WorkflowSettings extends ModelObject { * Default priority for new workflow instances. */ public final short defaultPriority; + /** + * Exception analyzer controls how an exception thrown by a state method should be handled. + */ public final BiFunction exceptionAnalyzer; WorkflowSettings(Builder builder) { @@ -259,6 +262,13 @@ public Builder setDefaultPriority(short defaultPriority) { return this; } + /** + * Set the exception analyzer function. + * + * @param exceptionAnalyzer + * The exception analyzer function. + * @return this. + */ public Builder setExceptionAnalyzer(BiFunction exceptionAnalyzer) { this.exceptionAnalyzer = exceptionAnalyzer; return this; From a6ee8ca31771a825ef805fd02bd0705a9d583a6b Mon Sep 17 00:00:00 2001 From: Edvard Fonsell Date: Tue, 2 Feb 2021 16:42:36 +0200 Subject: [PATCH 9/9] update changelog --- CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51a4aa74a..8b4c7e8e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,16 +3,16 @@ **Highlights** - Support updating workflow instance business key. - Support for searching workflow instances by state variable key and value. -- Control retryable exception handling via `WorkflowSettings` (replaces deprecated `WorkflowState.isRetryAllowed(...)`) +- Control retrying and logging of an exception thrown by a state method via `WorkflowSettings` (replaces deprecated `WorkflowState.isRetryAllowed(...)`) **Details** - `nflow-engine` - `WorkflowInstanceService.updateWorkflowInstance` can now be used to update business key of the workflow instance. - Use `QueryWorkflowInstances.setStateVariable` to limit search query by state variable name and key. Only the latest value of the state variable of the workflow instance is used. - - Control retryable exception handling via `WorkflowSettings.Builder.setExceptionAnalyzer(...)` / `ExceptionHandling`: - - Allow setting the log entry level of the retryable exception thrown by state processing. - - Allow controlling whether the stack trace of the retryable exception is logged or not. - - Allow controlling whether the exception is retryable or not (replaces deprecated `WorkflowState.isRetryAllowed(...)`). + - Control retrying and logging of an exception thrown by a state method via `WorkflowSettings.Builder.setExceptionAnalyzer(...)` / `ExceptionHandling`: + - Control whether the exception is considered retryable or not (replaces deprecated `WorkflowState.isRetryAllowed(...)`). + - Control which log level is used to log the retryable exception. + - Control whether the stack trace of the retryable exception is logged or not. - `nflow-rest-api-common`, `nflow-rest-api-jax-rs`, `nflow-rest-api-spring-web` - `UpdateWorkflowInstanceRequest.businessKey` field was added to support updating workflow instance business key via REST API. - Added support for new query parameters `stateVariableKey` and `stateVariableValue` to `GET /v1/workflow-instance` to limit search query by state variable name and key. Only the latest value of the state variable of the workflow instance is used.