Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GH-150: adjust feign retryer configuration (#151) #215

Merged
merged 2 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 147 additions & 11 deletions core/src/main/java/com/adobe/aio/util/feign/FeignUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,57 @@
*/
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;
import feign.Logger.Level;
import feign.Request;
import feign.Retryer;
import feign.form.FormEncoder;
import feign.jackson.JacksonDecoder;
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() {
}
Expand All @@ -35,15 +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()
.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();
}

/**
Expand All @@ -67,4 +95,112 @@ public static Feign.Builder getBuilderWithFormEncoder() {
.encoder(new FormEncoder());
}

public static class ConfigBuilder {
private Class<Logger> 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<Logger> 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<Logger> 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<String, String> configMap) {
try {
return this
.loggerClass((Class<Logger>) 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);
}
}
}

}
64 changes: 64 additions & 0 deletions core/src/test/java/com/adobe/aio/util/feign/FeignUtilTest.java
Original file line number Diff line number Diff line change
@@ -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<String, String> 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<String, String> 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<String, String> 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);
}

}
Loading