|
1 | 1 | package com.sap.hcp.cf.logging.servlet.filter;
|
2 | 2 |
|
3 |
| -import java.io.IOException; |
4 |
| -import java.util.Optional; |
5 |
| - |
6 |
| -import javax.servlet.FilterChain; |
7 |
| -import javax.servlet.ServletException; |
8 |
| -import javax.servlet.http.HttpServletRequest; |
9 |
| -import javax.servlet.http.HttpServletResponse; |
10 |
| - |
11 |
| -import org.apache.commons.lang3.concurrent.ConcurrentException; |
12 |
| -import org.apache.commons.lang3.concurrent.ConcurrentInitializer; |
13 |
| -import org.apache.commons.lang3.concurrent.LazyInitializer; |
14 |
| -import org.slf4j.Logger; |
15 |
| -import org.slf4j.LoggerFactory; |
16 |
| - |
17 |
| -import com.sap.hcp.cf.logging.servlet.dynlog.DynamicLogLevelConfiguration; |
18 |
| -import com.sap.hcp.cf.logging.servlet.dynlog.DynLogEnvironment; |
19 |
| -import com.sap.hcp.cf.logging.servlet.dynlog.DynamicLogLevelProcessor; |
| 3 | +import org.slf4j.MDC; |
20 | 4 |
|
21 | 5 | /**
|
22 | 6 | * <p>
|
23 |
| - * A simple servlet filter that logs HTTP request processing info. It will read |
24 |
| - * several HTTP Headers and store them in the SLF4J MDC, so that all log |
25 |
| - * messages created during request handling will have those additional fields. |
26 |
| - * It will also instrument the request to generate a request log containing |
27 |
| - * metrics such as request and response sizes and response time. This |
28 |
| - * instrumentation can be disabled by denying logs from {@link RequestLogger} |
29 |
| - * with marker "request". |
| 7 | + * THe {@link RequestLoggingFilter} extracts information from HTTP requests and |
| 8 | + * can create request logs. It will read several HTTP Headers and store them in |
| 9 | + * the SLF4J MDC, so that all log messages created during request handling will |
| 10 | + * have those additional fields. It will also instrument the request to generate |
| 11 | + * a request log containing metrics such as request and response sizes and |
| 12 | + * response time. This instrumentation can be disabled by denying logs from |
| 13 | + * {@link RequestLogger} with marker "request". |
30 | 14 | * </p>
|
31 | 15 | * <p>
|
32 | 16 | * This filter will generate a correlation id, from the HTTP header
|
|
37 | 21 | * <p>
|
38 | 22 | * This filter supports dynamic log levels activated by JWT tokens in HTTP
|
39 | 23 | * headers. Setup and processing of these tokens can be changed with own
|
40 |
| - * implementations of {@link DynLogEnvironment} and |
41 |
| - * {@link DynamicLogLevelProcessor}. For integration provide a subclass of |
42 |
| - * {@link RequestLoggingFilter} and overwrite |
43 |
| - * {@link RequestLoggingFilter#getDynLogConfiguration()} and |
44 |
| - * {@link RequestLoggingFilter#getDynLogLevelProcessor()}. |
| 24 | + * implementations of {@link DynamicLogLevelFilter}. |
45 | 25 | * </p>
|
46 | 26 | * <p>
|
47 | 27 | * To use the filter, it needs to be added to the servlet configuration. It has
|
48 |
| - * a default constructor to support web.xml configuration. There are several |
49 |
| - * other constructors that support some customization, in case dynamic |
50 |
| - * configuration (e.g. Spring Boot) is used. You can add further customization |
51 |
| - * by subclassing the filter and overwrite its methods. |
| 28 | + * a default constructor to support web.xml configuration. You can customize the |
| 29 | + * filter by creating your own subclass of {@link CompositeFilter} and mix and |
| 30 | + * match any of the provided filters and add your own implementation: |
| 31 | + * <ul> |
| 32 | + * <li>{@link AddVcapEnvironmentToLogContextFilter} provide application metadata |
| 33 | + * (app_id, app_name, ...) from environment</li> |
| 34 | + * <li>{@link AddHttpHeadersToLogContextFilter} provides certain HTTP headers to |
| 35 | + * {@link MDC}</li> |
| 36 | + * <li>{@link CorrelationIdFilter} extracts "X-CorrelationId" HTTP header or |
| 37 | + * generates new to add to {@link MDC}</li> |
| 38 | + * <li>{@link DynamicLogLevelFilter} supports JWT based dynamic changes of log |
| 39 | + * level per request</li> |
| 40 | + * <li>{@link GenerateRequestLogFilter} instruments the request to generate the |
| 41 | + * final request log</li> |
| 42 | + * </ul> |
52 | 43 | * </p>
|
53 | 44 | */
|
54 |
| -public class RequestLoggingFilter extends RequestLoggingBaseFilter { |
55 |
| - |
56 |
| - private static final Logger LOG = LoggerFactory.getLogger(RequestLoggingFilter.class); |
57 |
| - |
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; |
61 |
| - |
62 |
| - private ConcurrentInitializer<DynamicLogLevelConfiguration> dynLogEnvironment; |
63 |
| - private ConcurrentInitializer<DynamicLogLevelProcessor> dynamicLogLevelProcessor; |
| 45 | +public class RequestLoggingFilter extends CompositeFilter { |
64 | 46 |
|
65 | 47 | public RequestLoggingFilter() {
|
66 |
| - this(createDefaultRequestRecordFactory()); |
| 48 | + super(new AddVcapEnvironmentToLogContextFilter(), new AddHttpHeadersToLogContextFilter(), |
| 49 | + new CorrelationIdFilter(), new DynamicLogLevelFilter(), new GenerateRequestLogFilter()); |
67 | 50 | }
|
68 | 51 |
|
69 | 52 | public RequestLoggingFilter(RequestRecordFactory requestRecordFactory) {
|
70 |
| - this(requestRecordFactory, createDefaultDynLogEnvironment()); |
71 |
| - } |
72 |
| - |
73 |
| - private static ConcurrentInitializer<DynamicLogLevelConfiguration> createDefaultDynLogEnvironment() { |
74 |
| - DynLogEnvironment environment = new DynLogEnvironment(); |
75 |
| - return () -> environment; |
76 |
| - } |
77 |
| - |
78 |
| - public RequestLoggingFilter(ConcurrentInitializer<DynamicLogLevelConfiguration> dynLogEnvironment) { |
79 |
| - this(createDefaultRequestRecordFactory(), dynLogEnvironment); |
80 |
| - } |
81 |
| - |
82 |
| - public RequestLoggingFilter(RequestRecordFactory requestRecordFactory, |
83 |
| - ConcurrentInitializer<DynamicLogLevelConfiguration> dynLogEnvironment) { |
84 |
| - super(requestRecordFactory); |
85 |
| - this.dynLogEnvironment = dynLogEnvironment; |
86 |
| - this.dynamicLogLevelProcessor = new LazyInitializer<DynamicLogLevelProcessor>() { |
87 |
| - |
88 |
| - @Override |
89 |
| - protected DynamicLogLevelProcessor initialize() throws ConcurrentException { |
90 |
| - return getDynLogConfiguration().map(DynamicLogLevelConfiguration::getRsaPublicKey).map(DynamicLogLevelProcessor::new) |
91 |
| - .get(); |
92 |
| - } |
93 |
| - }; |
94 |
| - } |
95 |
| - |
96 |
| - public RequestLoggingFilter(ConcurrentInitializer<DynamicLogLevelConfiguration> dynLogEnvironment, |
97 |
| - ConcurrentInitializer<DynamicLogLevelProcessor> dynamicLogLevelProcessor) { |
98 |
| - this(createDefaultRequestRecordFactory(), dynLogEnvironment, dynamicLogLevelProcessor); |
| 53 | + super(new AddVcapEnvironmentToLogContextFilter(), new AddHttpHeadersToLogContextFilter(), |
| 54 | + new CorrelationIdFilter(), new DynamicLogLevelFilter(), new GenerateRequestLogFilter( |
| 55 | + requestRecordFactory)); |
99 | 56 | }
|
100 |
| - |
101 |
| - public RequestLoggingFilter(RequestRecordFactory requestRecordFactory, |
102 |
| - ConcurrentInitializer<DynamicLogLevelConfiguration> dynLogEnvironment, |
103 |
| - ConcurrentInitializer<DynamicLogLevelProcessor> dynamicLogLevelProcessor) { |
104 |
| - super(requestRecordFactory); |
105 |
| - this.dynLogEnvironment = dynLogEnvironment; |
106 |
| - this.dynamicLogLevelProcessor = dynamicLogLevelProcessor; |
107 |
| - } |
108 |
| - |
109 |
| - protected Optional<DynamicLogLevelConfiguration> getDynLogConfiguration() { |
110 |
| - try { |
111 |
| - return Optional.of(dynLogEnvironment.get()); |
112 |
| - } catch (ConcurrentException cause) { |
113 |
| - LOG.debug("Cannot initialize dynamic log level environment. Will continue without this feature", cause); |
114 |
| - return Optional.empty(); |
115 |
| - } |
116 |
| - } |
117 |
| - |
118 |
| - protected Optional<DynamicLogLevelProcessor> getDynLogLevelProcessor() { |
119 |
| - try { |
120 |
| - if (getDynLogConfiguration().map(DynamicLogLevelConfiguration::getRsaPublicKey).isPresent()) { |
121 |
| - return Optional.of(dynamicLogLevelProcessor.get()); |
122 |
| - } |
123 |
| - } catch (ConcurrentException cause) { |
124 |
| - LOG.debug("Cannot initialize dynamic log level processor. Will continue without this feature", cause); |
125 |
| - } |
126 |
| - return Optional.empty(); |
127 |
| - } |
128 |
| - |
129 |
| - @Override |
130 |
| - protected void doFilterRequest(HttpServletRequest httpRequest, HttpServletResponse httpResponse, FilterChain chain) |
131 |
| - throws IOException, |
132 |
| - ServletException { |
133 |
| - activateDynamicLogLevels(httpRequest); |
134 |
| - try { |
135 |
| - super.doFilterRequest(httpRequest, httpResponse, chain); |
136 |
| - } finally { |
137 |
| - deactivateDynamicLogLevels(); |
138 |
| - } |
139 |
| - } |
140 |
| - |
141 |
| - private void activateDynamicLogLevels(HttpServletRequest httpRequest) { |
142 |
| - getDynLogLevelProcessor().ifPresent(processor -> { |
143 |
| - getDynLogConfiguration().map(env -> env.getDynLogHeaderValue(httpRequest)).ifPresent( |
144 |
| - processor::copyDynamicLogLevelToMDC); |
145 |
| - }); |
146 |
| - } |
147 |
| - |
148 |
| - private void deactivateDynamicLogLevels() { |
149 |
| - getDynLogLevelProcessor().ifPresent(DynamicLogLevelProcessor::removeDynamicLogLevelFromMDC); |
150 |
| - } |
151 |
| - |
152 | 57 | }
|
0 commit comments