Repository: zalando/logbook
Issue: Logbook removes body from response

I encountered an issue in combination with CXF and Spring Security where the body of an response vanishes (=is null) when using Logbook.

## Description
In Logbook version 1.13.0 the body of the response vanishes when the Writer Level is set to something that would lead to requests not getting logged (=INFO).

Using Logbook version 2.0.0-RC.33 the behaviour is inverted, e. g. the body disappears when the level would actually log it (=TRACE).

The Logbook Writer Level setting influences this behaviour. 

The setup is very simple:

The Spring Boot App exposes a CXF service list under /services. This resource is accessible for everyone (permitAll).

GET requests to /services do not yield any body when logging is turned _off_ by changing the log level of Logbook. When I turn the logging _on_, the body is contained in the response as it would be without Logbook.

## Expected Behavior
The response should contain the original body.

## Actual Behavior
The body of the response is null.

## Possible Fix
I have no idea, sadly.

## Steps to Reproduce
Reproduce erroneus behaviour :
1. Check out https://github.com/Breeki32/logbooktest113
2. Start app and send GET request to /services
or simply run the tests

Reproduce correct behaviour :
1. Check out https://github.com/Breeki32/logbooktest113
2. set logging.level.org.zalando property to TRACE
2. Start app and send GET request to /services
or simply run the tests

Reproduce erroneus behaviour with **2.0.0-RC.3**:
1. Check out https://github.com/Breeki32/logbooktest113
2. set Logbook version to 2.0.0-RC.3 in pom.xml
2. set logging.level.org.zalando property to TRACE
or simply run the tests with given version

## Context
I´ve noticed that certain things did not work in an web application when the log level was set to INFO. I tracked it down to the combination of CXF, Spring Security and Logbook (the minimal setup in the example project). The use case was logging the traffic to and from SOAP services for development purposes - but I noticed missing data in the service list servlet. 

## Your Environment

* Version used: Logbook 1.13.0 - 2.0.0-RC3
* Spring Boot 2.1.6 - 2.1.8
* CXF 3.3.2 - 3.3.3
* Link to your project: https://github.com/Breeki32/logbooktest113

Thank you for looking into this!

Changed files:
logbook-servlet/src/main/java/org/zalando/logbook/servlet/LocalResponse.java, logbook-servlet/src/main/java/org/zalando/logbook/servlet/LogbookFilter.java, logbook-servlet/src/test/java/org/zalando/logbook/servlet/ExampleController.java, logbook-servlet/src/test/java/org/zalando/logbook/servlet/LocalResponseTest.java

diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LocalResponse.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LocalResponse.java
index 6e02255e..fe6ce083 100644
--- a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LocalResponse.java
+++ b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LocalResponse.java
@@ -106,6 +106,14 @@ final class LocalResponse extends HttpServletResponseWrapper implements HttpResp
         }
     }
 
+    @Override
+    public void flushBuffer() throws IOException {
+        if (buffer != null) {
+            buffer.flush();
+        }
+        super.flushBuffer();
+    }
+
     @Override
     public byte[] getBody() {
         return body == null ? new byte[0] : body.getBytes();
@@ -135,6 +143,14 @@ final class LocalResponse extends HttpServletResponseWrapper implements HttpResp
             return writer;
         }
 
+        void flush() throws IOException {
+            if (writer == null) {
+                output.flush();
+            } else {
+                writer.flush();
+            }
+        }
+
         byte[] getBytes() {
             if (bytes == null) {
                 bytes = branch.toByteArray();
diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LogbookFilter.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LogbookFilter.java
index 1e8471cd..916f3120 100644
--- a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LogbookFilter.java
+++ b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LogbookFilter.java
@@ -46,7 +46,7 @@ public final class LogbookFilter implements HttpFilter {
         final RemoteRequest request = new RemoteRequest(httpRequest);
         final LocalResponse response = new LocalResponse(httpResponse, request.getProtocolVersion());
 
-        final ResponseWritingStage stage = logRequest(request, request).process(response);
+        final ResponseWritingStage stage = logRequest(request).process(response);
 
         chain.doFilter(request, response);
 
@@ -58,14 +58,12 @@ public final class LogbookFilter implements HttpFilter {
         stage.write();
     }
 
-    private ResponseProcessingStage logRequest(final HttpServletRequest httpRequest,
-            final HttpRequest request) throws IOException {
-
-        if (httpRequest.getDispatcherType() == DispatcherType.ASYNC) {
-            return (ResponseProcessingStage) httpRequest.getAttribute(STAGE);
+    private ResponseProcessingStage logRequest(final RemoteRequest request) throws IOException {
+        if (request.getDispatcherType() == DispatcherType.ASYNC) {
+            return (ResponseProcessingStage) request.getAttribute(STAGE);
         } else {
             final ResponseProcessingStage stage = process(request).write();
-            httpRequest.setAttribute(STAGE, stage);
+            request.setAttribute(STAGE, stage);
             return stage;
         }
     }
diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ExampleController.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ExampleController.java
index 032b526f..6a3dbf8f 100644
--- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ExampleController.java
+++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ExampleController.java
@@ -13,7 +13,6 @@ import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
-import java.io.PrintWriter;
 import java.nio.CharBuffer;
 import java.util.Objects;
 import java.util.concurrent.Callable;
@@ -95,9 +94,7 @@ public class ExampleController {
 
     @RequestMapping(path = "/reader", produces = TEXT_PLAIN_VALUE)
     public void reader(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
-        try (final PrintWriter writer = response.getWriter()) {
-            copy(request.getReader(), writer);
-        }
+        copy(request.getReader(), response.getWriter());
     }
 
     @RequestMapping(path = "/binary", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/LocalResponseTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/LocalResponseTest.java
index b94e6c62..366089d2 100644
--- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/LocalResponseTest.java
+++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/LocalResponseTest.java
@@ -94,6 +94,12 @@ class LocalResponseTest {
         verifyNoMoreInteractions(mock);
     }
 
+    @Test
+    void shouldDelegateClose() throws IOException {
+        unit.withBody();
+        unit.getOutputStream().close();
+    }
+
     @Test
     void shouldTeeGetWriter() throws IOException {
         unit.withBody();