Browse Source

Merge pull request #30 from Netflix/log-ioe

log ioexceptions
pull/26/merge
Adrian Cole 11 years ago
parent
commit
0598afed98
  1. 3
      CHANGES.md
  2. 75
      feign-core/src/main/java/feign/Logger.java
  3. 41
      feign-core/src/main/java/feign/MethodHandler.java
  4. 272
      feign-core/src/test/java/feign/LoggerTest.java

3
CHANGES.md

@ -4,6 +4,9 @@ @@ -4,6 +4,9 @@
* `Observer<T>` replaces `IncrementalCallback<T>` and is passed to `Observable.subscribe()`.
* On `Subscription.unsubscribe()`, `Observer.onNext()` will stop being called.
### Version 3.1
* Log when an http request is retried or a response fails due to an IOException.
### Version 3.0
* Added support for asynchronous callbacks via `IncrementalCallback<T>` and `IncrementalDecoder.TextStream<T>`.
* Wire is now Logger, with configurable Logger.Level.

75
feign-core/src/main/java/feign/Logger.java

@ -17,7 +17,9 @@ package feign; @@ -17,7 +17,9 @@ package feign;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.util.logging.FileHandler;
import java.util.logging.LogRecord;
import java.util.logging.SimpleFormatter;
@ -59,8 +61,8 @@ public abstract class Logger { @@ -59,8 +61,8 @@ public abstract class Logger {
public static class ErrorLogger extends Logger {
final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(Logger.class.getName());
@Override protected void log(Target<?> target, String format, Object... args) {
System.err.printf(format + "%n", args);
@Override protected void log(String configKey, String format, Object... args) {
System.err.printf(methodTag(configKey) + format + "%n", args);
}
}
@ -70,22 +72,22 @@ public abstract class Logger { @@ -70,22 +72,22 @@ public abstract class Logger {
public static class JavaLogger extends Logger {
final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(Logger.class.getName());
@Override void logRequest(Target<?> target, Level logLevel, Request request) {
@Override void logRequest(String configKey, Level logLevel, Request request) {
if (logger.isLoggable(java.util.logging.Level.FINE)) {
super.logRequest(target, logLevel, request);
super.logRequest(configKey, logLevel, request);
}
}
@Override
Response logAndRebufferResponse(Target<?> target, Level logLevel, Response response, long elapsedTime) throws IOException {
Response logAndRebufferResponse(String configKey, Level logLevel, Response response, long elapsedTime) throws IOException {
if (logger.isLoggable(java.util.logging.Level.FINE)) {
return super.logAndRebufferResponse(target, logLevel, response, elapsedTime);
return super.logAndRebufferResponse(configKey, logLevel, response, elapsedTime);
}
return response;
}
@Override protected void log(Target<?> target, String format, Object... args) {
logger.fine(String.format(format, args));
@Override protected void log(String configKey, String format, Object... args) {
logger.fine(String.format(methodTag(configKey) + format, args));
}
/**
@ -110,16 +112,16 @@ public abstract class Logger { @@ -110,16 +112,16 @@ public abstract class Logger {
}
public static class NoOpLogger extends Logger {
@Override void logRequest(Target<?> target, Level logLevel, Request request) {
@Override void logRequest(String configKey, Level logLevel, Request request) {
}
@Override
Response logAndRebufferResponse(Target<?> target, Level logLevel, Response response, long elapsedTime) throws IOException {
Response logAndRebufferResponse(String configKey, Level logLevel, Response response, long elapsedTime) throws IOException {
return response;
}
@Override
protected void log(Target<?> target, String format, Object... args) {
protected void log(String configKey, String format, Object... args) {
}
}
@ -127,19 +129,19 @@ public abstract class Logger { @@ -127,19 +129,19 @@ public abstract class Logger {
* Override to log requests and responses using your own implementation.
* Messages will be http request and response text.
*
* @param target useful if using MDC (Mapped Diagnostic Context) loggers
* @param format {@link java.util.Formatter format string}
* @param args arguments applied to {@code format}
* @param configKey value of {@link Feign#configKey(java.lang.reflect.Method)}
* @param format {@link java.util.Formatter format string}
* @param args arguments applied to {@code format}
*/
protected abstract void log(Target<?> target, String format, Object... args);
protected abstract void log(String configKey, String format, Object... args);
void logRequest(Target<?> target, Level logLevel, Request request) {
log(target, "---> %s %s HTTP/1.1", request.method(), request.url());
void logRequest(String configKey, Level logLevel, Request request) {
log(configKey, "---> %s %s HTTP/1.1", request.method(), request.url());
if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {
for (String field : request.headers().keySet()) {
for (String value : valuesOrEmpty(request.headers(), field)) {
log(target, "%s: %s", field, value);
log(configKey, "%s: %s", field, value);
}
}
@ -147,27 +149,31 @@ public abstract class Logger { @@ -147,27 +149,31 @@ public abstract class Logger {
if (request.body() != null) {
bytes = request.body().getBytes(UTF_8).length;
if (logLevel.ordinal() >= Level.FULL.ordinal()) {
log(target, ""); // CRLF
log(target, "%s", request.body());
log(configKey, ""); // CRLF
log(configKey, "%s", request.body());
}
}
log(target, "---> END HTTP (%s-byte body)", bytes);
log(configKey, "---> END HTTP (%s-byte body)", bytes);
}
}
Response logAndRebufferResponse(Target<?> target, Level logLevel, Response response, long elapsedTime) throws IOException {
log(target, "<--- HTTP/1.1 %s %s (%sms)", response.status(), response.reason(), elapsedTime);
void logRetry(String configKey, Level logLevel) {
log(configKey, "---> RETRYING");
}
Response logAndRebufferResponse(String configKey, Level logLevel, Response response, long elapsedTime) throws IOException {
log(configKey, "<--- HTTP/1.1 %s %s (%sms)", response.status(), response.reason(), elapsedTime);
if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {
for (String field : response.headers().keySet()) {
for (String value : valuesOrEmpty(response.headers(), field)) {
log(target, "%s: %s", field, value);
log(configKey, "%s: %s", field, value);
}
}
if (response.body() != null) {
if (logLevel.ordinal() >= Level.FULL.ordinal()) {
log(target, ""); // CRLF
log(configKey, ""); // CRLF
}
Reader body = response.body().asReader();
@ -178,11 +184,11 @@ public abstract class Logger { @@ -178,11 +184,11 @@ public abstract class Logger {
while ((line = reader.readLine()) != null) {
buffered.append(line);
if (logLevel.ordinal() >= Level.FULL.ordinal()) {
log(target, "%s", line);
log(configKey, "%s", line);
}
}
String bodyAsString = buffered.toString();
log(target, "<--- END HTTP (%s-byte body)", bodyAsString.getBytes(UTF_8).length);
log(configKey, "<--- END HTTP (%s-byte body)", bodyAsString.getBytes(UTF_8).length);
return Response.create(response.status(), response.reason(), response.headers(), bodyAsString);
} finally {
ensureClosed(response.body());
@ -191,4 +197,19 @@ public abstract class Logger { @@ -191,4 +197,19 @@ public abstract class Logger {
}
return response;
}
IOException logIOException(String configKey, Level logLevel, IOException ioe, long elapsedTime) {
log(configKey, "<--- ERROR %s: %s (%sms)", ioe.getClass().getSimpleName(), ioe.getMessage(), elapsedTime);
if (logLevel.ordinal() >= Level.FULL.ordinal()) {
StringWriter sw = new StringWriter();
ioe.printStackTrace(new PrintWriter(sw));
log(configKey, sw.toString());
log(configKey, "<--- END ERROR");
}
return ioe;
}
static String methodTag(String configKey) {
return new StringBuilder().append('[').append(configKey.substring(0, configKey.indexOf('('))).append("] ").toString();
}
}

41
feign-core/src/main/java/feign/MethodHandler.java

@ -45,10 +45,10 @@ interface MethodHandler { @@ -45,10 +45,10 @@ interface MethodHandler {
private final Lazy<Executor> httpExecutor;
private final Provider<Retryer> retryer;
private final Logger logger;
private final Logger.Level logLevel;
private final Provider<Logger.Level> logLevel;
@Inject Factory(Client client, @Named("http") Lazy<Executor> httpExecutor, Provider<Retryer> retryer, Logger logger,
Logger.Level logLevel) {
Provider<Logger.Level> logLevel) {
this.client = checkNotNull(client, "client");
this.httpExecutor = checkNotNull(httpExecutor, "httpExecutor");
this.retryer = checkNotNull(retryer, "retryer");
@ -107,9 +107,10 @@ interface MethodHandler { @@ -107,9 +107,10 @@ interface MethodHandler {
private final IncrementalDecoder.TextStream<?> incrementalDecoder;
private ObserverHandler(Target<?> target, Client client, Provider<Retryer> retryer, Logger logger,
Logger.Level logLevel, MethodMetadata metadata, BuildTemplateFromArgs buildTemplateFromArgs,
Options options, IncrementalDecoder.TextStream<?> incrementalDecoder,
ErrorDecoder errorDecoder, Lazy<Executor> httpExecutor) {
Provider<Logger.Level> logLevel, MethodMetadata metadata,
BuildTemplateFromArgs buildTemplateFromArgs, Options options,
IncrementalDecoder.TextStream<?> incrementalDecoder, ErrorDecoder errorDecoder,
Lazy<Executor> httpExecutor) {
super(target, client, retryer, logger, logLevel, metadata, buildTemplateFromArgs, options, errorDecoder);
this.httpExecutor = checkNotNull(httpExecutor, "httpExecutor for %s", target);
this.incrementalDecoder = checkNotNull(incrementalDecoder, "incrementalDecoder for %s", target);
@ -185,7 +186,7 @@ interface MethodHandler { @@ -185,7 +186,7 @@ interface MethodHandler {
private final Decoder.TextStream<?> decoder;
private SynchronousMethodHandler(Target<?> target, Client client, Provider<Retryer> retryer, Logger logger,
Logger.Level logLevel, MethodMetadata metadata,
Provider<Logger.Level> logLevel, MethodMetadata metadata,
BuildTemplateFromArgs buildTemplateFromArgs, Options options,
Decoder.TextStream<?> decoder, ErrorDecoder errorDecoder) {
super(target, client, retryer, logger, logLevel, metadata, buildTemplateFromArgs, options, errorDecoder);
@ -215,15 +216,14 @@ interface MethodHandler { @@ -215,15 +216,14 @@ interface MethodHandler {
protected final Client client;
protected final Provider<Retryer> retryer;
protected final Logger logger;
protected final Logger.Level logLevel;
protected final Provider<Logger.Level> logLevel;
protected final BuildTemplateFromArgs buildTemplateFromArgs;
protected final Options options;
protected final ErrorDecoder errorDecoder;
private BaseMethodHandler(Target<?> target, Client client, Provider<Retryer> retryer, Logger logger,
Logger.Level logLevel, MethodMetadata metadata, BuildTemplateFromArgs buildTemplateFromArgs,
Options options, ErrorDecoder errorDecoder) {
Provider<Logger.Level> logLevel, MethodMetadata metadata,
BuildTemplateFromArgs buildTemplateFromArgs, Options options, ErrorDecoder errorDecoder) {
this.target = checkNotNull(target, "target");
this.client = checkNotNull(client, "client for %s", target);
this.retryer = checkNotNull(retryer, "retryer for %s", target);
@ -243,6 +243,9 @@ interface MethodHandler { @@ -243,6 +243,9 @@ interface MethodHandler {
return executeAndDecode(argv, template);
} catch (RetryableException e) {
retryer.continueOrPropagate(e);
if (logLevel.get() != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel.get());
}
continue;
}
}
@ -251,8 +254,8 @@ interface MethodHandler { @@ -251,8 +254,8 @@ interface MethodHandler {
public Object executeAndDecode(Object[] argv, RequestTemplate template) throws Throwable {
Request request = targetRequest(template);
if (logLevel.ordinal() > Logger.Level.NONE.ordinal()) {
logger.logRequest(target, logLevel, request);
if (logLevel.get() != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel.get(), request);
}
Response response;
@ -260,13 +263,16 @@ interface MethodHandler { @@ -260,13 +263,16 @@ interface MethodHandler {
try {
response = client.execute(request, options);
} catch (IOException e) {
if (logLevel.get() != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel.get(), e, elapsedTime(start));
}
throw errorExecuting(request, e);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
try {
if (logLevel.ordinal() > Logger.Level.NONE.ordinal()) {
response = logger.logAndRebufferResponse(target, logLevel, response, elapsedTime);
if (logLevel.get() != Logger.Level.NONE) {
response = logger.logAndRebufferResponse(metadata.configKey(), logLevel.get(), response, elapsedTime);
}
if (response.status() >= 200 && response.status() < 300) {
return decode(argv, response);
@ -274,12 +280,19 @@ interface MethodHandler { @@ -274,12 +280,19 @@ interface MethodHandler {
throw errorDecoder.decode(metadata.configKey(), response);
}
} catch (IOException e) {
if (logLevel.get() != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel.get(), e, elapsedTime);
}
throw errorReading(request, response, e);
} finally {
ensureClosed(response.body());
}
}
protected long elapsedTime(long start) {
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
}
protected Request targetRequest(RequestTemplate template) {
return target.apply(new RequestTemplate(template));
}

272
feign-core/src/test/java/feign/LoggerTest.java

@ -15,13 +15,11 @@ @@ -15,13 +15,11 @@
*/
package feign;
import com.google.common.collect.ImmutableMap;
import com.google.common.base.Joiner;
import com.google.mockwebserver.MockResponse;
import com.google.mockwebserver.MockWebServer;
import dagger.Provides;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.codec.StringDecoder;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
@ -32,18 +30,19 @@ import java.io.IOException; @@ -32,18 +30,19 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import static dagger.Provides.Type.SET;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
@Test
public class LoggerTest {
Logger logger = new Logger() {
@Override protected void log(Target<?> target, String format, Object... args) {
messages.add(String.format(format, args));
@Override protected void log(String configKey, String format, Object... args) {
messages.add(methodTag(configKey) + String.format(format, args));
}
};
@ -64,38 +63,38 @@ public class LoggerTest { @@ -64,38 +63,38 @@ public class LoggerTest {
}
@DataProvider(name = "levelToOutput")
public Object[][] createData() {
public Object[][] levelToOutput() {
Object[][] data = new Object[4][2];
data[0][0] = Logger.Level.NONE;
data[0][1] = Arrays.<String>asList();
data[1][0] = Logger.Level.BASIC;
data[1][1] = Arrays.asList(
"---> POST http://localhost:[0-9]+/ HTTP/1.1",
"<--- HTTP/1.1 200 OK \\([0-9]+ms\\)"
"\\[SendsStuff#login\\] ---> POST http://localhost:[0-9]+/ HTTP/1.1",
"\\[SendsStuff#login\\] <--- HTTP/1.1 200 OK \\([0-9]+ms\\)"
);
data[2][0] = Logger.Level.HEADERS;
data[2][1] = Arrays.asList(
"---> POST http://localhost:[0-9]+/ HTTP/1.1",
"Content-Type: application/json",
"Content-Length: 80",
"---> END HTTP \\(80-byte body\\)",
"<--- HTTP/1.1 200 OK \\([0-9]+ms\\)",
"Content-Length: 3",
"<--- END HTTP \\(3-byte body\\)"
"\\[SendsStuff#login\\] ---> POST http://localhost:[0-9]+/ HTTP/1.1",
"\\[SendsStuff#login\\] Content-Type: application/json",
"\\[SendsStuff#login\\] Content-Length: 80",
"\\[SendsStuff#login\\] ---> END HTTP \\(80-byte body\\)",
"\\[SendsStuff#login\\] <--- HTTP/1.1 200 OK \\([0-9]+ms\\)",
"\\[SendsStuff#login\\] Content-Length: 3",
"\\[SendsStuff#login\\] <--- END HTTP \\(3-byte body\\)"
);
data[3][0] = Logger.Level.FULL;
data[3][1] = Arrays.asList(
"---> POST http://localhost:[0-9]+/ HTTP/1.1",
"Content-Type: application/json",
"Content-Length: 80",
"",
"\\{\"customer_name\": \"netflix\", \"user_name\": \"denominator\", \"password\": \"password\"\\}",
"---> END HTTP \\(80-byte body\\)",
"<--- HTTP/1.1 200 OK \\([0-9]+ms\\)",
"Content-Length: 3",
"",
"foo",
"<--- END HTTP \\(3-byte body\\)"
"\\[SendsStuff#login\\] ---> POST http://localhost:[0-9]+/ HTTP/1.1",
"\\[SendsStuff#login\\] Content-Type: application/json",
"\\[SendsStuff#login\\] Content-Length: 80",
"\\[SendsStuff#login\\] ",
"\\[SendsStuff#login\\] \\{\"customer_name\": \"netflix\", \"user_name\": \"denominator\", \"password\": \"password\"\\}",
"\\[SendsStuff#login\\] ---> END HTTP \\(80-byte body\\)",
"\\[SendsStuff#login\\] <--- HTTP/1.1 200 OK \\([0-9]+ms\\)",
"\\[SendsStuff#login\\] Content-Length: 3",
"\\[SendsStuff#login\\] ",
"\\[SendsStuff#login\\] foo",
"\\[SendsStuff#login\\] <--- END HTTP \\(3-byte body\\)"
);
return data;
}
@ -103,7 +102,7 @@ public class LoggerTest { @@ -103,7 +102,7 @@ public class LoggerTest {
@Test(dataProvider = "levelToOutput")
public void levelEmits(final Logger.Level logLevel, List<String> expectedMessages) throws IOException, InterruptedException {
final MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse().setResponseCode(200).setBody("foo"));
server.enqueue(new MockResponse().setBody("foo"));
server.play();
@dagger.Module(overrides = true, library = true) class Module {
@ -140,4 +139,221 @@ public class LoggerTest { @@ -140,4 +139,221 @@ public class LoggerTest {
server.shutdown();
}
}
@DataProvider(name = "levelToReadTimeoutOutput")
public Object[][] levelToReadTimeoutOutput() {
Object[][] data = new Object[4][2];
data[0][0] = Logger.Level.NONE;
data[0][1] = Arrays.<String>asList();
data[1][0] = Logger.Level.BASIC;
data[1][1] = Arrays.asList(
"\\[SendsStuff#login\\] ---> POST http://localhost:[0-9]+/ HTTP/1.1",
"\\[SendsStuff#login\\] <--- HTTP/1.1 200 OK \\([0-9]+ms\\)",
"\\[SendsStuff#login\\] <--- ERROR SocketTimeoutException: Read timed out \\([0-9]+ms\\)"
);
data[2][0] = Logger.Level.HEADERS;
data[2][1] = Arrays.asList(
"\\[SendsStuff#login\\] ---> POST http://localhost:[0-9]+/ HTTP/1.1",
"\\[SendsStuff#login\\] Content-Type: application/json",
"\\[SendsStuff#login\\] Content-Length: 80",
"\\[SendsStuff#login\\] ---> END HTTP \\(80-byte body\\)",
"\\[SendsStuff#login\\] <--- HTTP/1.1 200 OK \\([0-9]+ms\\)",
"\\[SendsStuff#login\\] Content-Length: 3",
"\\[SendsStuff#login\\] <--- ERROR SocketTimeoutException: Read timed out \\([0-9]+ms\\)"
);
data[3][0] = Logger.Level.FULL;
data[3][1] = Arrays.asList(
"\\[SendsStuff#login\\] ---> POST http://localhost:[0-9]+/ HTTP/1.1",
"\\[SendsStuff#login\\] Content-Type: application/json",
"\\[SendsStuff#login\\] Content-Length: 80",
"\\[SendsStuff#login\\] ",
"\\[SendsStuff#login\\] \\{\"customer_name\": \"netflix\", \"user_name\": \"denominator\", \"password\": \"password\"\\}",
"\\[SendsStuff#login\\] ---> END HTTP \\(80-byte body\\)",
"\\[SendsStuff#login\\] <--- HTTP/1.1 200 OK \\([0-9]+ms\\)",
"\\[SendsStuff#login\\] Content-Length: 3",
"\\[SendsStuff#login\\] ",
"\\[SendsStuff#login\\] <--- ERROR SocketTimeoutException: Read timed out \\([0-9]+ms\\)",
"\\[SendsStuff#login\\] java.net.SocketTimeoutException: Read timed out.*",
"\\[SendsStuff#login\\] <--- END ERROR"
);
return data;
}
@Test(dataProvider = "levelToReadTimeoutOutput")
public void readTimeoutEmits(final Logger.Level logLevel, List<String> expectedMessages) throws IOException, InterruptedException {
final MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse().setBytesPerSecond(1).setBody("foo"));
server.play();
@dagger.Module(overrides = true, library = true) class Module {
@Provides Request.Options lessReadTimeout() {
return new Request.Options(10 * 1000, 50);
}
@Provides(type = SET) Encoder defaultEncoder() {
return new Encoder.Text<Object>() {
@Override public String encode(Object object) {
return object.toString();
}
};
}
@Provides @Singleton Logger logger() {
return logger;
}
@Provides @Singleton Logger.Level level() {
return logLevel;
}
}
try {
SendsStuff api = Feign.create(SendsStuff.class, "http://localhost:" + server.getUrl("").getPort(), new Module());
api.login("netflix", "denominator", "password");
fail();
} catch (FeignException e) {
assertMessagesMatch(expectedMessages);
assertEquals(new String(server.takeRequest().getBody()),
"{\"customer_name\": \"netflix\", \"user_name\": \"denominator\", \"password\": \"password\"}");
} finally {
server.shutdown();
}
}
@DataProvider(name = "levelToUnknownHostOutput")
public Object[][] levelToUnknownHostOutput() {
Object[][] data = new Object[4][2];
data[0][0] = Logger.Level.NONE;
data[0][1] = Arrays.<String>asList();
data[1][0] = Logger.Level.BASIC;
data[1][1] = Arrays.asList(
"\\[SendsStuff#login\\] ---> POST http://robofu.abc/ HTTP/1.1",
"\\[SendsStuff#login\\] <--- ERROR UnknownHostException: robofu.abc \\([0-9]+ms\\)"
);
data[2][0] = Logger.Level.HEADERS;
data[2][1] = Arrays.asList(
"\\[SendsStuff#login\\] ---> POST http://robofu.abc/ HTTP/1.1",
"\\[SendsStuff#login\\] Content-Type: application/json",
"\\[SendsStuff#login\\] Content-Length: 80",
"\\[SendsStuff#login\\] ---> END HTTP \\(80-byte body\\)",
"\\[SendsStuff#login\\] <--- ERROR UnknownHostException: robofu.abc \\([0-9]+ms\\)"
);
data[3][0] = Logger.Level.FULL;
data[3][1] = Arrays.asList(
"\\[SendsStuff#login\\] ---> POST http://robofu.abc/ HTTP/1.1",
"\\[SendsStuff#login\\] Content-Type: application/json",
"\\[SendsStuff#login\\] Content-Length: 80",
"\\[SendsStuff#login\\] ",
"\\[SendsStuff#login\\] \\{\"customer_name\": \"netflix\", \"user_name\": \"denominator\", \"password\": \"password\"\\}",
"\\[SendsStuff#login\\] ---> END HTTP \\(80-byte body\\)",
"\\[SendsStuff#login\\] <--- ERROR UnknownHostException: robofu.abc \\([0-9]+ms\\)",
"\\[SendsStuff#login\\] java.net.UnknownHostException: robofu.abc.*",
"\\[SendsStuff#login\\] <--- END ERROR"
);
return data;
}
@Test(dataProvider = "levelToUnknownHostOutput")
public void unknownHostEmits(final Logger.Level logLevel, List<String> expectedMessages) throws IOException, InterruptedException {
@dagger.Module(overrides = true, library = true) class Module {
@Provides Retryer retryer() {
return new Retryer() {
@Override public void continueOrPropagate(RetryableException e) {
throw e;
}
};
}
@Provides(type = SET) Encoder defaultEncoder() {
return new Encoder.Text<Object>() {
@Override public String encode(Object object) {
return object.toString();
}
};
}
@Provides @Singleton Logger logger() {
return logger;
}
@Provides @Singleton Logger.Level level() {
return logLevel;
}
}
try {
SendsStuff api = Feign.create(SendsStuff.class, "http://robofu.abc", new Module());
api.login("netflix", "denominator", "password");
fail();
} catch (FeignException e) {
assertMessagesMatch(expectedMessages);
}
}
public void retryEmits() throws IOException, InterruptedException {
@dagger.Module(overrides = true, library = true) class Module {
@Provides Retryer retryer() {
return new Retryer() {
boolean retried;
@Override public void continueOrPropagate(RetryableException e) {
if (!retried) {
retried = true;
return;
}
throw e;
}
};
}
@Provides(type = SET) Encoder defaultEncoder() {
return new Encoder.Text<Object>() {
@Override public String encode(Object object) {
return object.toString();
}
};
}
@Provides @Singleton Logger logger() {
return logger;
}
@Provides @Singleton Logger.Level level() {
return Logger.Level.BASIC;
}
}
try {
SendsStuff api = Feign.create(SendsStuff.class, "http://robofu.abc", new Module());
api.login("netflix", "denominator", "password");
fail();
} catch (FeignException e) {
assertMessagesMatch(Arrays.asList(
"\\[SendsStuff#login\\] ---> POST http://robofu.abc/ HTTP/1.1",
"\\[SendsStuff#login\\] <--- ERROR UnknownHostException: robofu.abc \\([0-9]+ms\\)",
"\\[SendsStuff#login\\] ---> RETRYING",
"\\[SendsStuff#login\\] ---> POST http://robofu.abc/ HTTP/1.1",
"\\[SendsStuff#login\\] <--- ERROR UnknownHostException: robofu.abc \\([0-9]+ms\\)"
));
}
}
private void assertMessagesMatch(List<String> expectedMessages) {
assertEquals(messages.size(), expectedMessages.size());
for (int i = 0; i < messages.size(); i++) {
assertTrue(Pattern.compile(expectedMessages.get(i), Pattern.DOTALL).matcher(messages.get(i)).matches(),
"Didn't match at message " + (i + 1) + ":\n" + Joiner.on('\n').join(messages));
}
}
}

Loading…
Cancel
Save