Skip to content

Commit

Permalink
Merge pull request #878 from rjmveloso/feature/json-field-writer
Browse files Browse the repository at this point in the history
Externalize a handler to write json fields
  • Loading branch information
Willi Schönborn authored Nov 3, 2020
2 parents 2d56b0d + 092216a commit 90eda0f
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 119 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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.
Expand All @@ -30,67 +30,39 @@ 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<C extends Precorrelation, H extends HttpMessage> {
void format(C correlation, H message, JsonGenerator generator) throws IOException;
}

@Override
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
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 <C extends Precorrelation, H extends HttpMessage> String format(
Expand All @@ -103,92 +75,56 @@ private <C extends Precorrelation, H extends HttpMessage> 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<C extends Precorrelation, H extends HttpMessage> {
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<String, List<String>> headers = message.getHeaders();

if (headers.isEmpty()) {
return;
@Override
public <M extends HttpMessage> 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<String, List<String>> 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);
}

}
Original file line number Diff line number Diff line change
@@ -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 {

<M extends HttpMessage> 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";
}

}

0 comments on commit 90eda0f

Please sign in to comment.