-
Notifications
You must be signed in to change notification settings - Fork 264
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* initial OpenFeign support Implement Logbook interceptor as Feign.Logger * Move server bootstrap to separate base class * Cover catch {} blocks with tests * Add mockito-inline dependency * Adapt to a new project structure * Add logbook-openfeign to BOM * Add missing test cases, make JaCoCo coverage 100%
- Loading branch information
Showing
14 changed files
with
885 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<parent> | ||
<groupId>org.zalando</groupId> | ||
<artifactId>logbook-parent</artifactId> | ||
<version>2.14.0-SNAPSHOT</version> | ||
<relativePath>../logbook-parent/pom.xml</relativePath> | ||
</parent> | ||
|
||
<artifactId>logbook-openfeign</artifactId> | ||
<version>2.14.0-SNAPSHOT</version> | ||
<description>OpenFeign implementations for request and response logging</description> | ||
<scm> | ||
<url>https://github.com/zalando/logbook</url> | ||
<connection>scm:git:git@github.com:zalando/logbook.git</connection> | ||
<developerConnection>scm:git:git@github.com:zalando/logbook.git</developerConnection> | ||
</scm> | ||
|
||
<properties> | ||
<feign.version>11.6</feign.version> | ||
</properties> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>org.zalando</groupId> | ||
<artifactId>logbook-core</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.github.openfeign</groupId> | ||
<artifactId>feign-core</artifactId> | ||
<version>${feign.version}</version> | ||
<scope>provided</scope> | ||
</dependency> | ||
|
||
<!-- test dependencies --> | ||
<dependency> | ||
<groupId>org.zalando</groupId> | ||
<artifactId>logbook-test</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.mockito</groupId> | ||
<artifactId>mockito-inline</artifactId> | ||
<version>${mockito.version}</version> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
</project> |
29 changes: 29 additions & 0 deletions
29
logbook-openfeign/src/main/java/org/zalando/logbook/openfeign/ByteStreams.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package org.zalando.logbook.openfeign; | ||
|
||
import java.io.ByteArrayOutputStream; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.io.OutputStream; | ||
|
||
final class ByteStreams { | ||
private ByteStreams() { | ||
} | ||
|
||
static byte[] toByteArray(final InputStream in) throws IOException { | ||
final ByteArrayOutputStream out = new ByteArrayOutputStream(); | ||
copy(in, out); | ||
return out.toByteArray(); | ||
} | ||
|
||
static void copy(final InputStream from, final OutputStream to) throws IOException { | ||
final byte[] buf = new byte[4096]; | ||
while (true) { | ||
final int r = from.read(buf); | ||
if (r == -1) { | ||
break; | ||
} | ||
to.write(buf, 0, r); | ||
} | ||
} | ||
|
||
} |
87 changes: 87 additions & 0 deletions
87
logbook-openfeign/src/main/java/org/zalando/logbook/openfeign/FeignLogbookLogger.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package org.zalando.logbook.openfeign; | ||
|
||
import feign.Request; | ||
import feign.Response; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Generated; | ||
import org.apiguardian.api.API; | ||
import org.zalando.logbook.HttpRequest; | ||
import org.zalando.logbook.HttpResponse; | ||
import org.zalando.logbook.Logbook; | ||
import org.zalando.logbook.Logbook.ResponseProcessingStage; | ||
|
||
import java.io.IOException; | ||
import java.io.UncheckedIOException; | ||
|
||
/** | ||
* Example usage: | ||
* <pre>{@code | ||
* Logbook logbook = ...; | ||
* FeignLogbookLogger interceptor = new FeignLogbookLogger(logbook); | ||
* client = Feign.builder() | ||
* ... | ||
* .logger(interceptor) | ||
* .logLevel(Logger.Level.FULL) | ||
* ...; | ||
* }</pre> | ||
*/ | ||
@API(status = API.Status.EXPERIMENTAL) | ||
@AllArgsConstructor | ||
public final class FeignLogbookLogger extends feign.Logger { | ||
private final Logbook logbook; | ||
// Feign is blocking, so there is no context switch between request and response | ||
private final ThreadLocal<ResponseProcessingStage> stage = new ThreadLocal<>(); | ||
|
||
@Override | ||
@Generated | ||
// HACK: JaCoCo ignores a code with "*Generated*" annotation | ||
// this method is a rudiment (not called anywhere), and shouldn't be covered | ||
protected void log(String configKey, String format, Object... args) { | ||
/* no-op, logging is delegated to logbook */ | ||
} | ||
|
||
@Override | ||
protected void logRetry(String configKey, Level logLevel) { | ||
/* no-op, logging is delegated to logbook */ | ||
} | ||
|
||
@Override | ||
protected IOException logIOException(String configKey, Level logLevel, IOException ioe, long elapsedTime) { | ||
/* no-op, logging is delegated to logbook */ | ||
return ioe; | ||
} | ||
|
||
@Override | ||
protected void logRequest(String configKey, Level logLevel, Request request) { | ||
final HttpRequest httpRequest = LocalRequest.create(request); | ||
try { | ||
ResponseProcessingStage processingStage = logbook.process(httpRequest).write(); | ||
stage.set(processingStage); | ||
} catch (IOException e) { | ||
throw new UncheckedIOException(e); | ||
} | ||
} | ||
|
||
@Override | ||
protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response, long elapsedTime) { | ||
try { | ||
// Logbook will consume body stream, making it impossible to read it again | ||
// read body here and create new response based on byte array instead | ||
byte[] body = ByteStreams.toByteArray(response.body().asInputStream()); | ||
|
||
final HttpResponse httpResponse = RemoteResponse.create(response, body); | ||
stage.get().process(httpResponse).write(); | ||
|
||
// create a copy of response to provide consumed body | ||
return Response.builder() | ||
.status(response.status()) | ||
.request(response.request()) | ||
.reason(response.reason()) | ||
.headers(response.headers()) | ||
.body(body) | ||
.build(); | ||
} catch (IOException e) { | ||
throw new UncheckedIOException(e); | ||
} | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
logbook-openfeign/src/main/java/org/zalando/logbook/openfeign/HeaderUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package org.zalando.logbook.openfeign; | ||
|
||
import org.zalando.logbook.HttpHeaders; | ||
|
||
import java.util.*; | ||
|
||
class HeaderUtils { | ||
private HeaderUtils() { | ||
} | ||
|
||
/** | ||
* Convert Feign headers to Logbook-compatible format | ||
* | ||
* @param feignHeaders original headers | ||
* @return Logbook headers | ||
*/ | ||
static HttpHeaders toLogbookHeaders(Map<String, Collection<String>> feignHeaders) { | ||
Map<String, List<String>> convertedHeaders = new HashMap<>(); | ||
for (Map.Entry<String, Collection<String>> header : feignHeaders.entrySet()) { | ||
convertedHeaders.put(header.getKey(), new ArrayList<>(header.getValue())); | ||
} | ||
return HttpHeaders.of(convertedHeaders); | ||
} | ||
} |
115 changes: 115 additions & 0 deletions
115
logbook-openfeign/src/main/java/org/zalando/logbook/openfeign/LocalRequest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package org.zalando.logbook.openfeign; | ||
|
||
import feign.Request; | ||
import lombok.RequiredArgsConstructor; | ||
import org.zalando.logbook.HttpHeaders; | ||
import org.zalando.logbook.HttpRequest; | ||
import org.zalando.logbook.Origin; | ||
|
||
import javax.annotation.Nullable; | ||
import java.net.URI; | ||
import java.nio.charset.Charset; | ||
import java.nio.charset.StandardCharsets; | ||
import java.util.Optional; | ||
|
||
@RequiredArgsConstructor | ||
final class LocalRequest implements HttpRequest { | ||
private final URI uri; | ||
private final Request.HttpMethod httpMethod; | ||
private final HttpHeaders headers; | ||
private final byte[] body; | ||
private final Charset charset; | ||
private boolean withBody = false; | ||
|
||
public static LocalRequest create(Request request) { | ||
return new LocalRequest( | ||
URI.create(request.url()), | ||
request.httpMethod(), | ||
HeaderUtils.toLogbookHeaders(request.headers()), | ||
request.body(), | ||
request.charset() | ||
); | ||
} | ||
|
||
@Override | ||
public String getRemote() { | ||
return "localhost"; | ||
} | ||
|
||
@Override | ||
public String getMethod() { | ||
return httpMethod.toString(); | ||
} | ||
|
||
@Override | ||
public String getScheme() { | ||
return uri.getScheme() == null ? "" : uri.getScheme(); | ||
} | ||
|
||
@Override | ||
public String getHost() { | ||
return uri.getHost() == null ? "" : uri.getHost(); | ||
} | ||
|
||
@Override | ||
public Optional<Integer> getPort() { | ||
return Optional.of(uri).map(URI::getPort).filter(p -> p != -1); | ||
} | ||
|
||
@Override | ||
public String getPath() { | ||
return uri.getPath() == null ? "" : uri.getPath(); | ||
} | ||
|
||
@Override | ||
public String getQuery() { | ||
return uri.getQuery() == null ? "" : uri.getQuery(); | ||
} | ||
|
||
@Override | ||
public HttpRequest withBody() { | ||
withBody = true; | ||
return this; | ||
} | ||
|
||
@Override | ||
public HttpRequest withoutBody() { | ||
withBody = false; | ||
return this; | ||
} | ||
|
||
@Override | ||
public String getProtocolVersion() { | ||
// feign doesn't support HTTP/2, their own toString looks like this: | ||
// builder.append(httpMethod).append(' ').append(url).append(" HTTP/1.1\n"); | ||
return "HTTP/1.1"; | ||
} | ||
|
||
@Override | ||
public Origin getOrigin() { | ||
return Origin.LOCAL; | ||
} | ||
|
||
@Override | ||
public HttpHeaders getHeaders() { | ||
return headers; | ||
} | ||
|
||
@Nullable | ||
@Override | ||
public String getContentType() { | ||
return Optional.ofNullable(headers.get("Content-Type")) | ||
.flatMap(ct -> ct.stream().findFirst()) | ||
.orElse(null); | ||
} | ||
|
||
@Override | ||
public Charset getCharset() { | ||
return charset == null ? StandardCharsets.UTF_8 : charset; | ||
} | ||
|
||
@Override | ||
public byte[] getBody() { | ||
return withBody && body != null ? body : new byte[0]; | ||
} | ||
} |
Oops, something went wrong.