How to print server responses using LoggingFeature in Dropwizard 1.0.2? How to print server responses using LoggingFeature in Dropwizard 1.0.2? json json

How to print server responses using LoggingFeature in Dropwizard 1.0.2?


This does the trick:

new LoggingFeature(Logger.getLogger(getClass().getName()), Level.OFF, LoggingFeature.Verbosity.PAYLOAD_TEXT, 8192)

I'm guessing the logging feature in the client acts like a filter, rather than as an include as expected.


A short example to illustrate a common issue which makes developers think that the logging feature is not working.

private static final LOG = Logger.getLogger(getClass().getName());public void test() {    Client client = ClientBuilder.newBuilder()            .register(new LoggingFeature(LOG, Level.FINE, LoggingFeature.Verbosity.PAYLOAD_ALL, 8192))            .build();    // all requests and responses using this client will now be logged    // with the log-level FINE to the logger LOG, but the logger    // will simply ignore them, because it's default level is INFO}

The created logger instance LOG uses the default log level, which is INFO. That means it will accept all log messages with an level of at least INFO or higher (WARNING, SEVERE, ...), but it will ignore all messages with a lower level, like FINE. (It will still pass the message to it's parent logger, if there is one)

Notice: The default log level for handlers is Level.ALL and they should not reject any log records, as long you don't modify their level.

So you will need either to raise the LoggingFeatures level or lower the loggers level to see the messages.

Solution 1: Increasing the level of the LoggingFeature:

new LoggingFeature(LOG, Level.INFO, LoggingFeature.Verbosity.PAYLOAD_ALL, 8192)

Solution 2: Decreasing the level of the Logger:

LOG.setLevel(Level.FINE)


For logging the custom logs for Server and Client request/response using the LoggingFeature, you need to create a custom class that extends LoggingFeature and implement ContainerRequestFilter, ContainerResponseFilter, ClientRequestFilter, ClientResponseFilter and WriterInterceptor.

You need to override the LoggingFeature method public boolean configure(FeatureContext context) and register your CustomLoggingFeature object with the context

@Overridepublic boolean configure(FeatureContext context) {    context.register(this);    return true;}

ContainerRequestFilter and ContainerResponseFilter interface has methods used to log the Server request and response.

ClientRequestFilter and ContainerResponseFilter interface has methods used to log the Client request and response.

Also, you need to implement the WriterInterceptor to log the Client request and Server response body.

Finally registered your CustomLoggingFeature class with jersey.

environment.jersey().register(new CustomLoggingFeature(Logger.getLogger(getClass().getName()), Level.INFO, LoggingFeature.Verbosity.PAYLOAD_ANY, 8192));

I am using Dropwizard v1.3.5.

Here is my implementation.

public class CustomLoggingFeature extends LoggingFeature implements ContainerRequestFilter, ContainerResponseFilter,    ClientRequestFilter, ClientResponseFilter, WriterInterceptor {private static final boolean printEntity = true;private static final int maxEntitySize = 8 * 1024;private final Logger logger = Logger.getLogger("CustomLoggingFeature");private static final String ENTITY_LOGGER_PROPERTY = CustomLoggingFeature.class.getName();private static final String NOTIFICATION_PREFIX = "* ";private static final String REQUEST_PREFIX = "> ";private static final String RESPONSE_PREFIX = "< ";private static final String AUTHORIZATION = "Authorization";private static final String EQUAL = " = ";private static final String HEADERS_SEPARATOR = ", ";private static List<String> requestHeaders;static {    requestHeaders = new ArrayList<>();    requestHeaders.add(AUTHORIZATION);}public CustomLoggingFeature(Logger logger, Level level, Verbosity verbosity, Integer maxEntitySize) {    super(logger, level, verbosity, maxEntitySize);}@Overridepublic boolean configure(FeatureContext context) {    context.register(this);    return true;}@Overridepublic void filter(final ClientRequestContext context) {    final StringBuilder b = new StringBuilder();    printHeaders(b, context.getStringHeaders());    printRequestLine(b, "Sending client request", context.getMethod(), context.getUri());    if (printEntity && context.hasEntity()) {        final OutputStream stream = new LoggingStream(b, context.getEntityStream());        context.setEntityStream(stream);        context.setProperty(ENTITY_LOGGER_PROPERTY, stream);        // not calling log(b) here - it will be called by the interceptor    } else {        log(b);    }}@Overridepublic void filter(final ClientRequestContext requestContext, final ClientResponseContext responseContext) throws IOException {    final StringBuilder b = new StringBuilder();    printResponseLine(b, "Client response received", responseContext.getStatus());    if (printEntity && responseContext.hasEntity()) {        responseContext.setEntityStream(logInboundEntity(b, responseContext.getEntityStream(),                MessageUtils.getCharset(responseContext.getMediaType())));    }    log(b);}@Overridepublic void filter(final ContainerRequestContext context) throws IOException {    final StringBuilder b = new StringBuilder();    printHeaders(b, context.getHeaders());    printRequestLine(b, "Server has received a request", context.getMethod(), context.getUriInfo().getRequestUri());    if (printEntity && context.hasEntity()) {        context.setEntityStream(logInboundEntity(b, context.getEntityStream(), MessageUtils.getCharset(context.getMediaType())));    }    log(b);}@Overridepublic void filter(final ContainerRequestContext requestContext, final ContainerResponseContext responseContext) {    final StringBuilder b = new StringBuilder();    printResponseLine(b, "Server responded with a response", responseContext.getStatus());    if (printEntity && responseContext.hasEntity()) {        final OutputStream stream = new LoggingStream(b, responseContext.getEntityStream());        responseContext.setEntityStream(stream);        requestContext.setProperty(ENTITY_LOGGER_PROPERTY, stream);        // not calling log(b) here - it will be called by the interceptor    } else {        log(b);    }}@Overridepublic void aroundWriteTo(final WriterInterceptorContext writerInterceptorContext) throws IOException, WebApplicationException {    final LoggingStream stream = (LoggingStream) writerInterceptorContext.getProperty(ENTITY_LOGGER_PROPERTY);    writerInterceptorContext.proceed();    if (stream != null) {        log(stream.getStringBuilder(MessageUtils.getCharset(writerInterceptorContext.getMediaType())));    }}private static class LoggingStream extends FilterOutputStream {    private final StringBuilder b;    private final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();    LoggingStream(final StringBuilder b, final OutputStream inner) {        super(inner);        this.b = b;    }    StringBuilder getStringBuilder(Charset charset) {        // write entity to the builder        final byte[] entity = byteArrayOutputStream.toByteArray();        b.append(new String(entity, 0, Math.min(entity.length, maxEntitySize), charset));        if (entity.length > maxEntitySize) {            b.append("...more...");        }        b.append('\n');        return b;    }    public void write(final int i) throws IOException {        if (byteArrayOutputStream.size() <= maxEntitySize) {            byteArrayOutputStream.write(i);        }        out.write(i);    }}private void printHeaders(StringBuilder b, MultivaluedMap<String, String> headers) {    for (String header : requestHeaders) {        if (Objects.nonNull(headers.get(header))) {            b.append(header).append(EQUAL).append(headers.get(header)).append(HEADERS_SEPARATOR);        }    }    int lastIndex = b.lastIndexOf(HEADERS_SEPARATOR);    if (lastIndex != -1) {        b.delete(lastIndex, lastIndex + HEADERS_SEPARATOR.length());        b.append("\n");    }}private void log(final StringBuilder b) {    String message = Util.mask(b.toString());    if (logger != null) {        logger.info(message);    }}private void printRequestLine(final StringBuilder b, final String note, final String method, final URI uri) {    b.append(NOTIFICATION_PREFIX)            .append(note)            .append(" on thread ").append(Thread.currentThread().getId())            .append(REQUEST_PREFIX).append(method).append(" ")            .append(uri.toASCIIString()).append("\n");}private void printResponseLine(final StringBuilder b, final String note, final int status) {    b.append(NOTIFICATION_PREFIX)            .append(note)            .append(" on thread ").append(Thread.currentThread().getId())            .append(RESPONSE_PREFIX)            .append(Integer.toString(status))            .append("\n");}private InputStream logInboundEntity(final StringBuilder b, InputStream stream, final Charset charset) throws IOException {    if (!stream.markSupported()) {        stream = new BufferedInputStream(stream);    }    stream.mark(maxEntitySize + 1);    final byte[] entity = new byte[maxEntitySize + 1];    final int entitySize = stream.read(entity);    b.append(new String(entity, 0, Math.min(entitySize, maxEntitySize), charset));    if (entitySize > maxEntitySize) {        b.append("...more...");    }    b.append('\n');    stream.reset();    return stream;}