|
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