diff --git a/request-response/logbook-filter/README.md b/request-response/logbook-filter/README.md new file mode 100644 index 0000000..2b4d92a --- /dev/null +++ b/request-response/logbook-filter/README.md @@ -0,0 +1,51 @@ +# logbook-filter +Various request/response/body filters for Logbook: + + * max body size filter + * max string field value body filter + +with + + * per-path request and/or response body filter selection based on path + +## JSON filtering + +``` +@Bean +public JsonOnlyMatchPathStrategy strategy() { + return new JsonOnlyMatchPathStrategy(); +} +``` + +with request filter + +``` +@Bean +public RequestFilter myRequestFilter() { + JacksonJsonFieldBodyFilter customerRequestFilter = new JacksonJsonFieldBodyFilter(Arrays.asList("name"), "XXX"); + return RequestMatcherRequestFilter.newPathPrefixBuilder().withPathPrefixFilter("/api/customer", customerRequestFilter).build(); +} +``` + +and response filter + +``` +@Bean +public ResponseFilter myResponseFilter() { + JacksonJsonFieldBodyFilter customerResponseFilter = new JacksonJsonFieldBodyFilter(Arrays.asList("rating"), "XXX"); + return RequestResponseMatcherResponseFilter.newPathPrefixBuilder().withPathPrefixFilter("/api/customer", customerResponseFilter).build(); +} +``` + +### Advanced filtering +Construct your own `PathFilterMatcher` to handle more complex filtering, i.e. + + * path + * max size + * origin + +using + + * SizePathFilterMatcher + * SizeOriginPathFilterMatcher + diff --git a/request-response/logbook-filter/build.gradle b/request-response/logbook-filter/build.gradle new file mode 100644 index 0000000..043f5f9 --- /dev/null +++ b/request-response/logbook-filter/build.gradle @@ -0,0 +1,23 @@ + +dependencies { + api("org.slf4j:slf4j-api:${slf4jVersion}") + api("org.zalando:logbook-api:${logbookVersion}") + api("org.zalando:logbook-api:${logbookVersion}") + + api("com.fasterxml.jackson.core:jackson-core:${jacksonVersion}") + api("com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}") + api "commons-io:commons-io:${commonsIoVersion}" + + // JUnit Jupiter API and TestEngine implementation + testImplementation("org.junit.jupiter:junit-jupiter:${junitJupiterVersion}") + + testImplementation("org.mockito:mockito-core:${mockitoVersion}") + + testImplementation("com.google.truth:truth:${googleTruthVersion}") + testImplementation("com.google.truth.extensions:truth-java8-extension:${googleTruthVersion}") + +} + + + + diff --git a/request-response/logbook/src/main/java/no/entur/logging/cloud/logbook/filter/JsonMaxBodySizeFilter.java b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/JsonMaxBodySizeFilter.java similarity index 96% rename from request-response/logbook/src/main/java/no/entur/logging/cloud/logbook/filter/JsonMaxBodySizeFilter.java rename to request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/JsonMaxBodySizeFilter.java index d70630a..468b25a 100644 --- a/request-response/logbook/src/main/java/no/entur/logging/cloud/logbook/filter/JsonMaxBodySizeFilter.java +++ b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/JsonMaxBodySizeFilter.java @@ -5,15 +5,13 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonStreamContext; import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.MappingJsonFactory; import org.apache.commons.io.output.StringBuilderWriter; import org.zalando.logbook.BodyFilter; import org.zalando.logbook.ContentType; import java.io.IOException; -import java.io.StringWriter; import java.util.function.LongSupplier; -import java.util.function.Predicate; /** * Thread-safe filter for JSON fields. @@ -34,7 +32,7 @@ public static JsonMaxBodySizeFilter newInstance(int maxBodySize) { public JsonMaxBodySizeFilter(int maxBodySize) { this.maxBodySize = maxBodySize; - this.factory = new ObjectMapper().getFactory(); + this.factory = new MappingJsonFactory(); } @Override diff --git a/request-response/logbook/src/main/java/no/entur/logging/cloud/logbook/filter/JsonMaxValueLengthBodyFilter.java b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/JsonMaxValueLengthBodyFilter.java similarity index 95% rename from request-response/logbook/src/main/java/no/entur/logging/cloud/logbook/filter/JsonMaxValueLengthBodyFilter.java rename to request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/JsonMaxValueLengthBodyFilter.java index 812f8d5..a253d4d 100644 --- a/request-response/logbook/src/main/java/no/entur/logging/cloud/logbook/filter/JsonMaxValueLengthBodyFilter.java +++ b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/JsonMaxValueLengthBodyFilter.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.MappingJsonFactory; import com.fasterxml.jackson.databind.ObjectMapper; import org.zalando.logbook.BodyFilter; import org.zalando.logbook.ContentType; @@ -29,7 +30,7 @@ public static JsonMaxValueLengthBodyFilter newInstance(int maxFieldLength) { public JsonMaxValueLengthBodyFilter(int maxFieldLength) { this.maxFieldLength = maxFieldLength; - this.factory = new ObjectMapper().getFactory(); + this.factory = new MappingJsonFactory(); } @Override diff --git a/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/BodyReplacementHttpRequest.java b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/BodyReplacementHttpRequest.java new file mode 100644 index 0000000..33d22a4 --- /dev/null +++ b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/BodyReplacementHttpRequest.java @@ -0,0 +1,44 @@ +package no.entur.logging.cloud.logbook.filter.path; + +import java.nio.charset.StandardCharsets; + +import org.zalando.logbook.ForwardingHttpRequest; +import org.zalando.logbook.HttpRequest; + +public class BodyReplacementHttpRequest implements ForwardingHttpRequest { + + private final HttpRequest request; + private final String replacement; + + public BodyReplacementHttpRequest(HttpRequest request, String replacement) { + super(); + this.request = request; + this.replacement = replacement; + } + + @Override + public HttpRequest delegate() { + return request; + } + + @Override + public HttpRequest withBody() { + return withoutBody(); + } + + @Override + public HttpRequest withoutBody() { + return new BodyReplacementHttpRequest(request.withoutBody(), replacement); + } + + @Override + public byte[] getBody() { + return replacement.getBytes(StandardCharsets.UTF_8); + } + + @Override + public String getBodyAsString() { + return replacement; + } + +} \ No newline at end of file diff --git a/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/BodyReplacementHttpResponse.java b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/BodyReplacementHttpResponse.java new file mode 100644 index 0000000..d79d8f1 --- /dev/null +++ b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/BodyReplacementHttpResponse.java @@ -0,0 +1,45 @@ +package no.entur.logging.cloud.logbook.filter.path; +import org.zalando.logbook.ForwardingHttpResponse; +import org.zalando.logbook.HttpResponse; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.nio.charset.StandardCharsets; + +public class BodyReplacementHttpResponse implements ForwardingHttpResponse, HttpResponse { + + private final HttpResponse response; + private final String replacement; + + public BodyReplacementHttpResponse(HttpResponse response, String replacement) { + super(); + this.response = response; + this.replacement = replacement; + } + + @Override + public HttpResponse delegate() { + return response; + } + + @Override + public HttpResponse withBody() { + return withoutBody(); + } + + @Override + public HttpResponse withoutBody() { + return new BodyReplacementHttpResponse(response.withoutBody(), replacement); + } + + @Override + public byte[] getBody() { + return replacement.getBytes(UTF_8); + } + + @Override + public String getBodyAsString() { + return replacement; + } + +} \ No newline at end of file diff --git a/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/PathPrefixRequestMatcherRequestFilter.java b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/PathPrefixRequestMatcherRequestFilter.java new file mode 100644 index 0000000..d4d533e --- /dev/null +++ b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/PathPrefixRequestMatcherRequestFilter.java @@ -0,0 +1,35 @@ +package no.entur.logging.cloud.logbook.filter.path; + +import no.entur.logging.cloud.logbook.filter.path.matcher.MatcherPathFilterCollection; +import no.entur.logging.cloud.logbook.filter.path.matcher.PathFilterMatcher; +import no.entur.logging.cloud.logbook.filter.path.matcher.SimplePathFilterMatcher; +import org.zalando.logbook.BodyFilter; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class PathPrefixRequestMatcherRequestFilter { + + private Map pathPrefixFilters = new HashMap<>(); + + public PathPrefixRequestMatcherRequestFilter withPathPrefixFilter(String path, BodyFilter bodyFilter) { + pathPrefixFilters.put(path, bodyFilter); + return this; + } + + public RequestMatcherRequestFilter build() { + List matchers = new ArrayList<>(pathPrefixFilters.size()); + + for (Map.Entry entry : pathPrefixFilters.entrySet()) { + + SimplePathFilterMatcher matcher = new SimplePathFilterMatcher( (path) -> path != null && path.startsWith(entry.getKey()), entry.getValue()); + + matchers.add(matcher); + } + MatcherPathFilterCollection collection = new MatcherPathFilterCollection(matchers); + return new RequestMatcherRequestFilter(collection); + } + +} diff --git a/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/PathPrefixRequestResponseMatcherResponseFilterBuilder.java b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/PathPrefixRequestResponseMatcherResponseFilterBuilder.java new file mode 100644 index 0000000..738f999 --- /dev/null +++ b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/PathPrefixRequestResponseMatcherResponseFilterBuilder.java @@ -0,0 +1,35 @@ +package no.entur.logging.cloud.logbook.filter.path; + +import no.entur.logging.cloud.logbook.filter.path.matcher.MatcherPathFilterCollection; +import no.entur.logging.cloud.logbook.filter.path.matcher.PathFilterMatcher; +import no.entur.logging.cloud.logbook.filter.path.matcher.SimplePathFilterMatcher; +import org.zalando.logbook.BodyFilter; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class PathPrefixRequestResponseMatcherResponseFilterBuilder { + + private Map pathPrefixFilters = new HashMap<>(); + + public PathPrefixRequestResponseMatcherResponseFilterBuilder withPathPrefixFilter(String path, BodyFilter bodyFilter) { + pathPrefixFilters.put(path, bodyFilter); + return this; + } + + public RequestResponseMatcherResponseFilter build() { + List matchers = new ArrayList<>(pathPrefixFilters.size()); + + for (Map.Entry entry : pathPrefixFilters.entrySet()) { + + SimplePathFilterMatcher matcher = new SimplePathFilterMatcher( (path) -> path != null && path.startsWith(entry.getKey()), entry.getValue()); + + matchers.add(matcher); + } + MatcherPathFilterCollection collection = new MatcherPathFilterCollection(matchers); + return new RequestResponseMatcherResponseFilter(collection); + } + +} diff --git a/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/RequestFilterCollection.java b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/RequestFilterCollection.java new file mode 100644 index 0000000..08f53f2 --- /dev/null +++ b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/RequestFilterCollection.java @@ -0,0 +1,17 @@ +package no.entur.logging.cloud.logbook.filter.path; + +import org.zalando.logbook.BodyFilter; +import org.zalando.logbook.HttpRequest; + +/** + * + * Interface for returning the proper {@link BodyFilter} for a given request path.

