Skip to content

Commit 7143a78

Browse files
authored
Split RequestLoggingFilter into an additional RequestLoggingFilterBase (#84)
* Split RequestLoggingFilter into an additional RequestLoggingFilterBase * The RequestLoggingFilterBase has no dependencies to com.sap.hcp.cf.logging.servlet.dynlog * For scenarios without the requirement to dynamically set log levels, the following dependencies can then be omitted: - jaxb-api - jaxb-runtime - java-jwt * Java doc needs review * Rename RequestLoggingFilterBase to RequestLoggingBaseFilter * Fix compile and test error
1 parent 8758ff8 commit 7143a78

File tree

2 files changed

+182
-121
lines changed

2 files changed

+182
-121
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package com.sap.hcp.cf.logging.servlet.filter;
2+
3+
import java.io.IOException;
4+
5+
import javax.servlet.Filter;
6+
import javax.servlet.FilterChain;
7+
import javax.servlet.FilterConfig;
8+
import javax.servlet.ServletException;
9+
import javax.servlet.ServletRequest;
10+
import javax.servlet.ServletResponse;
11+
import javax.servlet.http.HttpServletRequest;
12+
import javax.servlet.http.HttpServletResponse;
13+
14+
import org.slf4j.MDC;
15+
16+
import com.sap.hcp.cf.logging.common.LogContext;
17+
import com.sap.hcp.cf.logging.common.LogOptionalFieldsSettings;
18+
import com.sap.hcp.cf.logging.common.request.HttpHeader;
19+
import com.sap.hcp.cf.logging.common.request.HttpHeaders;
20+
import com.sap.hcp.cf.logging.common.request.RequestRecord;
21+
22+
/**
23+
* <p>
24+
* A simple servlet filter that logs HTTP request processing info. It will read
25+
* several HTTP Headers and store them in the SLF4J MDC, so that all log
26+
* messages created during request handling will have those additional fields.
27+
* It will also instrument the request to generate a request log containing
28+
* metrics such as request and response sizes and response time. This
29+
* instrumentation can be disabled by denying logs from {@link RequestLogger}
30+
* with marker "request".
31+
* </p>
32+
* <p>
33+
* This filter will generate a correlation id, from the HTTP header
34+
* "X-CorrelationID" falling back to "x-vcap-request-id" if not found or using a
35+
* random UUID. The correlation id will be added as an HTTP header
36+
* "X-CorrelationID" to the response if possible.
37+
* </p>
38+
* <p>
39+
* To use the filter, it needs to be added to the servlet configuration. It has
40+
* a default constructor to support web.xml configuration. There are several
41+
* other constructors that support some customization, in case dynamic
42+
* configuration (e.g. Spring Boot) is used. You can add further customization
43+
* by subclassing the filter and overwrite its methods.
44+
* </p>
45+
*/
46+
public class RequestLoggingBaseFilter implements Filter {
47+
48+
public static final String LOG_PROVIDER = "[SERVLET]";
49+
public static final String WRAP_RESPONSE_INIT_PARAM = "wrapResponse";
50+
public static final String WRAP_REQUEST_INIT_PARAM = "wrapRequest";
51+
52+
private boolean wrapResponse = true;
53+
private boolean wrapRequest = true;
54+
private RequestRecordFactory requestRecordFactory;
55+
56+
public RequestLoggingBaseFilter() {
57+
this(createDefaultRequestRecordFactory());
58+
}
59+
60+
protected static RequestRecordFactory createDefaultRequestRecordFactory() {
61+
String invokingClass = RequestLoggingBaseFilter.class.getName();
62+
LogOptionalFieldsSettings logOptionalFieldsSettings = new LogOptionalFieldsSettings(invokingClass);
63+
return new RequestRecordFactory(logOptionalFieldsSettings);
64+
}
65+
66+
public RequestLoggingBaseFilter(RequestRecordFactory requestRecordFactory) {
67+
this.requestRecordFactory = requestRecordFactory;
68+
}
69+
70+
@Override
71+
public void init(FilterConfig filterConfig) throws ServletException {
72+
String value = filterConfig.getInitParameter(WRAP_RESPONSE_INIT_PARAM);
73+
if (value != null && "false".equalsIgnoreCase(value)) {
74+
wrapResponse = false;
75+
}
76+
value = filterConfig.getInitParameter(WRAP_REQUEST_INIT_PARAM);
77+
if (value != null && "false".equalsIgnoreCase(value)) {
78+
wrapRequest = false;
79+
}
80+
}
81+
82+
@Override
83+
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
84+
ServletException {
85+
if (HttpServletRequest.class.isAssignableFrom(request.getClass()) && HttpServletResponse.class.isAssignableFrom(
86+
response.getClass())) {
87+
doFilterRequest((HttpServletRequest) request, (HttpServletResponse) response, chain);
88+
} else {
89+
chain.doFilter(request, response);
90+
}
91+
}
92+
93+
@Override
94+
public void destroy() {
95+
MDC.clear();
96+
}
97+
98+
protected void doFilterRequest(HttpServletRequest httpRequest, HttpServletResponse httpResponse, FilterChain chain)
99+
throws IOException,
100+
ServletException {
101+
/*
102+
* -- make sure correlation id is read from headers
103+
*/
104+
LogContext.initializeContext(HttpHeaderUtilities.getHeaderValue(httpRequest, HttpHeaders.CORRELATION_ID));
105+
106+
try {
107+
108+
RequestRecord rr = requestRecordFactory.create(httpRequest);
109+
httpRequest.setAttribute(MDC.class.getName(), MDC.getCopyOfContextMap());
110+
111+
if (!httpResponse.isCommitted() && httpResponse.getHeader(HttpHeaders.CORRELATION_ID.getName()) == null) {
112+
httpResponse.setHeader(HttpHeaders.CORRELATION_ID.getName(), LogContext.getCorrelationId());
113+
}
114+
115+
/*
116+
* If request logging is disabled skip request instrumentation and
117+
* continue the filter chain immediately.
118+
*/
119+
if (!RequestLogger.isRequestLoggingEnabled()) {
120+
doFilter(chain, httpRequest, httpResponse);
121+
return;
122+
}
123+
124+
/*
125+
* -- we essentially do three things here: -- a) we create a log
126+
* record using our library and log it via STDOUT -- b) keep track
127+
* of certain header fields so that they are available in later
128+
* processing steps -- b) inject a response wrapper to keep track of
129+
* content length (hopefully)
130+
*/
131+
if (wrapResponse) {
132+
httpResponse = new ContentLengthTrackingResponseWrapper(httpResponse);
133+
}
134+
135+
if (wrapRequest) {
136+
httpRequest = new ContentLengthTrackingRequestWrapper(httpRequest);
137+
}
138+
139+
RequestLogger loggingVisitor = new RequestLogger(rr, httpRequest, httpResponse);
140+
httpRequest = new LoggingContextRequestWrapper(httpRequest, loggingVisitor);
141+
142+
/* -- start measuring right before calling up the filter chain -- */
143+
rr.start();
144+
doFilter(chain, httpRequest, httpResponse);
145+
146+
if (!httpRequest.isAsyncStarted()) {
147+
loggingVisitor.logRequest();
148+
}
149+
/*
150+
* -- close this
151+
*/
152+
} finally {
153+
resetLogContext();
154+
}
155+
}
156+
157+
private void resetLogContext() {
158+
for (HttpHeader header: HttpHeaders.propagated()) {
159+
LogContext.remove(header.getField());
160+
}
161+
LogContext.resetContextFields();
162+
}
163+
164+
private void doFilter(FilterChain chain, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
165+
throws IOException,
166+
ServletException {
167+
if (chain != null) {
168+
chain.doFilter(httpRequest, httpResponse);
169+
}
170+
}
171+
172+
}

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

Lines changed: 10 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,8 @@
33
import java.io.IOException;
44
import java.util.Optional;
55

6-
import javax.servlet.Filter;
76
import javax.servlet.FilterChain;
8-
import javax.servlet.FilterConfig;
97
import javax.servlet.ServletException;
10-
import javax.servlet.ServletRequest;
11-
import javax.servlet.ServletResponse;
128
import javax.servlet.http.HttpServletRequest;
139
import javax.servlet.http.HttpServletResponse;
1410

@@ -17,13 +13,7 @@
1713
import org.apache.commons.lang3.concurrent.LazyInitializer;
1814
import org.slf4j.Logger;
1915
import org.slf4j.LoggerFactory;
20-
import org.slf4j.MDC;
2116

22-
import com.sap.hcp.cf.logging.common.LogContext;
23-
import com.sap.hcp.cf.logging.common.LogOptionalFieldsSettings;
24-
import com.sap.hcp.cf.logging.common.request.HttpHeader;
25-
import com.sap.hcp.cf.logging.common.request.HttpHeaders;
26-
import com.sap.hcp.cf.logging.common.request.RequestRecord;
2717
import com.sap.hcp.cf.logging.servlet.dynlog.DynLogConfiguration;
2818
import com.sap.hcp.cf.logging.servlet.dynlog.DynLogEnvironment;
2919
import com.sap.hcp.cf.logging.servlet.dynlog.DynamicLogLevelProcessor;
@@ -61,30 +51,21 @@
6151
* by subclassing the filter and overwrite its methods.
6252
* </p>
6353
*/
64-
public class RequestLoggingFilter implements Filter {
54+
public class RequestLoggingFilter extends RequestLoggingBaseFilter {
6555

6656
private static final Logger LOG = LoggerFactory.getLogger(RequestLoggingFilter.class);
6757

68-
public static final String LOG_PROVIDER = "[SERVLET]";
69-
public static final String WRAP_RESPONSE_INIT_PARAM = "wrapResponse";
70-
public static final String WRAP_REQUEST_INIT_PARAM = "wrapRequest";
58+
public static final String LOG_PROVIDER = RequestLoggingBaseFilter.LOG_PROVIDER;
59+
public static final String WRAP_RESPONSE_INIT_PARAM = RequestLoggingBaseFilter.WRAP_REQUEST_INIT_PARAM;
60+
public static final String WRAP_REQUEST_INIT_PARAM = RequestLoggingBaseFilter.WRAP_REQUEST_INIT_PARAM;
7161

72-
private boolean wrapResponse = true;
73-
private boolean wrapRequest = true;
7462
private ConcurrentInitializer<DynLogConfiguration> dynLogEnvironment;
7563
private ConcurrentInitializer<DynamicLogLevelProcessor> dynamicLogLevelProcessor;
76-
private RequestRecordFactory requestRecordFactory;
7764

7865
public RequestLoggingFilter() {
7966
this(createDefaultRequestRecordFactory());
8067
}
8168

82-
private static RequestRecordFactory createDefaultRequestRecordFactory() {
83-
String invokingClass = RequestLoggingFilter.class.getName();
84-
LogOptionalFieldsSettings logOptionalFieldsSettings = new LogOptionalFieldsSettings(invokingClass);
85-
return new RequestRecordFactory(logOptionalFieldsSettings);
86-
}
87-
8869
public RequestLoggingFilter(RequestRecordFactory requestRecordFactory) {
8970
this(requestRecordFactory, createDefaultDynLogEnvironment());
9071
}
@@ -100,7 +81,7 @@ public RequestLoggingFilter(ConcurrentInitializer<DynLogConfiguration> dynLogEnv
10081

10182
public RequestLoggingFilter(RequestRecordFactory requestRecordFactory,
10283
ConcurrentInitializer<DynLogConfiguration> dynLogEnvironment) {
103-
this.requestRecordFactory = requestRecordFactory;
84+
super(requestRecordFactory);
10485
this.dynLogEnvironment = dynLogEnvironment;
10586
this.dynamicLogLevelProcessor = new LazyInitializer<DynamicLogLevelProcessor>() {
10687

@@ -120,7 +101,7 @@ public RequestLoggingFilter(ConcurrentInitializer<DynLogConfiguration> dynLogEnv
120101
public RequestLoggingFilter(RequestRecordFactory requestRecordFactory,
121102
ConcurrentInitializer<DynLogConfiguration> dynLogEnvironment,
122103
ConcurrentInitializer<DynamicLogLevelProcessor> dynamicLogLevelProcessor) {
123-
this.requestRecordFactory = requestRecordFactory;
104+
super(requestRecordFactory);
124105
this.dynLogEnvironment = dynLogEnvironment;
125106
this.dynamicLogLevelProcessor = dynamicLogLevelProcessor;
126107
}
@@ -146,91 +127,14 @@ protected Optional<DynamicLogLevelProcessor> getDynLogLevelProcessor() {
146127
}
147128

148129
@Override
149-
public void init(FilterConfig filterConfig) throws ServletException {
150-
String value = filterConfig.getInitParameter(WRAP_RESPONSE_INIT_PARAM);
151-
if (value != null && "false".equalsIgnoreCase(value)) {
152-
wrapResponse = false;
153-
}
154-
value = filterConfig.getInitParameter(WRAP_REQUEST_INIT_PARAM);
155-
if (value != null && "false".equalsIgnoreCase(value)) {
156-
wrapRequest = false;
157-
}
158-
}
159-
160-
@Override
161-
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
162-
ServletException {
163-
if (HttpServletRequest.class.isAssignableFrom(request.getClass()) && HttpServletResponse.class.isAssignableFrom(
164-
response.getClass())) {
165-
doFilterRequest((HttpServletRequest) request, (HttpServletResponse) response, chain);
166-
} else {
167-
chain.doFilter(request, response);
168-
}
169-
}
170-
171-
@Override
172-
public void destroy() {
173-
MDC.clear();
174-
}
175-
176-
private void doFilterRequest(HttpServletRequest httpRequest, HttpServletResponse httpResponse, FilterChain chain)
177-
throws IOException,
178-
ServletException {
130+
protected void doFilterRequest(HttpServletRequest httpRequest, HttpServletResponse httpResponse, FilterChain chain)
131+
throws IOException,
132+
ServletException {
179133
activateDynamicLogLevels(httpRequest);
180-
/*
181-
* -- make sure correlation id is read from headers
182-
*/
183-
LogContext.initializeContext(HttpHeaderUtilities.getHeaderValue(httpRequest, HttpHeaders.CORRELATION_ID));
184-
185134
try {
186-
187-
RequestRecord rr = requestRecordFactory.create(httpRequest);
188-
httpRequest.setAttribute(MDC.class.getName(), MDC.getCopyOfContextMap());
189-
190-
if (!httpResponse.isCommitted() && httpResponse.getHeader(HttpHeaders.CORRELATION_ID.getName()) == null) {
191-
httpResponse.setHeader(HttpHeaders.CORRELATION_ID.getName(), LogContext.getCorrelationId());
192-
}
193-
194-
/*
195-
* If request logging is disabled skip request instrumentation and
196-
* continue the filter chain immediately.
197-
*/
198-
if (!RequestLogger.isRequestLoggingEnabled()) {
199-
doFilter(chain, httpRequest, httpResponse);
200-
return;
201-
}
202-
203-
/*
204-
* -- we essentially do three things here: -- a) we create a log
205-
* record using our library and log it via STDOUT -- b) keep track
206-
* of certain header fields so that they are available in later
207-
* processing steps -- b) inject a response wrapper to keep track of
208-
* content length (hopefully)
209-
*/
210-
if (wrapResponse) {
211-
httpResponse = new ContentLengthTrackingResponseWrapper(httpResponse);
212-
}
213-
214-
if (wrapRequest) {
215-
httpRequest = new ContentLengthTrackingRequestWrapper(httpRequest);
216-
}
217-
218-
RequestLogger loggingVisitor = new RequestLogger(rr, httpRequest, httpResponse);
219-
httpRequest = new LoggingContextRequestWrapper(httpRequest, loggingVisitor);
220-
221-
/* -- start measuring right before calling up the filter chain -- */
222-
rr.start();
223-
doFilter(chain, httpRequest, httpResponse);
224-
225-
if (!httpRequest.isAsyncStarted()) {
226-
loggingVisitor.logRequest();
227-
}
228-
/*
229-
* -- close this
230-
*/
135+
super.doFilterRequest(httpRequest, httpResponse, chain);
231136
} finally {
232137
deactivateDynamicLogLevels();
233-
resetLogContext();
234138
}
235139
}
236140

@@ -245,19 +149,4 @@ private void deactivateDynamicLogLevels() {
245149
getDynLogLevelProcessor().ifPresent(DynamicLogLevelProcessor::removeDynamicLogLevelFromMDC);
246150
}
247151

248-
private void resetLogContext() {
249-
for (HttpHeader header: HttpHeaders.propagated()) {
250-
LogContext.remove(header.getField());
251-
}
252-
LogContext.resetContextFields();
253-
}
254-
255-
private void doFilter(FilterChain chain, HttpServletRequest httpRequest, HttpServletResponse httpResponse)
256-
throws IOException,
257-
ServletException {
258-
if (chain != null) {
259-
chain.doFilter(httpRequest, httpResponse);
260-
}
261-
}
262-
263152
}

0 commit comments

Comments
 (0)