title: Spring cloud gateway - Response body logging causing out of direct memory error
body: <p>Below is the stack I am using to build an API gateway using Spring Cloud Gateway -</p>
  <pre><code>SCG - Hoxton.RELEASE
  Java - 1.8
  Spring Boot - 2.2.1.RELEASE
  </code></pre>
  <p>I got the below &quot;GlobalFilter&quot; code from internet to log request/response body. This works fine but I run into errors like</p>
  <pre><code>&quot;failed to allocate 16777216 byte(s) of direct memory (used: 1023410183, max: 1038876672)&quot;
  </code></pre>
  <p>Below is the filter code -</p>
  <pre><code>import java.net.URI;
  import java.nio.charset.StandardCharsets;
  import java.util.List;
  
  import org.reactivestreams.Publisher;
  import org.springframework.cloud.gateway.filter.GatewayFilterChain;
  import org.springframework.cloud.gateway.filter.GlobalFilter;
  import org.springframework.core.Ordered;
  import org.springframework.core.io.buffer.DataBuffer;
  import org.springframework.core.io.buffer.DataBufferFactory;
  import org.springframework.http.HttpHeaders;
  import org.springframework.http.MediaType;
  import org.springframework.http.server.reactive.ServerHttpRequest;
  import org.springframework.http.server.reactive.ServerHttpResponse;
  import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
  import org.springframework.stereotype.Component;
  import org.springframework.util.MultiValueMap;
  import org.springframework.web.server.ServerWebExchange;
  
  import com.xx.GatewayJsonLogger;
  
  import lombok.extern.slf4j.Slf4j;
  import reactor.core.publisher.Flux;
  import reactor.core.publisher.Mono;
  
  @Component
  @Slf4j
  public class RequestResponseLogFilter implements GlobalFilter, Ordered {
  
      private static final String MAGIC_HEADER = &quot;x-debug&quot;;
      private static final String START_TIME = &quot;startTime&quot;;
  
      private static final String HTTP_SCHEME = &quot;http&quot;;
  
      private static final String HTTPS_SCHEME = &quot;https&quot;;
  
      @Override
      public Mono&lt;Void&gt; filter(ServerWebExchange exchange, GatewayFilterChain chain) {
          List&lt;String&gt; debugHeader = exchange.getRequest().getHeaders().get(MAGIC_HEADER);
          if (!log.isDebugEnabled() &amp;&amp; debugHeader == null) {
              // DO NOTHING
              return chain.filter(exchange);
          }
          ServerHttpRequest request = exchange.getRequest();
          URI requestURI = request.getURI();
          String scheme = requestURI.getScheme();
          if (debugHeader != null) {
              String debugHeaderContent = debugHeader.get(0);
              if (!debugHeaderContent.equalsIgnoreCase(&quot;true&quot;) &amp;&amp; !requestURI.getPath().toLowerCase().contains(debugHeaderContent.toLowerCase())) {
                  return chain.filter(exchange);
              }
          }
          if ((!HTTP_SCHEME.equalsIgnoreCase(scheme) &amp;&amp; !HTTPS_SCHEME.equals(scheme))) {
              return chain.filter(exchange);
          }
          long startTime = System.currentTimeMillis();
          exchange.getAttributes().put(START_TIME, startTime);
          logRequest(request, exchange.getAttribute(&quot;cachedRequestBodyObject&quot;));
          return chain.filter(exchange.mutate().response(logResponse(exchange)).build());
      }
  
      @Override
      public int getOrder() {
          return Integer.MIN_VALUE;
      }
  
      private void logRequest(ServerHttpRequest request, String body) {
          URI requestURI = request.getURI();
          String scheme = requestURI.getScheme();
          HttpHeaders headers = request.getHeaders();
          log.info(&quot;Request Scheme:{},Path:{}&quot;, scheme, requestURI.getPath());
          log.info(&quot;Request Method:{},IP:{},Host:{}&quot;, request.getMethod(), request.getRemoteAddress(), requestURI.getHost());
          headers.forEach((key, value) -&gt; log.debug(&quot;Request Headers:Key-&gt;{},Value-&gt;{}&quot;, key, value));
          MultiValueMap&lt;String, String&gt; queryParams = request.getQueryParams();
          if (!queryParams.isEmpty()) {
              queryParams.forEach((key, value) -&gt; log.info(&quot;Request Query Param :Key-&gt;({}),Value-&gt;({})&quot;, key, value));
          }
          MediaType contentType = headers.getContentType();
          long length = headers.getContentLength();
          log.info(&quot;Request ContentType:{},Content Length:{}&quot;, contentType, length);
          if (body != null) {
              GatewayJsonLogger.log(null, requestURI.getPath(), body.replaceAll(&quot;\&quot;password\&quot;\\s*:\\s*\&quot;.*\&quot;&quot;, &quot;\&quot;password\&quot;:\&quot;*******\&quot;&quot;), &quot;REQUEST&quot;);
          }
      }
  
      private ServerHttpResponseDecorator logResponse(ServerWebExchange exchange) {
          ServerHttpResponse origResponse = exchange.getResponse();
          Long startTime = exchange.getAttribute(START_TIME);
          log.info(&quot;Response HttpStatus:{}&quot;, origResponse.getStatusCode());
          HttpHeaders headers = origResponse.getHeaders();
          headers.forEach((key, value) -&gt; log.debug(&quot;[RequestLogFilter]Headers:Key-&gt;{},Value-&gt;{}&quot;, key, value));
          MediaType contentType = headers.getContentType();
          long length = headers.getContentLength();
          log.info(&quot;Response ContentType:{},Content Length:{}&quot;, contentType, length);
          Long executeTime = (System.currentTimeMillis() - startTime);
          log.info(&quot;Response Original Path:{},Cost:{} ms&quot;, exchange.getRequest().getURI().getPath(), executeTime);
          DataBufferFactory bufferFactory = origResponse.bufferFactory();
          return new ServerHttpResponseDecorator(origResponse) {
              @Override
              public Mono&lt;Void&gt; writeWith(Publisher&lt;? extends DataBuffer&gt; body) {
                  if (body instanceof Flux) {
                      Flux&lt;? extends DataBuffer&gt; fluxBody = (Flux&lt;? extends DataBuffer&gt;) body;
                      return super.writeWith(fluxBody.map(dataBuffer -&gt; {
                          byte[] content = new byte[dataBuffer.readableByteCount()];
                          dataBuffer.read(content);
                          String bodyContent = new String(content, StandardCharsets.UTF_8);
                          GatewayJsonLogger.log(null, exchange.getRequest().getURI().getPath(), bodyContent, &quot;RESPONSE&quot;);
                          return bufferFactory.wrap(content);
                      }));
                  }
                  return super.writeWith(body);
              }
          };
  
      }
  }
  </code></pre>
  <p>I think the &quot;DataBuffer&quot; being used here for logging response is causing memory issue; request part looks fine as it is coming from a cached object.
  Could you please help advise a more efficient way to log response? I have tried removing this filter and doing below steps -</p>
  <pre><code>1. Enable DEBUG logging for reactor.netty
  2. Add below property to application.yml
     spring.cloud.gateway.httpserver.wiretap = true
  </code></pre>
  <p>This works in the sense that I can see request and response headers+body in log file but it is not in a readable format (some hex format I think).</p>
  <p>Regards,
  Jacob</p>