Skip to content

Commit a0b0935

Browse files
Make DynamicLogLevelProcessor extensible
Expose JWT validation and processing for subclasses. This allows custom extensions for dynamic log level processing. Fixes a clean-up issue with package based filtering.
1 parent 65f4501 commit a0b0935

File tree

3 files changed

+106
-38
lines changed

3 files changed

+106
-38
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.sap.hcp.cf.logging.servlet.dynlog;
22

3+
import java.security.interfaces.RSAPublicKey;
34
import java.util.Arrays;
45
import java.util.List;
56

@@ -15,6 +16,9 @@
1516
* HTTP-request-header. If this token is provided and does contain a correct
1617
* signature, valid timestamps and a log-level-value, the log-level for the
1718
* thread triggered by this request will be changed to the provided value.
19+
*
20+
* You can extend this processor to extract custom claims from the JWT. Override
21+
* {@link DynamicLogLevelProcessor#processJWT(DecodedJWT)} for this.
1822
*/
1923
public class DynamicLogLevelProcessor {
2024

@@ -23,33 +27,74 @@ public class DynamicLogLevelProcessor {
2327
"ERROR");
2428
private final TokenDecoder tokenDecoder;
2529

30+
/**
31+
* @deprecated Use
32+
* {@link DynamicLogLevelProcessor#DynamicLogLevelProcessor(RSAPublicKey)}
33+
* instead.
34+
* @param dynLogConfig
35+
* then {@link DynLogConfiguration} to read the public RSA key
36+
* for JWT validation from.
37+
*/
38+
@Deprecated
2639
public DynamicLogLevelProcessor(DynLogConfiguration dynLogConfig) {
27-
this.tokenDecoder = new TokenDecoder(dynLogConfig.getRsaPublicKey());
40+
this(dynLogConfig.getRsaPublicKey());
2841
}
2942

43+
public DynamicLogLevelProcessor(RSAPublicKey publicJwtKey) {
44+
this.tokenDecoder = new TokenDecoder(publicJwtKey);
45+
}
46+
47+
/**
48+
* Decodes and validate the JWT. Configures the dynamic log levels by
49+
* setting the corresponding fields in the MDC.
50+
*
51+
* @param logLevelToken
52+
* the HTTP Header containing the JWT for dynamic log levels
53+
*/
3054
public void copyDynamicLogLevelToMDC(String logLevelToken) {
3155
if (logLevelToken == null) {
3256
return;
3357
} else {
3458
try {
3559
DecodedJWT jwt = tokenDecoder.validateAndDecodeToken(logLevelToken);
36-
String dynamicLogLevel = jwt.getClaim("level").asString();
37-
String packages = jwt.getClaim("packages").asString();
38-
if (ALLOWED_DYNAMIC_LOGLEVELS.contains(dynamicLogLevel)) {
39-
MDC.put(DynamicLogLevelHelper.MDC_DYNAMIC_LOG_LEVEL_KEY, dynamicLogLevel);
40-
MDC.put(DynamicLogLevelHelper.MDC_DYNAMIC_LOG_LEVEL_PREFIXES, packages);
41-
} else {
42-
throw new DynamicLogLevelException("Dynamic Log-Level [" + dynamicLogLevel +
43-
"] provided in header is not valid. Allowed Values are " +
44-
ALLOWED_DYNAMIC_LOGLEVELS.toString());
45-
}
46-
} catch (DynamicLogLevelException e) {
47-
LOGGER.warn("DynamicLogLevelProcessor could not write dynamic log level to MDC", e);
60+
processJWT(jwt);
61+
} catch (DynamicLogLevelException cause) {
62+
LOGGER.warn("DynamicLogLevelProcessor could not write dynamic log level to MDC", cause);
4863
}
4964
}
5065
}
5166

67+
/**
68+
* Extracts the relevant claims for dynamic log level configuration from the
69+
* decoded token. In case of faulty content, i.e. unknown log level a
70+
* {@link DynamicLogLevelException} is thrown. You can override this method
71+
* to implement own interaction with the JWT, e.g. extraction and validation
72+
* of additional claims.
73+
*
74+
* @param jwt
75+
* the decoded JWT from the HTTP header
76+
* @throws DynamicLogLevelException
77+
* if validation of JWT claims fail
78+
*/
79+
protected void processJWT(DecodedJWT jwt) throws DynamicLogLevelException {
80+
String dynamicLogLevel = jwt.getClaim("level").asString();
81+
String packages = jwt.getClaim("packages").asString();
82+
if (ALLOWED_DYNAMIC_LOGLEVELS.contains(dynamicLogLevel)) {
83+
MDC.put(DynamicLogLevelHelper.MDC_DYNAMIC_LOG_LEVEL_KEY, dynamicLogLevel);
84+
MDC.put(DynamicLogLevelHelper.MDC_DYNAMIC_LOG_LEVEL_PREFIXES, packages);
85+
} else {
86+
throw new DynamicLogLevelException("Dynamic Log-Level [" + dynamicLogLevel +
87+
"] provided in header is not valid. Allowed Values are " +
88+
ALLOWED_DYNAMIC_LOGLEVELS.toString());
89+
}
90+
}
91+
92+
/**
93+
* Removes the MDC fields for dynamic log levels. This needs to be called to
94+
* remove the changed log level configuration.
95+
*/
5296
public void removeDynamicLogLevelFromMDC() {
5397
MDC.remove(DynamicLogLevelHelper.MDC_DYNAMIC_LOG_LEVEL_KEY);
98+
MDC.remove(DynamicLogLevelHelper.MDC_DYNAMIC_LOG_LEVEL_PREFIXES);
5499
}
55100
}

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ public RequestLoggingFilter(RequestRecordFactory requestRecordFactory,
105105

106106
@Override
107107
protected DynamicLogLevelProcessor initialize() throws ConcurrentException {
108-
return new DynamicLogLevelProcessor(getDynLogEnvironment().get());
108+
return getDynLogEnvironment().map(DynLogEnvironment::getRsaPublicKey).map(DynamicLogLevelProcessor::new)
109+
.get();
109110
}
110111
};
111112
}
@@ -228,7 +229,7 @@ private void doFilterRequest(HttpServletRequest httpRequest, HttpServletResponse
228229
*/
229230
} finally {
230231
deactivateDynamicLogLevels();
231-
resetLogContext();
232+
resetLogContext();
232233
}
233234
}
234235

