Skip to content

Commit d6bdfae

Browse files
Add Filter to Generate Request Logs
1 parent 90b4cf5 commit d6bdfae

File tree

5 files changed

+246
-5
lines changed

5 files changed

+246
-5
lines changed

cf-java-logging-support-servlet/src/main/java/com/sap/hcp/cf/logging/servlet/filter/AbstractLoggingFilter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ protected void doFilterRequest(HttpServletRequest request, HttpServletResponse r
5757
*
5858
* @param request
5959
* @param response
60+
* @throws IOException
6061
*/
6162
protected void preProcess(HttpServletRequest request, HttpServletResponse response) {
6263
}

cf-java-logging-support-servlet/src/main/java/com/sap/hcp/cf/logging/servlet/filter/ContentLengthTrackingResponseWrapper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class ContentLengthTrackingResponseWrapper extends HttpServletResponseWra
1919
private WrappedOutputStream wrappedOS = null;
2020
private WrappedPrintWriter wrappedWriter = null;
2121

22-
public ContentLengthTrackingResponseWrapper(HttpServletResponse response) throws IOException {
22+
public ContentLengthTrackingResponseWrapper(HttpServletResponse response) {
2323
super(response);
2424
this.response = response;
2525
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package com.sap.hcp.cf.logging.servlet.filter;
2+
3+
import java.io.IOException;
4+
5+
import javax.servlet.FilterChain;
6+
import javax.servlet.FilterConfig;
7+
import javax.servlet.ServletException;
8+
import javax.servlet.http.HttpServletRequest;
9+
import javax.servlet.http.HttpServletResponse;
10+
11+
import org.slf4j.MDC;
12+
13+
import com.sap.hcp.cf.logging.common.LogOptionalFieldsSettings;
14+
import com.sap.hcp.cf.logging.common.request.RequestRecord;
15+
16+
/**
17+
* The {@link GenerateRequestLogFilter} writes a log message for each incoming
18+
* request. The message contains metadata and metrics about request and
19+
* response. It adds the {@link MDC} as a request attribute and wraps the
20+
* request to support asynchronous request handling. Additionally request and
21+
* response are wrapped once more to determine request and response sizes. You
22+
* can disable this second wrapping by setting the init parameters <i>wrapRequest</i>
23+
* and <i>wrapResponse</i> to {@code false}.
24+
*
25+
*/
26+
27+
public class GenerateRequestLogFilter extends AbstractLoggingFilter {
28+
29+
private static final String WRAP_RESPONSE_INIT_PARAM = "wrapResponse";
30+
private static final String WRAP_REQUEST_INIT_PARAM = "wrapRequest";
31+
32+
private final RequestRecordFactory requestRecordFactory;
33+
34+
private boolean wrapResponse = true;
35+
private boolean wrapRequest = true;
36+
37+
public GenerateRequestLogFilter() {
38+
this(new RequestRecordFactory(new LogOptionalFieldsSettings(GenerateRequestLogFilter.class.getName())));
39+
}
40+
41+
public GenerateRequestLogFilter(RequestRecordFactory requestRecordFactory) {
42+
this.requestRecordFactory = requestRecordFactory;
43+
}
44+
45+
@Override
46+
public void init(FilterConfig filterConfig) throws ServletException {
47+
String value = filterConfig.getInitParameter(WRAP_RESPONSE_INIT_PARAM);
48+
if (value != null && "false".equalsIgnoreCase(value)) {
49+
wrapResponse = false;
50+
}
51+
value = filterConfig.getInitParameter(WRAP_REQUEST_INIT_PARAM);
52+
if (value != null && "false".equalsIgnoreCase(value)) {
53+
wrapRequest = false;
54+
}
55+
}
56+
57+
@Override
58+
protected void doFilterRequest(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
59+
throws IOException,
60+
ServletException {
61+
if (!RequestLogger.isRequestLoggingEnabled()) {
62+
doFilter(chain, request, response);
63+
return;
64+
}
65+
66+
RequestRecord record = requestRecordFactory.create(request);
67+
request.setAttribute(MDC.class.getName(), MDC.getCopyOfContextMap());
68+
69+
if (wrapRequest) {
70+
request = new ContentLengthTrackingRequestWrapper(request);
71+
}
72+
if (wrapResponse) {
73+
response = new ContentLengthTrackingResponseWrapper(response);
74+
}
75+
76+
RequestLogger logger = new RequestLogger(record, request, response);
77+
request = new LoggingContextRequestWrapper(request, logger);
78+
79+
record.start();
80+
81+
try {
82+
doFilter(chain, request, response);
83+
} finally {
84+
if (!request.isAsyncStarted()) {
85+
logger.logRequest();
86+
}
87+
88+
}
89+
}
90+
91+
private void doFilter(FilterChain chain, HttpServletRequest request, HttpServletResponse response)
92+
throws IOException,
93+
ServletException {
94+
if (chain != null) {
95+
chain.doFilter(request, response);
96+
}
97+
}
98+
99+
}

cf-java-logging-support-servlet/src/main/java/com/sap/hcp/cf/logging/servlet/filter/LogContextToRequestAttributeFilter.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,21 @@
66
import org.slf4j.MDC;
77

88
/**
9+
* <p>
910
* The {@link LogContextToRequestAttributeFilter} adds the current {@link MDC}
10-
* as a RequestAttribute to the current request. This helps with asynchronous
11-
* reqquest handling, where the MDC is not propagated correctly across thread
12-
* boundaries. The correct LogContext can be restored by
13-
* {@code MDC.setContextMap((Map<String, String>) httpRequest.getAttribute(MDC.class.getName()))}
11+
* as a request attribute to the current request. This helps with
12+
* asynchronous request handling, where the MDC is not propagated correctly
13+
* across thread boundaries. The correct LogContext can be restored by
14+
* {@code MDC.setContextMap((Map<String, String>) httpRequest.getAttribute(MDC.class.getName()))}.
15+
* </p>
16+
*
17+
* <p>
18+
* Adding the {@link MDC} as a request attribute is also done by
19+
* {@link GenerateRequestLogFilter}. You only need the
20+
* {@link LogContextToRequestAttributeFilter}, if you do not use
21+
* {@link GenerateRequestLogFilter}. The default configuration is to generate
22+
* the request logs. So this filter is not used there.
23+
* </p>
1424
*/
1525
public class LogContextToRequestAttributeFilter extends AbstractLoggingFilter {
1626

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package com.sap.hcp.cf.logging.servlet.filter;
2+
3+
import static org.hamcrest.MatcherAssert.assertThat;
4+
import static org.hamcrest.Matchers.instanceOf;
5+
import static org.hamcrest.Matchers.is;
6+
import static org.hamcrest.Matchers.isEmptyOrNullString;
7+
import static org.hamcrest.Matchers.not;
8+
import static org.mockito.Matchers.any;
9+
import static org.mockito.Matchers.anyMap;
10+
import static org.mockito.Matchers.eq;
11+
import static org.mockito.Mockito.doNothing;
12+
import static org.mockito.Mockito.mock;
13+
import static org.mockito.Mockito.verify;
14+
import static org.mockito.Mockito.when;
15+
16+
import javax.servlet.FilterChain;
17+
import javax.servlet.FilterConfig;
18+
import javax.servlet.http.HttpServletRequest;
19+
import javax.servlet.http.HttpServletResponse;
20+
21+
import org.junit.Before;
22+
import org.junit.Rule;
23+
import org.junit.Test;
24+
import org.junit.runner.RunWith;
25+
import org.mockito.ArgumentCaptor;
26+
import org.mockito.Captor;
27+
import org.mockito.Mock;
28+
import org.mockito.runners.MockitoJUnitRunner;
29+
import org.slf4j.LoggerFactory;
30+
import org.slf4j.MDC;
31+
32+
import com.sap.hcp.cf.logging.common.request.RequestRecord;
33+
34+
import ch.qos.logback.classic.Level;
35+
import ch.qos.logback.classic.LoggerContext;
36+
37+
@RunWith(MockitoJUnitRunner.class)
38+
public class GenerateRequestLogFilterTest {
39+
40+
@Rule
41+
public SystemOutRule systemOut = new SystemOutRule();
42+
43+
@Mock
44+
private RequestRecordFactory requestRecordFactory;
45+
46+
private RequestRecord requestRecord = new RequestRecord("TEST");;
47+
48+
@Mock
49+
private HttpServletRequest request;
50+
@Mock
51+
private HttpServletResponse response;
52+
@Mock
53+
private FilterChain chain;
54+
55+
@Captor
56+
private ArgumentCaptor<HttpServletRequest> forwardedRequest;
57+
@Captor
58+
private ArgumentCaptor<HttpServletResponse> forwardedResponse;
59+
60+
61+
@Before
62+
public void setUp() throws Exception {
63+
MDC.clear();
64+
when(requestRecordFactory.create(any())).thenReturn(requestRecord);
65+
doNothing().when(chain).doFilter(forwardedRequest.capture(), forwardedResponse.capture());
66+
}
67+
68+
@Test
69+
public void setsRequestAttribute() throws Exception {
70+
new GenerateRequestLogFilter(requestRecordFactory).doFilter(request, response, chain);
71+
verify(request).setAttribute(eq(MDC.class.getName()), anyMap());
72+
}
73+
74+
@Test
75+
public void wrapsRequest() throws Exception {
76+
new GenerateRequestLogFilter(requestRecordFactory).doFilter(request, response, chain);
77+
78+
assertThat(forwardedRequest.getValue(), is(instanceOf(LoggingContextRequestWrapper.class)));
79+
LoggingContextRequestWrapper wrappedRequest = (LoggingContextRequestWrapper) forwardedRequest.getValue();
80+
assertThat(wrappedRequest.getRequest(), is(instanceOf(ContentLengthTrackingRequestWrapper.class)));
81+
}
82+
83+
@Test
84+
public void doesNotCreateContentLengthTrackingRequestWrapperIfDisabled() throws Exception {
85+
GenerateRequestLogFilter filter = new GenerateRequestLogFilter(requestRecordFactory);
86+
filter.init(when(mock(FilterConfig.class).getInitParameter("wrapRequest")).thenReturn("false").getMock());
87+
88+
filter.doFilter(request, response, chain);
89+
90+
assertThat(forwardedRequest.getValue(), is(instanceOf(LoggingContextRequestWrapper.class)));
91+
LoggingContextRequestWrapper wrappedRequest = (LoggingContextRequestWrapper) forwardedRequest.getValue();
92+
assertThat(wrappedRequest.getRequest(), is(not(instanceOf(ContentLengthTrackingRequestWrapper.class))));
93+
}
94+
95+
@Test
96+
public void wrapsResponse() throws Exception {
97+
new GenerateRequestLogFilter(requestRecordFactory).doFilter(request, response, chain);
98+
99+
assertThat(forwardedResponse.getValue(), is(instanceOf(ContentLengthTrackingResponseWrapper.class)));
100+
}
101+
102+
@Test
103+
public void doesNotCreateContentLengthTrackingResponseWrapperIfDisabled() throws Exception {
104+
GenerateRequestLogFilter filter = new GenerateRequestLogFilter(requestRecordFactory);
105+
filter.init(when(mock(FilterConfig.class).getInitParameter("wrapResponse")).thenReturn("false").getMock());
106+
107+
filter.doFilter(request, response, chain);
108+
109+
assertThat(forwardedResponse.getValue(), is(not(instanceOf(ContentLengthTrackingResponseWrapper.class))));
110+
}
111+
112+
@Test
113+
public void doesNotWriteLogOnStartAsync() throws Exception {
114+
when(request.isAsyncStarted()).thenReturn(true);
115+
116+
new GenerateRequestLogFilter(requestRecordFactory).doFilter(request, response, chain);
117+
118+
assertThat(systemOut.toString(), isEmptyOrNullString());
119+
}
120+
121+
@Test
122+
public void directlyForwardsRequestResponseWhenLogIsDisabled() throws Exception {
123+
((LoggerContext) LoggerFactory.getILoggerFactory()).getLogger(RequestLogger.class).setLevel(Level.OFF);
124+
125+
new GenerateRequestLogFilter(requestRecordFactory).doFilter(request, response, chain);
126+
127+
verify(chain).doFilter(request, response);
128+
assertThat(systemOut.toString(), isEmptyOrNullString());
129+
}
130+
131+
}

0 commit comments

Comments
 (0)