com.effektif.adapter.helpers.RequestLogger.java Source code

Java tutorial

Introduction

Here is the source code for com.effektif.adapter.helpers.RequestLogger.java

Source

/*
 * Copyright 2014 Effektif GmbH.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.effektif.adapter.helpers;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicLong;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.WriterInterceptor;
import javax.ws.rs.ext.WriterInterceptorContext;

import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.internal.util.collection.StringIgnoreCaseKeyComparator;
import org.glassfish.jersey.message.MessageUtils;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.ObjectMapper;

public class RequestLogger implements ContainerRequestFilter, ContainerResponseFilter, WriterInterceptor {

    public static final org.slf4j.Logger log = LoggerFactory.getLogger("HTTP");

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private static final String REQUEST_PREFIX = ">>> ";
    private static final String RESPONSE_PREFIX = "<<< ";
    private static final String ENTITY_LOGGER_PROPERTY = LoggingFilter.class.getName() + ".entityLogger";
    private static final Comparator<Map.Entry<String, List<String>>> COMPARATOR = new Comparator<Map.Entry<String, List<String>>>() {
        @Override
        public int compare(final Map.Entry<String, List<String>> o1, final Map.Entry<String, List<String>> o2) {
            return StringIgnoreCaseKeyComparator.SINGLETON.compare(o1.getKey(), o2.getKey());
        }
    };
    private static final int DEFAULT_MAX_ENTITY_SIZE = 100 * 1024; // 100 KB

    protected boolean logEntity = true;
    protected boolean logEntityJsonPretty = true;
    protected boolean logHeaders = false;
    private final AtomicLong _id = new AtomicLong(0);
    private final int maxEntitySize = DEFAULT_MAX_ENTITY_SIZE;

    @Override
    public void filter(final ContainerRequestContext context) throws IOException {
        final long id = this._id.incrementAndGet();
        final StringBuilder logMsg = new StringBuilder();

        logMsg.append("\n\n").append(REQUEST_PREFIX).append(" ").append(context.getMethod()).append(" /")
                .append(context.getUriInfo().getPath()).append("\n");

        if (logHeaders) {
            logHeaders(logMsg, id, REQUEST_PREFIX, context.getHeaders());
        }

        if (logEntity && context.hasEntity()) {
            context.setEntityStream(logInboundEntity(logMsg, context.getEntityStream(),
                    MessageUtils.getCharset(context.getMediaType())));
        }

        if (log.isDebugEnabled())
            log.debug(logMsg.toString());
    }

    @Override
    public void filter(final ContainerRequestContext requestContext, final ContainerResponseContext responseContext)
            throws IOException {
        final long id = this._id.incrementAndGet();
        final StringBuilder logMsg = new StringBuilder();

        logMsg.append("\n<<< ").append(responseContext.getStatus()).append("\n");

        if (logHeaders) {
            logHeaders(logMsg, id, RESPONSE_PREFIX, responseContext.getStringHeaders());
        }

        if (logEntity && responseContext.hasEntity()) {
            final OutputStream stream = new LoggingStream(logMsg, responseContext.getEntityStream());
            responseContext.setEntityStream(stream);
            requestContext.setProperty(ENTITY_LOGGER_PROPERTY, stream);
            // not calling log(b) here - it will be called by the interceptor
        } else {
            if (log.isDebugEnabled())
                log.debug(logMsg.toString());
        }
    }

    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(REQUEST_PREFIX);
        String entityString = new String(entity, 0, Math.min(entitySize, maxEntitySize), charset);
        if (logEntityJsonPretty && (entitySize <= maxEntitySize)) {
            entityString = getJsonPrettyString(entityString);
        }
        b.append(entityString);
        if (entitySize > maxEntitySize) {
            b.append("...more...");
        }
        stream.reset();
        return stream;
    }

    protected String getJsonPrettyString(String entityString) {
        try {
            @SuppressWarnings("unchecked")
            Object json = OBJECT_MAPPER.readValue(entityString, Object.class);
            return OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(json);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void aroundWriteTo(final WriterInterceptorContext writerInterceptorContext)
            throws IOException, WebApplicationException {
        final LoggingStream stream = (LoggingStream) writerInterceptorContext.getProperty(ENTITY_LOGGER_PROPERTY);
        writerInterceptorContext.proceed();
        if (stream != null) {
            if (log.isDebugEnabled())
                log.debug(stream.getStringBuilder(MessageUtils.getCharset(writerInterceptorContext.getMediaType()))
                        .toString());
        }
    }

    private void logHeaders(final StringBuilder logMsg, final long id, final String prefix,
            final MultivaluedMap<String, String> headers) {
        for (final Map.Entry<String, List<String>> headerEntry : getSortedHeaders(headers.entrySet())) {
            final List<?> val = headerEntry.getValue();
            final String header = headerEntry.getKey();
            if (val.size() == 1) {
                logMsg.append(prefix).append(header).append(": ").append(val.get(0)).append("\n");
            } else {
                final StringBuilder sb = new StringBuilder();
                boolean add = false;
                for (final Object s : val) {
                    if (add) {
                        sb.append(',');
                    }
                    add = true;
                    sb.append(s);
                }
                logMsg.append(prefix).append(header).append(": ").append(sb.toString()).append("\n");
            }
        }
    }

    private Set<Map.Entry<String, List<String>>> getSortedHeaders(
            final Set<Map.Entry<String, List<String>>> headers) {
        final TreeSet<Map.Entry<String, List<String>>> sortedHeaders = new TreeSet<Map.Entry<String, List<String>>>(
                COMPARATOR);
        sortedHeaders.addAll(headers);
        return sortedHeaders;
    }

    private class LoggingStream extends OutputStream {
        private final StringBuilder logMsg;
        private final OutputStream inner;
        private final ByteArrayOutputStream baos = new ByteArrayOutputStream();

        LoggingStream(final StringBuilder logMsg, final OutputStream inner) {
            this.logMsg = logMsg;
            this.inner = inner;
        }

        StringBuilder getStringBuilder(Charset charset) {
            // write entity to the builder
            final byte[] entity = baos.toByteArray();

            String entityString = new String(entity, 0, Math.min(entity.length, maxEntitySize), charset);
            if (logEntityJsonPretty && (entity.length <= maxEntitySize)) {
                entityString = getJsonPrettyString(entityString);
            }
            logMsg.append(entityString);
            if (entity.length > maxEntitySize) {
                logMsg.append("...more...");
            }

            return logMsg;
        }

        @Override
        public void write(final int i) throws IOException {
            if (baos.size() <= maxEntitySize) {
                baos.write(i);
            }
            inner.write(i);
        }
    }
}