2  * ============LICENSE_START=======================================================
 
   4  * ================================================================================
 
   5  * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
 
   6  * ================================================================================
 
   7  * Licensed under the Apache License, Version 2.0 (the "License");
 
   8  * you may not use this file except in compliance with the License.
 
   9  * You may obtain a copy of the License at
 
  11  *      http://www.apache.org/licenses/LICENSE-2.0
 
  13  * Unless required by applicable law or agreed to in writing, software
 
  14  * distributed under the License is distributed on an "AS IS" BASIS,
 
  15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
  16  * See the License for the specific language governing permissions and
 
  17  * limitations under the License.
 
  18  * ============LICENSE_END=========================================================
 
  21 package org.onap.logging.filter.base;
 
  23 import java.io.BufferedInputStream;
 
  24 import java.io.ByteArrayOutputStream;
 
  25 import java.io.FilterOutputStream;
 
  26 import java.io.IOException;
 
  27 import java.io.InputStream;
 
  28 import java.io.OutputStream;
 
  29 import java.nio.charset.Charset;
 
  30 import java.nio.charset.StandardCharsets;
 
  31 import javax.ws.rs.WebApplicationException;
 
  32 import javax.ws.rs.client.ClientRequestContext;
 
  33 import javax.ws.rs.client.ClientRequestFilter;
 
  34 import javax.ws.rs.client.ClientResponseContext;
 
  35 import javax.ws.rs.client.ClientResponseFilter;
 
  36 import javax.ws.rs.core.HttpHeaders;
 
  37 import javax.ws.rs.core.MultivaluedHashMap;
 
  38 import javax.ws.rs.core.MultivaluedMap;
 
  39 import javax.ws.rs.ext.WriterInterceptor;
 
  40 import javax.ws.rs.ext.WriterInterceptorContext;
 
  41 import org.slf4j.Logger;
 
  42 import org.slf4j.LoggerFactory;
 
  44 public class PayloadLoggingClientFilter implements ClientRequestFilter, ClientResponseFilter, WriterInterceptor {
 
  46     private static final Logger logger = LoggerFactory.getLogger(PayloadLoggingClientFilter.class);
 
  47     private static final String ENTITY_STREAM_PROPERTY = "LoggingFilter.entityStream";
 
  48     private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
 
  49     private final int maxEntitySize;
 
  51     public PayloadLoggingClientFilter() {
 
  52         maxEntitySize = 1024 * 1024;
 
  55     public PayloadLoggingClientFilter(int maxPayloadSize) {
 
  56         this.maxEntitySize = Integer.min(maxPayloadSize, 1024 * 1024);
 
  59     private void log(StringBuilder sb) {
 
  60         logger.debug(sb.toString());
 
  63     protected InputStream logInboundEntity(final StringBuilder b, InputStream stream, final Charset charset)
 
  65         if (!stream.markSupported()) {
 
  66             stream = new BufferedInputStream(stream);
 
  68         stream.mark(maxEntitySize + 1);
 
  69         final byte[] entity = new byte[maxEntitySize + 1];
 
  70         final int entitySize = stream.read(entity);
 
  71         if (entitySize != -1) {
 
  72             b.append(new String(entity, 0, Math.min(entitySize, maxEntitySize), charset));
 
  74         if (entitySize > maxEntitySize) {
 
  75             b.append("...more...");
 
  83     public void filter(ClientRequestContext requestContext) throws IOException {
 
  84         if (requestContext.hasEntity()) {
 
  85             final OutputStream stream = new LoggingStream(requestContext.getEntityStream());
 
  86             requestContext.setEntityStream(stream);
 
  87             requestContext.setProperty(ENTITY_STREAM_PROPERTY, stream);
 
  89         String method = formatMethod(requestContext);
 
  90         log(new StringBuilder("Making " + method + " request to: " + requestContext.getUri() + "\nRequest Headers: "
 
  91                 + getHeaders(requestContext.getHeaders())));
 
  95     protected String getHeaders(MultivaluedMap<String, Object> headers) {
 
  96         MultivaluedMap<String, Object> printHeaders = new MultivaluedHashMap<>();
 
  97         for (String header : headers.keySet()) {
 
  98             if (!header.equals(HttpHeaders.AUTHORIZATION)) {
 
  99                 printHeaders.add(header, headers.getFirst(header));
 
 102         return printHeaders.toString();
 
 106     public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
 
 107         final StringBuilder sb = new StringBuilder();
 
 108         if (responseContext.hasEntity()) {
 
 109             responseContext.setEntityStream(logInboundEntity(sb, responseContext.getEntityStream(), DEFAULT_CHARSET));
 
 110             String method = formatMethod(requestContext);
 
 111             log(sb.insert(0, "Response from " + method + ": " + requestContext.getUri() + "\nResponse Headers: "
 
 112                     + responseContext.getHeaders().toString()));
 
 117     public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
 
 118         final LoggingStream stream = (LoggingStream) context.getProperty(ENTITY_STREAM_PROPERTY);
 
 120         if (stream != null) {
 
 121             log(stream.getStringBuilder(DEFAULT_CHARSET));
 
 125     private class LoggingStream extends FilterOutputStream {
 
 127         private final StringBuilder sb = new StringBuilder();
 
 128         private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
 
 130         LoggingStream(OutputStream out) {
 
 134         StringBuilder getStringBuilder(Charset charset) {
 
 135             // write entity to the builder
 
 136             final byte[] entity = baos.toByteArray();
 
 138             sb.append(new String(entity, 0, entity.length, charset));
 
 139             if (entity.length > maxEntitySize) {
 
 140                 sb.append("...more...");
 
 148         public void write(final int i) throws IOException {
 
 149             if (baos.size() <= maxEntitySize) {
 
 156     protected String formatMethod(ClientRequestContext requestContext) {
 
 157         String httpMethodOverride = requestContext.getHeaderString("X-HTTP-Method-Override");
 
 158         if (httpMethodOverride == null) {
 
 159             return requestContext.getMethod();
 
 161             return requestContext.getMethod() + " (overridden to " + httpMethodOverride + ")";