From e3a2bb2ab6ac3ebe73d1f95c75e51f7ad399bdeb Mon Sep 17 00:00:00 2001 From: Nicola Dardanis Date: Thu, 1 Feb 2024 10:29:07 +0100 Subject: [PATCH 1/2] GH-150: adjust feign retryer configuration (#151) Co-authored-by: Nicola Dardanis --- core/src/main/java/com/adobe/aio/util/feign/FeignUtil.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/main/java/com/adobe/aio/util/feign/FeignUtil.java b/core/src/main/java/com/adobe/aio/util/feign/FeignUtil.java index b3bac24c..3c449960 100644 --- a/core/src/main/java/com/adobe/aio/util/feign/FeignUtil.java +++ b/core/src/main/java/com/adobe/aio/util/feign/FeignUtil.java @@ -11,10 +11,13 @@ */ package com.adobe.aio.util.feign; +import static java.util.concurrent.TimeUnit.SECONDS; + import com.adobe.aio.util.JacksonUtil; import feign.Feign; import feign.Logger.Level; import feign.Request; +import feign.Retryer; import feign.form.FormEncoder; import feign.jackson.JacksonDecoder; import feign.jackson.JacksonEncoder; @@ -41,6 +44,7 @@ public static Feign.Builder getBaseBuilder() { .logLevel(Level.NONE) //.logLevel(Level.FULL) // use this instead when debugging .decode404() + .retryer(new Retryer.Default(1000, SECONDS.toMillis(4), 3)) .errorDecoder(new IOErrorDecoder()) .options(new Request.Options(DEFAULT_CONNECT_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS, DEFAULT_READ_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS, true)); From 5334e8a4591e791aef6821d24fd8cc8e3f12202f Mon Sep 17 00:00:00 2001 From: Nicola Dardanis Date: Fri, 2 Feb 2024 16:09:58 +0100 Subject: [PATCH 2/2] GH-150: add possibility to configure FeignBuilder from environment variables --- .../com/adobe/aio/util/feign/FeignUtil.java | 156 ++++++++++++++++-- .../adobe/aio/util/feign/FeignUtilTest.java | 64 +++++++ 2 files changed, 208 insertions(+), 12 deletions(-) create mode 100644 core/src/test/java/com/adobe/aio/util/feign/FeignUtilTest.java diff --git a/core/src/main/java/com/adobe/aio/util/feign/FeignUtil.java b/core/src/main/java/com/adobe/aio/util/feign/FeignUtil.java index 3c449960..579b1017 100644 --- a/core/src/main/java/com/adobe/aio/util/feign/FeignUtil.java +++ b/core/src/main/java/com/adobe/aio/util/feign/FeignUtil.java @@ -15,6 +15,7 @@ import com.adobe.aio.util.JacksonUtil; import feign.Feign; +import feign.Logger; import feign.Logger.Level; import feign.Request; import feign.Retryer; @@ -23,12 +24,44 @@ import feign.jackson.JacksonEncoder; import feign.optionals.OptionalDecoder; import feign.slf4j.Slf4jLogger; +import java.lang.reflect.InvocationTargetException; +import java.util.Map; import java.util.concurrent.TimeUnit; public class FeignUtil { - public static final int DEFAULT_CONNECT_TIMEOUT_IN_SECONDS = 10; - public static final int DEFAULT_READ_TIMEOUT_IN_SECONDS = 60; + /** + * Environment variable to set the Feign logger class. + * The value should be the fully qualified class name of the logger to use. + * The name should be the result of calling {@link Class#getName()} on the logger class. + * @see feign.Logger + */ + public static final String AIO_FEIGN_LOGGER_CLASS = "AIO_FEIGN_LOGGER_CLASS"; + /** + * Environment variable to set the log level for Feign clients. + * The value should be one of the following: NONE, BASIC, HEADERS, FULL + * @see Level + */ + public static final String AIO_FEIGN_LOG_LEVEL = "AIO_FEIGN_LOG_LEVEL"; + /** + * Environment variable to set the retry period in milliseconds for Feign clients, using a {@link Retryer.Default} + */ + public static final String AIO_FEIGN_RETRY_PERIOD = "AIO_FEIGN_RETRY_PERIOD"; + /** + * Environment variable to set the max attempts for Feign clients, using a {@link Retryer.Default} + */ + public static final String AIO_FEIGN_RETRY_MAX_ATTEMPTS = "AIO_FEIGN_RETRY_MAX_ATTEMPTS"; + /** + * Environment variable to set the max period in seconds for Feign clients, using a {@link Retryer.Default} + */ + public static final String AIO_FEIGN_RETRY_MAX_PERIOD = "AIO_FEIGN_RETRY_MAX_PERIOD"; + + + private static final int DEFAULT_CONNECT_TIMEOUT_IN_SECONDS = 10; + private static final int DEFAULT_READ_TIMEOUT_IN_SECONDS = 60; + public static final long DEFAULT_RETRY_PERIOD_IN_SECONDS = 1000L; + public static final int DEFAULT_MAX_ATTEMPTS = 3; + public static final long DEFAULT_MAX_PERIOD_IN_SECONDS = 4L; private FeignUtil() { } @@ -38,16 +71,7 @@ private FeignUtil() { * our global read and time out options */ public static Feign.Builder getBaseBuilder() { - return Feign.builder() - .logger(new Slf4jLogger()) - //.logLevel(Level.BASIC) - .logLevel(Level.NONE) - //.logLevel(Level.FULL) // use this instead when debugging - .decode404() - .retryer(new Retryer.Default(1000, SECONDS.toMillis(4), 3)) - .errorDecoder(new IOErrorDecoder()) - .options(new Request.Options(DEFAULT_CONNECT_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS, - DEFAULT_READ_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS, true)); + return new ConfigBuilder().systemEnv().build(); } /** @@ -71,4 +95,112 @@ public static Feign.Builder getBuilderWithFormEncoder() { .encoder(new FormEncoder()); } + public static class ConfigBuilder { + private Class loggerClass; + private Level logLevel; + private long retryPeriod; + private int maxAttempts; + private long maxPeriod; + + public ConfigBuilder() { + } + + /** + * @param loggerClass the logger class to use + * @see feign.Logger + * @return the current instance of the builder for chaining + */ + public ConfigBuilder loggerClass(final Class loggerClass) { + this.loggerClass = loggerClass; + return this; + } + + public ConfigBuilder logLevel(final Level logLevel) { + this.logLevel = logLevel; + return this; + } + + public ConfigBuilder retryPeriod(final long retryPeriod) { + this.retryPeriod = retryPeriod; + return this; + } + + public ConfigBuilder maxAttempts(final int maxAttempts) { + this.maxAttempts = maxAttempts; + return this; + } + + public ConfigBuilder maxPeriod(final long maxPeriod) { + this.maxPeriod = maxPeriod; + return this; + } + + public Class getLoggerClass() { + return loggerClass; + } + + public Level getLogLevel() { + return logLevel; + } + + public long getRetryPeriod() { + return retryPeriod; + } + + public int getMaxAttempts() { + return maxAttempts; + } + + public long getMaxPeriod() { + return maxPeriod; + } + + public ConfigBuilder configMap(final Map configMap) { + try { + return this + .loggerClass((Class) Class.forName( + configMap.getOrDefault(AIO_FEIGN_LOGGER_CLASS, Slf4jLogger.class.getName()))) + .logLevel(Level.valueOf( + configMap.getOrDefault(AIO_FEIGN_LOG_LEVEL, Level.NONE.name()))) + .retryPeriod(Long.parseLong( + configMap.getOrDefault(AIO_FEIGN_RETRY_PERIOD, String.valueOf(DEFAULT_RETRY_PERIOD_IN_SECONDS)))) + .maxAttempts(Integer.parseInt( + configMap.getOrDefault(AIO_FEIGN_RETRY_MAX_ATTEMPTS, String.valueOf(DEFAULT_MAX_ATTEMPTS)))) + .maxPeriod(Long.parseLong( + configMap.getOrDefault(AIO_FEIGN_RETRY_MAX_PERIOD, String.valueOf(DEFAULT_MAX_PERIOD_IN_SECONDS)))); + } catch (Exception e) { + throw new IllegalArgumentException("Provided Feign configuration is invalid", e); + } + } + + public ConfigBuilder systemEnv() { + return configMap(System.getenv()); + } + + public Feign.Builder build() { + try { + return Feign.builder() + .logger(this.loggerClass.getConstructor().newInstance()) + .logLevel(this.logLevel) + .decode404() + .retryer( + new Retryer.Default( + this.retryPeriod, SECONDS.toMillis(this.maxPeriod), this.maxAttempts)) + .errorDecoder(new IOErrorDecoder()) + .options( + new Request.Options( + DEFAULT_CONNECT_TIMEOUT_IN_SECONDS, + TimeUnit.SECONDS, + DEFAULT_READ_TIMEOUT_IN_SECONDS, + TimeUnit.SECONDS, + true)); + } catch (NoSuchMethodException + | InstantiationException + | IllegalAccessException + | InvocationTargetException e) { + throw new IllegalArgumentException("Provided Feign configuration is invalid", e); + } + } + } + } diff --git a/core/src/test/java/com/adobe/aio/util/feign/FeignUtilTest.java b/core/src/test/java/com/adobe/aio/util/feign/FeignUtilTest.java new file mode 100644 index 00000000..53ff5338 --- /dev/null +++ b/core/src/test/java/com/adobe/aio/util/feign/FeignUtilTest.java @@ -0,0 +1,64 @@ +package com.adobe.aio.util.feign; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import feign.Logger.Level; +import feign.slf4j.Slf4jLogger; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +public class FeignUtilTest { + + @Test + public void testConfigBuilderConfigMapWithEmptyMap() { + Map emptyConfigMap = new HashMap<>(); + assertDoesNotThrow(() -> new FeignUtil.ConfigBuilder().configMap(emptyConfigMap).build()); + } + + @Test + public void testConfigBuilderConfigMapWithNull() { + assertThrows(IllegalArgumentException.class, () -> new FeignUtil.ConfigBuilder().configMap(null).build()); + } + + @Test + public void testConfigBuilderWithDefaults() { + FeignUtil.ConfigBuilder configBuilder = new FeignUtil.ConfigBuilder().configMap(new HashMap<>()); + assertEquals(Slf4jLogger.class, configBuilder.getLoggerClass()); + assertEquals(Level.NONE, configBuilder.getLogLevel()); + assertEquals(FeignUtil.DEFAULT_RETRY_PERIOD_IN_SECONDS, configBuilder.getRetryPeriod()); + assertEquals(FeignUtil.DEFAULT_MAX_ATTEMPTS, configBuilder.getMaxAttempts()); + assertEquals(FeignUtil.DEFAULT_MAX_PERIOD_IN_SECONDS, configBuilder.getMaxPeriod()); + } + + @ParameterizedTest + @ValueSource(strings = {"feign.Logger$JavaLogger", "feign.slf4j.Slf4jLogger", "feign.Logger$NoOpLogger", "feign.Logger$ErrorLogger"}) + public void testConfigBuilderWithLoggerNames(String loggerName) { + Map configMap = new HashMap<>(); + configMap.put(FeignUtil.AIO_FEIGN_LOGGER_CLASS, loggerName); + FeignUtil.ConfigBuilder configBuilder = new FeignUtil.ConfigBuilder().configMap(configMap); + assertEquals(loggerName, configBuilder.getLoggerClass().getName()); + assertDoesNotThrow(configBuilder::build); + } + + @ParameterizedTest + @ValueSource(strings = {"NONE", "BASIC", "HEADERS", "FULL"}) + public void testConfigBuilderWithLogLevels(String logLevel) { + Map configMap = new HashMap<>(); + configMap.put(FeignUtil.AIO_FEIGN_LOG_LEVEL, logLevel); + configMap.put(FeignUtil.AIO_FEIGN_RETRY_PERIOD, "1"); + configMap.put(FeignUtil.AIO_FEIGN_RETRY_MAX_ATTEMPTS, "2"); + configMap.put(FeignUtil.AIO_FEIGN_RETRY_MAX_PERIOD, "3"); + FeignUtil.ConfigBuilder configBuilder = new FeignUtil.ConfigBuilder().configMap(configMap); + assertEquals(Level.valueOf(logLevel), configBuilder.getLogLevel()); + assertEquals(1, configBuilder.getRetryPeriod()); + assertEquals(2, configBuilder.getMaxAttempts()); + assertEquals(3, configBuilder.getMaxPeriod()); + assertDoesNotThrow(configBuilder::build); + } + +}