+ * + * + */ + +public interface RequestFilterCollection { + + BodyFilter getBodyFilter(HttpRequest request, int size); + +} diff --git a/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/RequestMatcherRequestFilter.java b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/RequestMatcherRequestFilter.java new file mode 100644 index 0000000..f1791f8 --- /dev/null +++ b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/RequestMatcherRequestFilter.java @@ -0,0 +1,49 @@ +package no.entur.logging.cloud.logbook.filter.path; + +import java.io.IOException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.zalando.logbook.BodyFilter; +import org.zalando.logbook.HttpRequest; +import org.zalando.logbook.RequestFilter; + +public class RequestMatcherRequestFilter implements RequestFilter { + + private final static Logger log = LoggerFactory.getLogger(RequestMatcherRequestFilter.class); + + public static PathPrefixRequestMatcherRequestFilter newPathPrefixBuilder() { + return new PathPrefixRequestMatcherRequestFilter(); + } + + private final RequestFilterCollection filter; + + public RequestMatcherRequestFilter(RequestFilterCollection filter) { + this.filter = filter; + } + + @Override + public HttpRequest filter(HttpRequest request) { + try { + String body = request.getBodyAsString(); + + if(body == null || body.length() == 0) { + return request; + } + + BodyFilter f = filter.getBodyFilter(request, body.length()); + + if(f != null) { + String filtered = f.filter(request.getContentType(), body); + if(filtered != null) { + return new BodyReplacementHttpRequest(request, filtered); + } + } + } catch (IOException e) { + log.warn("Problem filtering request body", e); + } + + return request; + } + +} diff --git a/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/RequestResponseFilterCollection.java b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/RequestResponseFilterCollection.java new file mode 100644 index 0000000..22f9443 --- /dev/null +++ b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/RequestResponseFilterCollection.java @@ -0,0 +1,17 @@ +package no.entur.logging.cloud.logbook.filter.path; + +import org.zalando.logbook.BodyFilter; +import org.zalando.logbook.HttpRequest; +import org.zalando.logbook.HttpResponse; + +/** + * + * Interface for returning the proper {@link BodyFilter} for a given response path.

+ * + */ + +public interface RequestResponseFilterCollection { + + BodyFilter getBodyFilter(HttpRequest request, HttpResponse httpResponse, int size); + +} diff --git a/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/RequestResponseMatcherResponseFilter.java b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/RequestResponseMatcherResponseFilter.java new file mode 100644 index 0000000..f5b3958 --- /dev/null +++ b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/RequestResponseMatcherResponseFilter.java @@ -0,0 +1,76 @@ +package no.entur.logging.cloud.logbook.filter.path; + +import java.io.IOException; + +import no.entur.logging.cloud.logbook.filter.path.strategy.RequestHttpResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.zalando.logbook.BodyFilter; +import org.zalando.logbook.ForwardingHttpResponse; +import org.zalando.logbook.HttpResponse; +import org.zalando.logbook.ResponseFilter; + +/** + * + * This filter only works with strategies like {@link no.entur.logging.cloud.logbook.filter.path.strategy.MatchPathStrategy} + * which insert a {@link RequestHttpResponse} as delegate for {@link HttpResponse} of subclass {@link ForwardingHttpResponse}. + */ + +public class RequestResponseMatcherResponseFilter implements ResponseFilter { + + private final static Logger log = LoggerFactory.getLogger(RequestResponseMatcherResponseFilter.class); + + public static PathPrefixRequestResponseMatcherResponseFilterBuilder newPathPrefixBuilder() { + return new PathPrefixRequestResponseMatcherResponseFilterBuilder(); + } + + private final RequestResponseFilterCollection filter; + + public RequestResponseMatcherResponseFilter(RequestResponseFilterCollection filter) { + this.filter = filter; + } + + @Override + public HttpResponse filter(HttpResponse response) { + RequestHttpResponse target = null; + + HttpResponse next = response; + do { + if(next instanceof RequestHttpResponse) { + target = (RequestHttpResponse)next; + break; + } + if(next instanceof ForwardingHttpResponse) { + ForwardingHttpResponse f = (ForwardingHttpResponse)next; + next = f.delegate(); + continue; + } + break; + } while(next != null); + + if(target == null) { + return response; + } + + try { + String body = response.getBodyAsString(); + if(body == null || body.length() == 0) { + return response; + } + + BodyFilter f = filter.getBodyFilter(target.getRequest(), response, body.length()); + + if(f != null) { + String filtered = f.filter(response.getContentType(), body); + if(filtered != null) { + return new BodyReplacementHttpResponse(response, filtered); + } + } + } catch (IOException e) { + log.warn("Problem filtering response body", e); + } + + return response; + } + +} diff --git a/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/matcher/MatcherPathFilterCollection.java b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/matcher/MatcherPathFilterCollection.java new file mode 100644 index 0000000..4439297 --- /dev/null +++ b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/matcher/MatcherPathFilterCollection.java @@ -0,0 +1,46 @@ +package no.entur.logging.cloud.logbook.filter.path.matcher; + +import no.entur.logging.cloud.logbook.filter.path.RequestFilterCollection; +import no.entur.logging.cloud.logbook.filter.path.RequestResponseFilterCollection; +import org.zalando.logbook.BodyFilter; +import org.zalando.logbook.HttpRequest; +import org.zalando.logbook.HttpResponse; +import org.zalando.logbook.Origin; + +import java.util.List; + +public class MatcherPathFilterCollection implements RequestFilterCollection, RequestResponseFilterCollection { + + protected final PathFilterMatcher[] paths; + + public MatcherPathFilterCollection(List requests) { + this(requests.toArray(new PathFilterMatcher[requests.size()])); + } + + public MatcherPathFilterCollection(PathFilterMatcher[] requests) { + this.paths = requests; + } + + public BodyFilter getBodyFilter(String path, Origin origin, int size) { + for(PathFilterMatcher matcher : paths) { + if(matcher.matches(path)) { + return matcher.getBodyFilter(origin, size); + } + } + return null; + } + + protected PathFilterMatcher[] getFilters() { + return paths; + } + + @Override + public BodyFilter getBodyFilter(HttpRequest request, int size) { + return getBodyFilter(request.getPath(), request.getOrigin(), size); + } + + @Override + public BodyFilter getBodyFilter(HttpRequest request, HttpResponse httpResponse, int size) { + return getBodyFilter(request.getPath(), httpResponse.getOrigin(), size); + } +} diff --git a/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/matcher/PathFilterMatcher.java b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/matcher/PathFilterMatcher.java new file mode 100644 index 0000000..46cb625 --- /dev/null +++ b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/matcher/PathFilterMatcher.java @@ -0,0 +1,12 @@ +package no.entur.logging.cloud.logbook.filter.path.matcher; + +import org.zalando.logbook.BodyFilter; +import org.zalando.logbook.Origin; + +public interface PathFilterMatcher { + + boolean matches(String path); + + BodyFilter getBodyFilter(Origin origin, int size); + +} diff --git a/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/matcher/SimplePathFilterMatcher.java b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/matcher/SimplePathFilterMatcher.java new file mode 100644 index 0000000..16f061f --- /dev/null +++ b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/matcher/SimplePathFilterMatcher.java @@ -0,0 +1,34 @@ +package no.entur.logging.cloud.logbook.filter.path.matcher; + +import org.zalando.logbook.BodyFilter; +import org.zalando.logbook.Origin; + +import java.util.function.Predicate; + +/** + * + * Matcher which returns the same filter for all origins and sizes. + * + */ + +public class SimplePathFilterMatcher implements PathFilterMatcher { + + protected final Predicate matcher; + protected final BodyFilter filter; + + public SimplePathFilterMatcher(Predicate matcher, BodyFilter filter) { + this.matcher = matcher; + this.filter = filter; + } + + @Override + public boolean matches(String path) { + return matcher.test(path); + } + + @Override + public BodyFilter getBodyFilter(Origin origin, int size) { + return filter; + } + +} diff --git a/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/matcher/SizeOriginPathFilterMatcher.java b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/matcher/SizeOriginPathFilterMatcher.java new file mode 100644 index 0000000..611f427 --- /dev/null +++ b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/matcher/SizeOriginPathFilterMatcher.java @@ -0,0 +1,56 @@ +package no.entur.logging.cloud.logbook.filter.path.matcher; + +import org.zalando.logbook.BodyFilter; +import org.zalando.logbook.Origin; + +import java.util.function.Predicate; + +/** + * + * Matcher which selects a filter based on origin and max size. + * + */ + + +public class SizeOriginPathFilterMatcher implements PathFilterMatcher { + + protected final BodyFilter remoteFilter; + protected final BodyFilter remoteMaxSizeFilter; + + protected final BodyFilter localFilter; + protected final BodyFilter localMaxSizeFilter; + + protected final int maxSize; + + protected final Predicate matcher; + + public SizeOriginPathFilterMatcher(Predicate matcher, BodyFilter remoteFilter, BodyFilter remoteMaxSizeFilter, BodyFilter localFilter, BodyFilter localMaxSizeFilter, int maxSize) { + this.remoteFilter = remoteFilter; + this.remoteMaxSizeFilter = remoteMaxSizeFilter; + this.localFilter = localFilter; + this.localMaxSizeFilter = localMaxSizeFilter; + this.maxSize = maxSize; + + this.matcher = matcher; + } + + @Override + public boolean matches(String path) { + return matcher.test(path); + } + + @Override + public BodyFilter getBodyFilter(Origin origin, int size) { + if(origin == Origin.REMOTE) { + if(size > maxSize) { + return remoteMaxSizeFilter; + } + return remoteFilter; + } + if(size > maxSize) { + return localMaxSizeFilter; + } + return localFilter; + } + +} diff --git a/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/matcher/SizePathFilterMatcher.java b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/matcher/SizePathFilterMatcher.java new file mode 100644 index 0000000..be6e38d --- /dev/null +++ b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/matcher/SizePathFilterMatcher.java @@ -0,0 +1,44 @@ +package no.entur.logging.cloud.logbook.filter.path.matcher; + +import org.zalando.logbook.BodyFilter; +import org.zalando.logbook.Origin; + +import java.util.function.Predicate; + +/** + * + * Matcher which selects a filter based on a max size. + * + */ + +public class SizePathFilterMatcher implements PathFilterMatcher { + + protected final BodyFilter filter; + protected final BodyFilter maxSizeFilter; + + protected final int maxSize; + + protected final Predicate matcher; + + public SizePathFilterMatcher(Predicate matcher, BodyFilter filter, BodyFilter maxSizeFilter, int maxSize) { + this.filter = filter; + this.maxSizeFilter = maxSizeFilter; + this.maxSize = maxSize; + + this.matcher = matcher; + } + + @Override + public boolean matches(String path) { + return matcher.test(path); + } + + @Override + public BodyFilter getBodyFilter(Origin origin, int size) { + if(size > maxSize) { + return maxSizeFilter; + } + return filter; + } + +} diff --git a/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/strategy/DelegateRequestHttpResponse.java b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/strategy/DelegateRequestHttpResponse.java new file mode 100644 index 0000000..3e9256f --- /dev/null +++ b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/strategy/DelegateRequestHttpResponse.java @@ -0,0 +1,80 @@ +package no.entur.logging.cloud.logbook.filter.path.strategy; + +import java.io.IOException; +import java.nio.charset.Charset; + +import org.zalando.logbook.HttpHeaders; +import org.zalando.logbook.HttpRequest; +import org.zalando.logbook.HttpResponse; +import org.zalando.logbook.Origin; +import org.zalando.logbook.attributes.HttpAttributes; + +/** + * A {@link RequestHttpResponse} which remembers its path. + */ + +public class DelegateRequestHttpResponse implements RequestHttpResponse { + + private HttpResponse response; + private HttpRequest request; + + public DelegateRequestHttpResponse(HttpRequest request, HttpResponse response, String path) { + this.request = request; + this.response = response; + } + + @Override + public HttpRequest getRequest() { + return request; + } + + public int getStatus() { + return response.getStatus(); + } + + public HttpResponse withBody() throws IOException { + return response.withBody(); + } + + public String getProtocolVersion() { + return response.getProtocolVersion(); + } + + public HttpResponse withoutBody() { + return response.withoutBody(); + } + + public Origin getOrigin() { + return response.getOrigin(); + } + + public HttpAttributes getAttributes() { + return response.getAttributes(); + } + + public HttpHeaders getHeaders() { + return response.getHeaders(); + } + + public String getContentType() { + return response.getContentType(); + } + + public String getReasonPhrase() { + return response.getReasonPhrase(); + } + + public Charset getCharset() { + return response.getCharset(); + } + + public byte[] getBody() throws IOException { + return response.getBody(); + } + + public String getBodyAsString() throws IOException { + return response.getBodyAsString(); + } + + +} diff --git a/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/strategy/JsonOnlyMatchPathStrategy.java b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/strategy/JsonOnlyMatchPathStrategy.java new file mode 100644 index 0000000..577668c --- /dev/null +++ b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/strategy/JsonOnlyMatchPathStrategy.java @@ -0,0 +1,33 @@ +package no.entur.logging.cloud.logbook.filter.path.strategy; + +import org.zalando.logbook.ContentType; +import org.zalando.logbook.HttpRequest; +import org.zalando.logbook.HttpResponse; + +import java.util.List; +import java.util.function.Predicate; + +public class JsonOnlyMatchPathStrategy extends MatchPathStrategy { + + public JsonOnlyMatchPathStrategy(List> includeRequests, List> includeResponses) { + super(includeRequests, includeResponses); + } + + @Override + protected boolean includeRequest(HttpRequest request) { + String contentType = request.getContentType(); + if(contentType != null && !ContentType.isJsonMediaType(contentType)) { + return false; + } + return super.includeRequest(request); + } + + @Override + protected boolean includeResponse(HttpRequest request, HttpResponse response) { + String contentType = response.getContentType(); + if(contentType != null && !ContentType.isJsonMediaType(contentType)) { + return false; + } + return super.includeResponse(request, response); + } +} diff --git a/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/strategy/MatchPathStrategy.java b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/strategy/MatchPathStrategy.java new file mode 100644 index 0000000..ac89cc0 --- /dev/null +++ b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/strategy/MatchPathStrategy.java @@ -0,0 +1,58 @@ +package no.entur.logging.cloud.logbook.filter.path.strategy; + +import java.io.IOException; +import java.util.List; +import java.util.function.Predicate; + +import org.zalando.logbook.HttpRequest; +import org.zalando.logbook.HttpResponse; +import org.zalando.logbook.Strategy; + +public class MatchPathStrategy implements Strategy { + + private final List> requestsMatchers; + private final List> responseMatchers; + + public MatchPathStrategy(List> requestsMatchers, List> responseMatchers) { + this.requestsMatchers = requestsMatchers; + this.responseMatchers = responseMatchers; + } + + public HttpRequest process(final HttpRequest request) throws IOException { + if(!includeRequest(request)) { + return request.withoutBody(); + } + return request.withBody(); + } + + public HttpResponse process(final HttpRequest request, final HttpResponse response) throws IOException { + if(!includeResponse(request, response)) { + return response.withoutBody(); + } + return new DelegateRequestHttpResponse(request, response.withBody(), request.getPath()); + } + + protected boolean includeRequest(HttpRequest request) { + String path = request.getPath(); + if(path != null) { + for(Predicate m : requestsMatchers) { + if(m.test(path)) { + return true; + } + } + } + return false; + } + + protected boolean includeResponse(HttpRequest request, HttpResponse response) { + String path = request.getPath(); + if(path != null) { + for(Predicate m : responseMatchers) { + if(m.test(path)) { + return true; + } + } + } + return false; + } +} diff --git a/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/strategy/RequestHttpResponse.java b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/strategy/RequestHttpResponse.java new file mode 100644 index 0000000..db58ac6 --- /dev/null +++ b/request-response/logbook-filter/src/main/java/no/entur/logging/cloud/logbook/filter/path/strategy/RequestHttpResponse.java @@ -0,0 +1,10 @@ +package no.entur.logging.cloud.logbook.filter.path.strategy; + +import org.zalando.logbook.HttpRequest; +import org.zalando.logbook.HttpResponse; + +public interface RequestHttpResponse extends HttpResponse { + + HttpRequest getRequest(); + +} diff --git a/request-response/logbook-filter/src/test/java/no/entur/logging/cloud/logbook/filter/path/RequestResponseMatcherResponseFilterTest.java b/request-response/logbook-filter/src/test/java/no/entur/logging/cloud/logbook/filter/path/RequestResponseMatcherResponseFilterTest.java new file mode 100644 index 0000000..fb0eddd --- /dev/null +++ b/request-response/logbook-filter/src/test/java/no/entur/logging/cloud/logbook/filter/path/RequestResponseMatcherResponseFilterTest.java @@ -0,0 +1,53 @@ +package no.entur.logging.cloud.logbook.filter.path; + +import no.entur.logging.cloud.logbook.filter.JsonMaxValueLengthBodyFilter; +import no.entur.logging.cloud.logbook.filter.path.strategy.JsonOnlyMatchPathStrategy; +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.zalando.logbook.HttpRequest; +import org.zalando.logbook.HttpResponse; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; + +import static org.mockito.Mockito.*; + +public class RequestResponseMatcherResponseFilterTest { + + + @Test + public void testBuilder() throws Exception { + String requestBody = IOUtils.resourceToString("/input/request.json", StandardCharsets.UTF_8); + String responseBody = IOUtils.resourceToString("/input/response.json", StandardCharsets.UTF_8); + + List> baseline = Arrays.asList( (path) -> path.startsWith("/api")); + JsonOnlyMatchPathStrategy strategy = new JsonOnlyMatchPathStrategy(baseline, baseline); + + HttpRequest request = mock(HttpRequest.class); + when(request.getPath()).thenReturn("/api/customer/add"); + when(request.getContentType()).thenReturn("application/json"); + when(request.withBody()).thenReturn(request); + when(request.getBodyAsString()).thenReturn(requestBody); + + HttpResponse response = mock(HttpResponse.class); + when(response.withBody()).thenReturn(response); + when(response.getContentType()).thenReturn("application/json"); + when(response.getBodyAsString()).thenReturn(responseBody); + + HttpRequest httpRequest = strategy.process(request); + HttpResponse httpResponse = strategy.process(request, response); + + RequestMatcherRequestFilter requestFilter = RequestMatcherRequestFilter.newPathPrefixBuilder().withPathPrefixFilter("/api/customer", new JsonMaxValueLengthBodyFilter(7)).build(); + RequestResponseMatcherResponseFilter responseFilter = RequestResponseMatcherResponseFilter.newPathPrefixBuilder().withPathPrefixFilter("/api/customer", new JsonMaxValueLengthBodyFilter(8)).build(); + + HttpRequest filteredRequest = requestFilter.filter(httpRequest); + HttpResponse filteredResponse = responseFilter.filter(httpResponse); + + Assertions.assertEquals(filteredRequest.getBodyAsString(), IOUtils.resourceToString("/output/request.json", StandardCharsets.UTF_8)); + Assertions.assertEquals(filteredResponse.getBodyAsString(), IOUtils.resourceToString("/output/response.json", StandardCharsets.UTF_8)); + } + +} diff --git a/request-response/logbook-filter/src/test/resources/input/request.json b/request-response/logbook-filter/src/test/resources/input/request.json new file mode 100644 index 0000000..feb45b9 --- /dev/null +++ b/request-response/logbook-filter/src/test/resources/input/request.json @@ -0,0 +1 @@ +{"title":"Request: Long text explaining this request"} \ No newline at end of file diff --git a/request-response/logbook-filter/src/test/resources/input/response.json b/request-response/logbook-filter/src/test/resources/input/response.json new file mode 100644 index 0000000..d891eec --- /dev/null +++ b/request-response/logbook-filter/src/test/resources/input/response.json @@ -0,0 +1 @@ +{"title":"Response: Long text explaining this response"} \ No newline at end of file diff --git a/request-response/logbook-filter/src/test/resources/output/request.json b/request-response/logbook-filter/src/test/resources/output/request.json new file mode 100644 index 0000000..f25fc0b --- /dev/null +++ b/request-response/logbook-filter/src/test/resources/output/request.json @@ -0,0 +1 @@ +{"title":"Request[filtered by logger]"} \ No newline at end of file diff --git a/request-response/logbook-filter/src/test/resources/output/response.json b/request-response/logbook-filter/src/test/resources/output/response.json new file mode 100644 index 0000000..c37bede --- /dev/null +++ b/request-response/logbook-filter/src/test/resources/output/response.json @@ -0,0 +1 @@ +{"title":"Response[filtered by logger]"} \ No newline at end of file diff --git a/request-response/logbook/build.gradle b/request-response/logbook/build.gradle index 7571e0e..6bd058b 100644 --- a/request-response/logbook/build.gradle +++ b/request-response/logbook/build.gradle @@ -2,6 +2,7 @@ dependencies { api("org.slf4j:slf4j-api:${slf4jVersion}") api project(':appender') + api project(':request-response:logbook-filter') api("org.zalando:logbook-api:${logbookVersion}") api("org.zalando:logbook-api:${logbookVersion}") diff --git a/settings.gradle b/settings.gradle index e683f01..b6433f6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -33,7 +33,7 @@ include 'azure:logbook-spring-boot-autoconfigure-azure' include 'trace:mdc-context-grpc-netty', 'trace:server:correlation-id-trace-spring-boot-web', 'trace:server:correlation-id-trace-grpc-netty', 'trace:server:correlation-id-trace-spring-boot-grpc' include 'request-response:netty-grpc', 'request-response:netty-grpc-test', 'request-response:request-response-spring-boot-autoconfigure-grpc' -include 'request-response:logbook', 'request-response:logbook-test', 'request-response:logbook-spring-boot-autoconfigure', 'request-response:logbook-spring-boot-autoconfigure-test' +include 'request-response:logbook-filter', 'request-response:logbook', 'request-response:logbook-test', 'request-response:logbook-spring-boot-autoconfigure', 'request-response:logbook-spring-boot-autoconfigure-test' include 'on-demand:on-demand-spring-boot-starter-web', 'on-demand:on-demand-spring-boot-starter-grpc' include 'request-response:request-response-spring-boot-autoconfigure-web', 'request-response:request-response-spring-boot-autoconfigure-grpc-lognet', 'request-response:request-response-spring-boot-autoconfigure-grpc-ecosystem'