@@ -244,7 +245,7 @@ private void deactivateDynamicLogLevels() {
244245
}
245246

246247
private void resetLogContext() {
247-
for (HttpHeader header : HttpHeaders.propagated()) {
248+
for (HttpHeader header: HttpHeaders.propagated()) {
248249
LogContext.remove(header.getField());
249250
}
250251
LogContext.resetContextFields();
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,92 @@
11
package com.sap.hcp.cf.logging.servlet.dynlog;
22

3-
import static org.junit.Assert.assertEquals;
3+
import static org.junit.Assert.*;
44

55
import java.security.KeyPair;
66
import java.security.KeyPairGenerator;
77
import java.security.NoSuchAlgorithmException;
88
import java.security.NoSuchProviderException;
9+
import java.security.PublicKey;
10+
import java.security.interfaces.RSAPublicKey;
911
import java.util.Date;
1012

11-
import javax.xml.bind.DatatypeConverter;
12-
1313
import org.junit.After;
1414
import org.junit.Before;
1515
import org.junit.Test;
1616
import org.junit.runner.RunWith;
17-
import org.mockito.Mock;
1817
import org.mockito.Mockito;
1918
import org.mockito.runners.MockitoJUnitRunner;
2019
import org.slf4j.MDC;
2120

21+
import com.auth0.jwt.interfaces.DecodedJWT;
2222
import com.sap.hcp.cf.logging.common.helper.DynamicLogLevelHelper;
23-
import com.sap.hcp.cf.logging.common.helper.Environment;
2423

2524
@RunWith(MockitoJUnitRunner.class)
2625
public class DynamicLogLevelProcessorTest extends Mockito {
2726

28-
@Mock
29-
private Environment environment;
30-
3127
private DynamicLogLevelProcessor processor;
3228

3329
private String token;
3430

31+
private KeyPair keyPair;
32+
3533
@Before
3634
public void setup() throws NoSuchAlgorithmException, NoSuchProviderException, DynamicLogLevelException {
37-
KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
38-
String keyBase64 = DatatypeConverter.printBase64Binary(keyPair.getPublic().getEncoded());
35+
this.keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
3936
Date issuedAt = new Date();
4037
Date expiresAt = new Date(new Date().getTime() + 10000);
41-
this.token = TokenCreator.createToken(keyPair, "issuer", issuedAt, expiresAt, "TRACE", "myPrefix");
42-
when(environment.getVariable("DYN_LOG_LEVEL_KEY")).thenReturn(keyBase64);
43-
when(environment.getVariable("DYN_LOG_HEADER")).thenReturn("SAP-LOG-LEVEL");
44-
processor = new DynamicLogLevelProcessor(new DynLogEnvironment(environment));
38+
this.token = TokenCreator.createToken(keyPair, "issuer", issuedAt, expiresAt, "TRACE", "myPrefix");
39+
this.processor = new DynamicLogLevelProcessor(getRSAPublicKey(keyPair));
40+
}
41+
42+
private static RSAPublicKey getRSAPublicKey(KeyPair keyPair) {
43+
PublicKey publicKey = keyPair.getPublic();
44+
if (publicKey instanceof RSAPublicKey) {
45+
return (RSAPublicKey) publicKey;
46+
}
47+
return null;
4548
}
4649

4750
@After
48-
public void tearDown() {
51+
public void removeDynamicLogLevelFromMDC() {
52+
processor.removeDynamicLogLevelFromMDC();
4953
}
5054

5155
@Test
52-
public void testCopyDynamicLogLevelToMDC() throws Exception {
56+
public void copiesDynamicLogLevelToMDC() throws Exception {
5357
processor.copyDynamicLogLevelToMDC(token);
5458
assertEquals("TRACE", MDC.get(DynamicLogLevelHelper.MDC_DYNAMIC_LOG_LEVEL_KEY));
5559
}
5660

5761
@Test
58-
public void testDeleteDynamicLogLevelFromMDC() throws Exception {
62+
public void deletesDynamicLogLevelFromMDC() throws Exception {
5963
processor.copyDynamicLogLevelToMDC(token);
6064
processor.removeDynamicLogLevelFromMDC();
6165
assertEquals(null, MDC.get(DynamicLogLevelHelper.MDC_DYNAMIC_LOG_LEVEL_KEY));
6266
}
6367

64-
@Test
65-
public void testCopyDynamicLogPackagesToMDC() throws Exception {
66-
processor.copyDynamicLogLevelToMDC(token);
67-
assertEquals("myPrefix", MDC.get(DynamicLogLevelHelper.MDC_DYNAMIC_LOG_LEVEL_PREFIXES));
68+
@Test
69+
public void copiesDynamicLogPackagesToMDC() throws Exception {
70+
processor.copyDynamicLogLevelToMDC(token);
71+
assertEquals("myPrefix", MDC.get(DynamicLogLevelHelper.MDC_DYNAMIC_LOG_LEVEL_PREFIXES));
72+
}
73+
74+
@Test
75+
public void doesNotCopyDynamicLogLevelOnInvalidJwt() throws Exception {
76+
KeyPair invalidKeyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
77+
new DynamicLogLevelProcessor(getRSAPublicKey(invalidKeyPair)).copyDynamicLogLevelToMDC(token);
78+
assertEquals(null, MDC.get(DynamicLogLevelHelper.MDC_DYNAMIC_LOG_LEVEL_KEY));
79+
}
6880

69-
}
81+
@Test
82+
public void doesNotCopyDynamicLogLevelOnCustomException() throws Exception {
83+
DynamicLogLevelProcessor myProcessor = new DynamicLogLevelProcessor(getRSAPublicKey(keyPair)) {
84+
@Override
85+
protected void processJWT(DecodedJWT jwt) throws DynamicLogLevelException {
86+
throw new DynamicLogLevelException("Always fail in this test-case.");
87+
}
88+
};
89+
myProcessor.copyDynamicLogLevelToMDC(token);
90+
assertEquals(null, MDC.get(DynamicLogLevelHelper.MDC_DYNAMIC_LOG_LEVEL_KEY));
91+
}
7092
}

0 commit comments

Comments
 (0)