diff --git a/cloudplatform/resilience4j/src/main/java/com/sap/cloud/sdk/cloudplatform/resilience4j/DefaultCircuitBreakerProvider.java b/cloudplatform/resilience4j/src/main/java/com/sap/cloud/sdk/cloudplatform/resilience4j/DefaultCircuitBreakerProvider.java index 7bac57f93..43a2c15d3 100644 --- a/cloudplatform/resilience4j/src/main/java/com/sap/cloud/sdk/cloudplatform/resilience4j/DefaultCircuitBreakerProvider.java +++ b/cloudplatform/resilience4j/src/main/java/com/sap/cloud/sdk/cloudplatform/resilience4j/DefaultCircuitBreakerProvider.java @@ -66,7 +66,6 @@ public CircuitBreaker getCircuitBreaker( @Nonnull final ResilienceConfiguration return circuitBreaker; } - @SuppressWarnings( "PMD.PreserveStackTrace" ) // The circuit breaker stack-trace doesn't contain any info @Nonnull @Override public Callable decorateCallable( @@ -83,9 +82,19 @@ public Callable decorateCallable( return CircuitBreaker.decorateCallable(circuitBreaker, callable).call(); } catch( CallNotPermittedException e ) { - log.debug("Circuit breaker '{}' is open, call not permitted.", circuitBreaker.getName()); + val message = + "CircuitBreaker '" + circuitBreaker.getName() + "' is OPEN and does not permit further calls"; + log.debug(message); val lastException = lastExceptions.get(circuitBreaker.getName()); - throw new ResilienceRuntimeException(lastException != null ? lastException : e); + if( lastException == null ) { + throw new ResilienceRuntimeException(message, e); + } + val resilienceRuntimeException = + new ResilienceRuntimeException( + message + ". Triggered by " + lastException.getMessage(), + lastException); + resilienceRuntimeException.addSuppressed(e); + throw resilienceRuntimeException; } }; } diff --git a/cloudplatform/resilience4j/src/test/java/com/sap/cloud/sdk/cloudplatform/resilience4j/CircuitBreakerTest.java b/cloudplatform/resilience4j/src/test/java/com/sap/cloud/sdk/cloudplatform/resilience4j/CircuitBreakerTest.java index ec9bd496c..333b9fdba 100644 --- a/cloudplatform/resilience4j/src/test/java/com/sap/cloud/sdk/cloudplatform/resilience4j/CircuitBreakerTest.java +++ b/cloudplatform/resilience4j/src/test/java/com/sap/cloud/sdk/cloudplatform/resilience4j/CircuitBreakerTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.catchThrowable; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -21,6 +22,7 @@ import com.sap.cloud.sdk.cloudplatform.resilience.ResilienceRuntimeException; import com.sap.cloud.sdk.cloudplatform.thread.exception.ThreadContextExecutionException; +import io.github.resilience4j.circuitbreaker.CallNotPermittedException; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -76,12 +78,22 @@ void testCircuitBreakerOpens() assertThat(callResults).hasSize(attemptedInvocations); assertThat(callResults).startsWith(attempts.toArray(new Boolean[0])); - assertThatThrownBy(wrappedCallable::call) + Throwable thrown = catchThrowable(wrappedCallable::call); + + assertThat(thrown) .isExactlyInstanceOf(ResilienceRuntimeException.class) .hasCauseExactlyInstanceOf(ThreadContextExecutionException.class) .hasRootCauseExactlyInstanceOf(Exception.class) .hasMessage( - "com.sap.cloud.sdk.cloudplatform.thread.exception.ThreadContextExecutionException: java.lang.Exception: Simulated failure, attempt nr: 3"); + "CircuitBreaker 'circuitbreaker.test.2' is OPEN and does not permit further calls. Triggered by java.lang.Exception: Simulated failure, attempt nr: 3"); + + assertThat(thrown.getCause()).hasMessage("java.lang.Exception: Simulated failure, attempt nr: 3"); + + assertThat(thrown.getSuppressed().length).isEqualTo(1); + Throwable suppressed = thrown.getSuppressed()[0]; + assertThat(suppressed) + .isExactlyInstanceOf(CallNotPermittedException.class) + .hasMessage("CircuitBreaker 'circuitbreaker.test.2' is OPEN and does not permit further calls"); verify(callable, times(circuitBreakerConfiguration.closedBufferSize())).call(); } diff --git a/release_notes.md b/release_notes.md index 3112514a9..e150016a6 100644 --- a/release_notes.md +++ b/release_notes.md @@ -16,7 +16,7 @@ ### 📈 Improvements -- +- When the circuit breaker opens, the resulting `ResilienceRuntimeException` will have the original `CallNotPermittedException` from the circuit breaker stored as a suppressed exception. ### 🐛 Fixed Issues