All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.effektif.adapter.helpers.RequestLogger Maven / Gradle / Ivy

/*
 * 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>> COMPARATOR = new Comparator>>() {
    @Override
    public int compare(final Map.Entry> o1, final Map.Entry> 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 headers) {
    for (final Map.Entry> 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>> getSortedHeaders(final Set>> headers) {
    final TreeSet>> sortedHeaders = new TreeSet>>(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);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy