From 8a7a7743d2f04beb8197759ab0cf6a563d133acb Mon Sep 17 00:00:00 2001 From: rjmveloso <> Date: Mon, 2 Nov 2020 20:02:04 +0000 Subject: [PATCH 1/2] Externalize a handler to write json fields --- .../json/FastJsonHttpLogFormatter.java | 131 +++++------------- .../zalando/logbook/json/JsonFieldWriter.java | 50 +++++++ 2 files changed, 83 insertions(+), 98 deletions(-) create mode 100644 logbook-json/src/main/java/org/zalando/logbook/json/JsonFieldWriter.java diff --git a/logbook-json/src/main/java/org/zalando/logbook/json/FastJsonHttpLogFormatter.java b/logbook-json/src/main/java/org/zalando/logbook/json/FastJsonHttpLogFormatter.java index 42b0db317..0946be5db 100644 --- a/logbook-json/src/main/java/org/zalando/logbook/json/FastJsonHttpLogFormatter.java +++ b/logbook-json/src/main/java/org/zalando/logbook/json/FastJsonHttpLogFormatter.java @@ -1,9 +1,12 @@ package org.zalando.logbook.json; -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.AllArgsConstructor; +import static org.apiguardian.api.API.Status.STABLE; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.List; +import java.util.Map; + import org.apiguardian.api.API; import org.zalando.logbook.Correlation; import org.zalando.logbook.HttpLogFormatter; @@ -12,24 +15,23 @@ import org.zalando.logbook.HttpResponse; import org.zalando.logbook.Precorrelation; -import java.io.IOException; -import java.io.StringWriter; -import java.util.List; -import java.util.Map; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; -import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.apiguardian.api.API.Status.STABLE; -import static org.zalando.logbook.Origin.LOCAL; +import lombok.RequiredArgsConstructor; /** * A custom {@link HttpLogFormatter} that produces JSON objects. */ @API(status = STABLE) -@AllArgsConstructor -public final class FastJsonHttpLogFormatter implements HttpLogFormatter { +@RequiredArgsConstructor +public final class FastJsonHttpLogFormatter implements HttpLogFormatter, JsonFieldWriter { private final JsonFactory factory; + private JsonFieldWriter delegate = this; // default to this implementation + public FastJsonHttpLogFormatter() { this(new ObjectMapper()); } @@ -38,31 +40,23 @@ public FastJsonHttpLogFormatter(final ObjectMapper mapper) { this(mapper.getFactory()); } - @Override - public String format( - final Precorrelation precorrelation, - final HttpRequest request) throws IOException { + public FastJsonHttpLogFormatter(final ObjectMapper mapper, + final JsonFieldWriter writer) { + this(mapper.getFactory()); + this.delegate = writer; + } - return format(precorrelation, request, this::prepare); + @FunctionalInterface + private interface Formatter { + void format(C correlation, H message, JsonGenerator generator) throws IOException; } - @API(status = EXPERIMENTAL) - public void prepare( + @Override + public String format( final Precorrelation precorrelation, - final HttpRequest request, - final JsonGenerator generator) throws IOException { + final HttpRequest request) throws IOException { - generator.writeStringField("origin", - request.getOrigin() == LOCAL ? "local" : "remote"); - generator.writeStringField("type", "request"); - generator.writeStringField("correlation", precorrelation.getId()); - generator.writeStringField("protocol", request.getProtocolVersion()); - generator.writeStringField("remote", request.getRemote()); - generator.writeStringField("method", request.getMethod()); - generator.writeStringField("uri", reconstructUri(request)); - - writeHeaders(request, generator); - writeBody(request, generator); + return format(precorrelation, request, delegate::write); } @Override @@ -70,27 +64,7 @@ public String format( final Correlation correlation, final HttpResponse response) throws IOException { - return format(correlation, response, this::prepare); - } - - @API(status = EXPERIMENTAL) - public void prepare( - final Correlation correlation, - final HttpResponse response, - final JsonGenerator generator) throws IOException { - - final String correlationId = correlation.getId(); - - generator.writeStringField("origin", - response.getOrigin() == LOCAL ? "local" : "remote"); - generator.writeStringField("type", "response"); - generator.writeStringField("correlation", correlationId); - generator.writeStringField("protocol", response.getProtocolVersion()); - generator.writeNumberField("duration", correlation.getDuration().toMillis()); - generator.writeNumberField("status", response.getStatus()); - - writeHeaders(response, generator); - writeBody(response, generator); + return format(correlation, response, delegate::write); } private String format( @@ -103,15 +77,17 @@ private String format( try (final JsonGenerator generator = factory.createGenerator(writer)) { generator.writeStartObject(); formatter.format(correlation, message, generator); + delegate.write(message, generator); generator.writeEndObject(); } return writer.toString(); } - @FunctionalInterface - private interface Formatter { - void format(C correlation, H message, JsonGenerator generator) throws IOException; + @Override + public void write(M message, JsonGenerator generator) throws IOException { + writeHeaders(message, generator); + writeBody(message, generator); } private void writeHeaders( @@ -150,45 +126,4 @@ private void writeBody( } } - private String reconstructUri(final HttpRequest request) { - final StringBuilder builder = new StringBuilder(256); - - final String scheme = request.getScheme(); - builder.append(scheme); - builder.append("://"); - builder.append(request.getHost()); - appendPort(request, builder); - builder.append(request.getPath()); - appendQuery(request, builder); - - return builder.toString(); - } - - private void appendPort(final HttpRequest request, final StringBuilder builder) { - request.getPort().ifPresent(port -> { - final String scheme = request.getScheme(); - if (isStandardPort(scheme, port)) { - return; - } - - builder.append(':').append(port); - }); - } - - private void appendQuery(final HttpRequest request, final StringBuilder builder) { - final String query = request.getQuery(); - - if (query.isEmpty()) { - return; - } - - builder.append('?'); - builder.append(query); - } - - private boolean isStandardPort(final String scheme, final int port) { - return ("http".equals(scheme) && port == 80) - || ("https".equals(scheme) && port == 443); - } - } diff --git a/logbook-json/src/main/java/org/zalando/logbook/json/JsonFieldWriter.java b/logbook-json/src/main/java/org/zalando/logbook/json/JsonFieldWriter.java new file mode 100644 index 000000000..824c1ebf6 --- /dev/null +++ b/logbook-json/src/main/java/org/zalando/logbook/json/JsonFieldWriter.java @@ -0,0 +1,50 @@ +package org.zalando.logbook.json; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.zalando.logbook.Origin.LOCAL; + +import java.io.IOException; + +import org.apiguardian.api.API; +import org.zalando.logbook.Correlation; +import org.zalando.logbook.HttpMessage; +import org.zalando.logbook.HttpRequest; +import org.zalando.logbook.HttpResponse; +import org.zalando.logbook.Precorrelation; + +import com.fasterxml.jackson.core.JsonGenerator; + +public interface JsonFieldWriter { + + void write(M message, JsonGenerator generator) throws IOException; + + @API(status = EXPERIMENTAL) + default void write(Precorrelation correlation, HttpRequest request, JsonGenerator generator) throws IOException { + generator.writeStringField("origin", getOrigin(request)); + generator.writeStringField("type", "request"); + generator.writeStringField("correlation", correlation.getId()); + generator.writeStringField("protocol", request.getProtocolVersion()); + generator.writeStringField("remote", request.getRemote()); + generator.writeStringField("method", request.getMethod()); + generator.writeStringField("uri", request.getRequestUri()); + + write(request, generator); + } + + @API(status = EXPERIMENTAL) + default void write(Correlation correlation, HttpResponse response, JsonGenerator generator) throws IOException { + generator.writeStringField("origin", getOrigin(response)); + generator.writeStringField("type", "response"); + generator.writeStringField("correlation", correlation.getId()); + generator.writeStringField("protocol", response.getProtocolVersion()); + generator.writeNumberField("duration", correlation.getDuration().toMillis()); + generator.writeNumberField("status", response.getStatus()); + + write(response, generator); + } + + static String getOrigin(HttpMessage message) { + return message.getOrigin() == LOCAL ? "local" : "remote"; + } + +} From 092216a5e1fe7aa7b4fb9b28a37b124397fcf232 Mon Sep 17 00:00:00 2001 From: rjmveloso <> Date: Tue, 3 Nov 2020 09:39:29 +0000 Subject: [PATCH 2/2] Prevent expose FastJsonHttpLogFormatter as a JsonFieldWriter itself --- .../json/FastJsonHttpLogFormatter.java | 79 ++++++++++--------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/logbook-json/src/main/java/org/zalando/logbook/json/FastJsonHttpLogFormatter.java b/logbook-json/src/main/java/org/zalando/logbook/json/FastJsonHttpLogFormatter.java index 0946be5db..764465396 100644 --- a/logbook-json/src/main/java/org/zalando/logbook/json/FastJsonHttpLogFormatter.java +++ b/logbook-json/src/main/java/org/zalando/logbook/json/FastJsonHttpLogFormatter.java @@ -19,31 +19,29 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.RequiredArgsConstructor; +import lombok.AllArgsConstructor; /** * A custom {@link HttpLogFormatter} that produces JSON objects. */ @API(status = STABLE) -@RequiredArgsConstructor -public final class FastJsonHttpLogFormatter implements HttpLogFormatter, JsonFieldWriter { +@AllArgsConstructor +public final class FastJsonHttpLogFormatter implements HttpLogFormatter { private final JsonFactory factory; - private JsonFieldWriter delegate = this; // default to this implementation + private final JsonFieldWriter delegate; public FastJsonHttpLogFormatter() { this(new ObjectMapper()); } public FastJsonHttpLogFormatter(final ObjectMapper mapper) { - this(mapper.getFactory()); + this(mapper, new DefaultJsonFieldWriter()); } - public FastJsonHttpLogFormatter(final ObjectMapper mapper, - final JsonFieldWriter writer) { - this(mapper.getFactory()); - this.delegate = writer; + public FastJsonHttpLogFormatter(final ObjectMapper mapper, final JsonFieldWriter writer) { + this(mapper.getFactory(), writer); } @FunctionalInterface @@ -84,45 +82,48 @@ private String format( return writer.toString(); } - @Override - public void write(M message, JsonGenerator generator) throws IOException { - writeHeaders(message, generator); - writeBody(message, generator); - } + private static class DefaultJsonFieldWriter implements JsonFieldWriter { - private void writeHeaders( - final HttpMessage message, - final JsonGenerator generator) throws IOException { + @Override + public void write(M message, JsonGenerator generator) throws IOException { + writeHeaders(message, generator); + writeBody(message, generator); + } + + private void writeHeaders( + final HttpMessage message, + final JsonGenerator generator) throws IOException { - final Map> headers = message.getHeaders(); + final Map> headers = message.getHeaders(); - if (headers.isEmpty()) { - return; - } + if (headers.isEmpty()) { + return; + } - // implementation note: - // for some unclear reason, manually iterating over the headers - // while writing performs worse than letting Jackson do the job. - generator.writeObjectField("headers", headers); - } + // implementation note: + // for some unclear reason, manually iterating over the headers + // while writing performs worse than letting Jackson do the job. + generator.writeObjectField("headers", headers); + } - private void writeBody( - final HttpMessage message, - final JsonGenerator generator) throws IOException { + private void writeBody( + final HttpMessage message, + final JsonGenerator generator) throws IOException { - final String body = message.getBodyAsString(); + final String body = message.getBodyAsString(); - if (body.isEmpty()) { - return; - } - generator.writeFieldName("body"); + if (body.isEmpty()) { + return; + } + generator.writeFieldName("body"); - final String contentType = message.getContentType(); + final String contentType = message.getContentType(); - if (JsonMediaType.JSON.test(contentType)) { - generator.writeRawValue(body); - } else { - generator.writeString(body); + if (JsonMediaType.JSON.test(contentType)) { + generator.writeRawValue(body); + } else { + generator.writeString(body); + } } }