Skip to content

Commit 90b4cf5

Browse files
Add Filter for dynamic log level configuration
1 parent 42f8aa3 commit 90b4cf5

File tree

7 files changed

+212
-17
lines changed

7 files changed

+212
-17
lines changed

cf-java-logging-support-servlet/src/main/java/com/sap/hcp/cf/logging/servlet/dynlog/DynLogEnvironment.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
import com.sap.hcp.cf.logging.common.helper.Environment;
1212

13-
public class DynLogEnvironment implements DynLogConfiguration {
13+
public class DynLogEnvironment implements DynamicLogLevelConfiguration {
1414

1515
private static final Logger LOGGER = LoggerFactory.getLogger(DynLogEnvironment.class);
1616
private final RSAPublicKey rsaPublicKey;

cf-java-logging-support-servlet/src/main/java/com/sap/hcp/cf/logging/servlet/dynlog/DynLogConfiguration.java renamed to cf-java-logging-support-servlet/src/main/java/com/sap/hcp/cf/logging/servlet/dynlog/DynamicLogLevelConfiguration.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44

55
import javax.servlet.http.HttpServletRequest;
66

7-
public interface DynLogConfiguration {
8-
9-
String getDynLogHeaderKey();
7+
@FunctionalInterface
8+
public interface DynamicLogLevelConfiguration {
109

1110
RSAPublicKey getRsaPublicKey();
1211

12+
default String getDynLogHeaderKey() {
13+
return "SAP-LOG-LEVEL";
14+
};
15+
1316
default String getDynLogHeaderValue(HttpServletRequest httpRequest) {
1417
return httpRequest.getHeader(getDynLogHeaderKey());
1518
}

cf-java-logging-support-servlet/src/main/java/com/sap/hcp/cf/logging/servlet/dynlog/DynamicLogLevelProcessor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ public class DynamicLogLevelProcessor {
3232
* {@link DynamicLogLevelProcessor#DynamicLogLevelProcessor(RSAPublicKey)}
3333
* instead.
3434
* @param dynLogConfig
35-
* the {@link DynLogConfiguration} to read the public RSA key for
35+
* the {@link DynamicLogLevelConfiguration} to read the public RSA key for
3636
* JWT validation from.
3737
*/
3838
@Deprecated
39-
public DynamicLogLevelProcessor(DynLogConfiguration dynLogConfig) {
39+
public DynamicLogLevelProcessor(DynamicLogLevelConfiguration dynLogConfig) {
4040
this(dynLogConfig.getRsaPublicKey());
4141
}
4242

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package com.sap.hcp.cf.logging.servlet.filter;
2+
3+
import java.util.Optional;
4+
5+
import javax.servlet.http.HttpServletRequest;
6+
import javax.servlet.http.HttpServletResponse;
7+
8+
import org.apache.commons.lang3.concurrent.ConcurrentException;
9+
import org.apache.commons.lang3.concurrent.ConcurrentInitializer;
10+
import org.apache.commons.lang3.concurrent.LazyInitializer;
11+
import org.slf4j.Logger;
12+
import org.slf4j.LoggerFactory;
13+
14+
import com.sap.hcp.cf.logging.servlet.dynlog.DynamicLogLevelConfiguration;
15+
import com.sap.hcp.cf.logging.servlet.dynlog.DynLogEnvironment;
16+
import com.sap.hcp.cf.logging.servlet.dynlog.DynamicLogLevelProcessor;
17+
18+
/**
19+
* <p>
20+
* The {@link DynamicLogLevelFilter} provides an adapter to an
21+
* {@link DynamicLogLevelProcessor}. It extracts the JWT from the HTTP header
22+
* and hands it over to the {@link DynamicLogLevelProcessor} for verification
23+
* and modification of the MDC.
24+
* </p>
25+
*
26+
* <p>
27+
* Setup and processing of these tokens can be changed with own implementations
28+
* of {@link DynamicLogLevelConfiguration} and {@link DynamicLogLevelProcessor}.
29+
* For integration provide a subclass of {@link DynamicLogLevelFilter} and
30+
* overwrite {@link DynamicLogLevelFilter#getDynLogConfiguration()} and
31+
* {@link DynamicLogLevelFilter#getDynLogLevelProcessor()}. Alternatively you
32+
* can use the different constructors to provide a custom configuration and
33+
* processor
34+
* </p>
35+
*/
36+
public class DynamicLogLevelFilter extends AbstractLoggingFilter {
37+
38+
private static final Logger LOG = LoggerFactory.getLogger(DynamicLogLevelFilter.class);
39+
40+
private ConcurrentInitializer<DynamicLogLevelConfiguration> configuration;
41+
private ConcurrentInitializer<DynamicLogLevelProcessor> processor;
42+
43+
/**
44+
* Provides dynamic log levels by reading the configuration from environment
45+
* variables and using the default {@link DynamicLogLevelProcessor}.
46+
*/
47+
public DynamicLogLevelFilter() {
48+
this(() -> new DynLogEnvironment());
49+
}
50+
51+
/**
52+
* Provides dynamic log levels by using the given configuration and the
53+
* default {@link DynamicLogLevelProcessor}.
54+
*
55+
* @param configuration
56+
* a {@link ConcurrentInitializer} for the configuration, you can
57+
* use a lambda: {@code () -> config}
58+
*/
59+
public DynamicLogLevelFilter(ConcurrentInitializer<DynamicLogLevelConfiguration> configuration) {
60+
this.configuration = configuration;
61+
this.processor = new LazyInitializer<DynamicLogLevelProcessor>() {
62+
63+
@Override
64+
protected DynamicLogLevelProcessor initialize() throws ConcurrentException {
65+
return getConfiguration().map(DynamicLogLevelConfiguration::getRsaPublicKey).map(
66+
DynamicLogLevelProcessor::new)
67+
.get();
68+
}
69+
};
70+
}
71+
72+
/**
73+
* Provides dynamic log levels by using the given configuration and
74+
* processor.
75+
*
76+
* @param configuration
77+
* a {@link ConcurrentInitializer} for the configuration, you can
78+
* use a lambda: {@code () -> config}
79+
* @param processor
80+
* a {@link ConcurrentInitializer} for the processor, you can use
81+
* a lambda: {@code () -> processor}
82+
*/
83+
public DynamicLogLevelFilter(ConcurrentInitializer<DynamicLogLevelConfiguration> configuration,
84+
ConcurrentInitializer<DynamicLogLevelProcessor> processor) {
85+
this.configuration = configuration;
86+
this.processor = processor;
87+
}
88+
89+
/**
90+
* Get the current {@link DynamicLogLevelConfiguration}. Overload this
91+
* method for customization when you cannot use the constructors.
92+
*
93+
* @return an {@link Optional} of the current configuration
94+
*/
95+
protected Optional<DynamicLogLevelConfiguration> getConfiguration() {
96+
try {
97+
return Optional.of(configuration.get());
98+
} catch (ConcurrentException cause) {
99+
LOG.debug("Cannot initialize dynamic log level environment. Will continue without this feature", cause);
100+
return Optional.empty();
101+
}
102+
}
103+
104+
/**
105+
* Get the current {@link DynamicLogLevelProcessor}. Overload this method
106+
* for customization when you cannot use the constructors.
107+
*
108+
* @return an {@link Optional} of the current processor
109+
*/
110+
protected Optional<DynamicLogLevelProcessor> getProcessor() {
111+
try {
112+
return Optional.of(processor.get());
113+
} catch (ConcurrentException cause) {
114+
LOG.debug("Cannot initialize dynamic log level processor. Will continue without this feature", cause);
115+
}
116+
return Optional.empty();
117+
}
118+
119+
@Override
120+
protected void preProcess(HttpServletRequest request, HttpServletResponse response) {
121+
getProcessor().ifPresent(processor -> extractHeader(request).ifPresent(processor::copyDynamicLogLevelToMDC));
122+
}
123+
124+
private Optional<String> extractHeader(HttpServletRequest request) {
125+
return getConfiguration().map(cfg -> cfg.getDynLogHeaderValue(request));
126+
}
127+
128+
@Override
129+
protected void postProcess(HttpServletRequest request, HttpServletResponse response) {
130+
getProcessor().ifPresent(DynamicLogLevelProcessor::removeDynamicLogLevelFromMDC);
131+
}
132+
133+
}

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import org.slf4j.Logger;
1515
import org.slf4j.LoggerFactory;
1616

17-
import com.sap.hcp.cf.logging.servlet.dynlog.DynLogConfiguration;
17+
import com.sap.hcp.cf.logging.servlet.dynlog.DynamicLogLevelConfiguration;
1818
import com.sap.hcp.cf.logging.servlet.dynlog.DynLogEnvironment;
1919
import com.sap.hcp.cf.logging.servlet.dynlog.DynamicLogLevelProcessor;
2020

@@ -59,7 +59,7 @@ public class RequestLoggingFilter extends RequestLoggingBaseFilter {
5959
public static final String WRAP_RESPONSE_INIT_PARAM = RequestLoggingBaseFilter.WRAP_REQUEST_INIT_PARAM;
6060
public static final String WRAP_REQUEST_INIT_PARAM = RequestLoggingBaseFilter.WRAP_REQUEST_INIT_PARAM;
6161

62-
private ConcurrentInitializer<DynLogConfiguration> dynLogEnvironment;
62+
private ConcurrentInitializer<DynamicLogLevelConfiguration> dynLogEnvironment;
6363
private ConcurrentInitializer<DynamicLogLevelProcessor> dynamicLogLevelProcessor;
6464

6565
public RequestLoggingFilter() {
@@ -70,43 +70,43 @@ public RequestLoggingFilter(RequestRecordFactory requestRecordFactory) {
7070
this(requestRecordFactory, createDefaultDynLogEnvironment());
7171
}
7272

73-
private static ConcurrentInitializer<DynLogConfiguration> createDefaultDynLogEnvironment() {
73+
private static ConcurrentInitializer<DynamicLogLevelConfiguration> createDefaultDynLogEnvironment() {
7474
DynLogEnvironment environment = new DynLogEnvironment();
7575
return () -> environment;
7676
}
7777

78-
public RequestLoggingFilter(ConcurrentInitializer<DynLogConfiguration> dynLogEnvironment) {
78+
public RequestLoggingFilter(ConcurrentInitializer<DynamicLogLevelConfiguration> dynLogEnvironment) {
7979
this(createDefaultRequestRecordFactory(), dynLogEnvironment);
8080
}
8181

8282
public RequestLoggingFilter(RequestRecordFactory requestRecordFactory,
83-
ConcurrentInitializer<DynLogConfiguration> dynLogEnvironment) {
83+
ConcurrentInitializer<DynamicLogLevelConfiguration> dynLogEnvironment) {
8484
super(requestRecordFactory);
8585
this.dynLogEnvironment = dynLogEnvironment;
8686
this.dynamicLogLevelProcessor = new LazyInitializer<DynamicLogLevelProcessor>() {
8787

8888
@Override
8989
protected DynamicLogLevelProcessor initialize() throws ConcurrentException {
90-
return getDynLogConfiguration().map(DynLogConfiguration::getRsaPublicKey).map(DynamicLogLevelProcessor::new)
90+
return getDynLogConfiguration().map(DynamicLogLevelConfiguration::getRsaPublicKey).map(DynamicLogLevelProcessor::new)
9191
.get();
9292
}
9393
};
9494
}
9595

96-
public RequestLoggingFilter(ConcurrentInitializer<DynLogConfiguration> dynLogEnvironment,
96+
public RequestLoggingFilter(ConcurrentInitializer<DynamicLogLevelConfiguration> dynLogEnvironment,
9797
ConcurrentInitializer<DynamicLogLevelProcessor> dynamicLogLevelProcessor) {
9898
this(createDefaultRequestRecordFactory(), dynLogEnvironment, dynamicLogLevelProcessor);
9999
}
100100

101101
public RequestLoggingFilter(RequestRecordFactory requestRecordFactory,
102-
ConcurrentInitializer<DynLogConfiguration> dynLogEnvironment,
102+
ConcurrentInitializer<DynamicLogLevelConfiguration> dynLogEnvironment,
103103
ConcurrentInitializer<DynamicLogLevelProcessor> dynamicLogLevelProcessor) {
104104
super(requestRecordFactory);
105105
this.dynLogEnvironment = dynLogEnvironment;
106106
this.dynamicLogLevelProcessor = dynamicLogLevelProcessor;
107107
}
108108

109-
protected Optional<DynLogConfiguration> getDynLogConfiguration() {
109+
protected Optional<DynamicLogLevelConfiguration> getDynLogConfiguration() {
110110
try {
111111
return Optional.of(dynLogEnvironment.get());
112112
} catch (ConcurrentException cause) {
@@ -117,7 +117,7 @@ protected Optional<DynLogConfiguration> getDynLogConfiguration() {
117117

118118
protected Optional<DynamicLogLevelProcessor> getDynLogLevelProcessor() {
119119
try {
120-
if (getDynLogConfiguration().map(DynLogConfiguration::getRsaPublicKey).isPresent()) {
120+
if (getDynLogConfiguration().map(DynamicLogLevelConfiguration::getRsaPublicKey).isPresent()) {
121121
return Optional.of(dynamicLogLevelProcessor.get());
122122
}
123123
} catch (ConcurrentException cause) {

cf-java-logging-support-servlet/src/test/java/com/sap/hcp/cf/logging/servlet/filter/CorrelationIdFilterTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public void setUp() throws Exception {
4747
doAnswer(mdcExtractor).when(chain).doFilter(request, response);
4848
}
4949

50-
public String getExtractedCorrelationId() {
50+
private String getExtractedCorrelationId() {
5151
return mdcExtractor.getField(HttpHeaders.CORRELATION_ID.getField());
5252
}
5353

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.sap.hcp.cf.logging.servlet.filter;
2+
3+
import static org.mockito.Mockito.verify;
4+
import static org.mockito.Mockito.verifyZeroInteractions;
5+
import static org.mockito.Mockito.when;
6+
7+
import javax.servlet.FilterChain;
8+
import javax.servlet.http.HttpServletRequest;
9+
import javax.servlet.http.HttpServletResponse;
10+
11+
import org.junit.Test;
12+
import org.junit.runner.RunWith;
13+
import org.mockito.Mock;
14+
import org.mockito.runners.MockitoJUnitRunner;
15+
16+
import com.sap.hcp.cf.logging.servlet.dynlog.DynamicLogLevelConfiguration;
17+
import com.sap.hcp.cf.logging.servlet.dynlog.DynamicLogLevelProcessor;
18+
19+
@RunWith(MockitoJUnitRunner.class)
20+
public class DynamicLogLevelFilterTest {
21+
22+
@Mock
23+
private HttpServletRequest request;
24+
@Mock
25+
private HttpServletResponse response;
26+
@Mock
27+
private FilterChain chain;
28+
29+
@Mock
30+
private DynamicLogLevelConfiguration configuration;
31+
@Mock
32+
private DynamicLogLevelProcessor processor;
33+
34+
@Test
35+
public void forwardsHeaderToProcessor() throws Exception {
36+
when(configuration.getDynLogHeaderValue(request)).thenReturn("header-value");
37+
38+
new DynamicLogLevelFilter(() -> configuration, () -> processor).doFilter(request, response, chain);
39+
40+
verify(processor).copyDynamicLogLevelToMDC("header-value");
41+
}
42+
43+
@Test
44+
public void removesDynamicLogLevelFromMDC() throws Exception {
45+
new DynamicLogLevelFilter(() -> configuration, () -> processor).doFilter(request, response, chain);
46+
47+
verify(processor).removeDynamicLogLevelFromMDC();
48+
}
49+
50+
@Test
51+
public void doesNotCallProcessorOnMissingHeader() throws Exception {
52+
new DynamicLogLevelFilter(() -> configuration, () -> processor).doFilter(request, response, chain);
53+
54+
verify(processor).removeDynamicLogLevelFromMDC();
55+
verifyZeroInteractions(processor);
56+
57+
}
58+
59+
}

0 commit comments

Comments
 (0)