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..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 @@ -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,14 +15,11 @@ 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.AllArgsConstructor; /** * A custom {@link HttpLogFormatter} that produces JSON objects. @@ -30,12 +30,23 @@ public final class FastJsonHttpLogFormatter implements HttpLogFormatter { private final JsonFactory factory; + 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(), writer); + } + + @FunctionalInterface + private interface Formatter { + void format(C correlation, H message, JsonGenerator generator) throws IOException; } @Override @@ -43,26 +54,7 @@ public String format( final Precorrelation precorrelation, final HttpRequest request) throws IOException { - return format(precorrelation, request, this::prepare); - } - - @API(status = EXPERIMENTAL) - public void prepare( - final Precorrelation precorrelation, - final HttpRequest request, - final JsonGenerator generator) 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 +62,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,92 +75,56 @@ 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; - } - - private void writeHeaders( - final HttpMessage message, - final JsonGenerator generator) throws IOException { + private static class DefaultJsonFieldWriter implements JsonFieldWriter { - final Map> headers = message.getHeaders(); - - if (headers.isEmpty()) { - return; + @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 { - // 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 { + final Map> headers = message.getHeaders(); - final String body = message.getBodyAsString(); - - if (body.isEmpty()) { - return; - } - generator.writeFieldName("body"); - - final String contentType = message.getContentType(); + if (headers.isEmpty()) { + return; + } - if (JsonMediaType.JSON.test(contentType)) { - generator.writeRawValue(body); - } else { - generator.writeString(body); + // 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 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); + private void writeBody( + final HttpMessage message, + final JsonGenerator generator) throws IOException { - return builder.toString(); - } + final String body = message.getBodyAsString(); - private void appendPort(final HttpRequest request, final StringBuilder builder) { - request.getPort().ifPresent(port -> { - final String scheme = request.getScheme(); - if (isStandardPort(scheme, port)) { + if (body.isEmpty()) { return; } + generator.writeFieldName("body"); - builder.append(':').append(port); - }); - } - - private void appendQuery(final HttpRequest request, final StringBuilder builder) { - final String query = request.getQuery(); + final String contentType = message.getContentType(); - if (query.isEmpty()) { - return; + if (JsonMediaType.JSON.test(contentType)) { + generator.writeRawValue(body); + } else { + generator.writeString(body); + } } - - 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"; + } + +}