diff --git a/pom.xml b/pom.xml
index 58462d45..d9b3c91f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -107,6 +107,11 @@
micrometer-registry-prometheus
${micrometer.version}
+
+ ch.qos.logback
+ logback-classic
+ 1.3.5
+
com.github.ben-manes.caffeine
caffeine
diff --git a/src/main/java/com/uid2/shared/util/MaskingPatternLayout.java b/src/main/java/com/uid2/shared/util/MaskingPatternLayout.java
new file mode 100644
index 00000000..809cbdf0
--- /dev/null
+++ b/src/main/java/com/uid2/shared/util/MaskingPatternLayout.java
@@ -0,0 +1,31 @@
+package com.uid2.shared.util;
+
+import ch.qos.logback.classic.PatternLayout;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+
+import java.util.Map;
+
+public class MaskingPatternLayout extends PatternLayout {
+ private static final Map MASKING_PATTERNS = Map.of(
+ "[^\\s]+s3\\.amazonaws\\.com\\/.*X-Amz-Security-Token=[^\\s]+", "REDACTED - S3"
+ );
+
+ @Override
+ public String doLayout(ILoggingEvent event) {
+ return mask(super.doLayout(event));
+ }
+
+ private String mask(String message) {
+ if (message == null) {
+ return null;
+ }
+
+ String maskedMessage = message;
+ for (Map.Entry entry : MASKING_PATTERNS.entrySet()) {
+ String regex = entry.getKey();
+ String mask = entry.getValue();
+ maskedMessage = maskedMessage.replaceAll(regex, mask);
+ }
+ return maskedMessage;
+ }
+}
diff --git a/src/test/java/com/uid2/shared/util/MaskingPatternLayoutTest.java b/src/test/java/com/uid2/shared/util/MaskingPatternLayoutTest.java
new file mode 100644
index 00000000..fa287157
--- /dev/null
+++ b/src/test/java/com/uid2/shared/util/MaskingPatternLayoutTest.java
@@ -0,0 +1,69 @@
+package com.uid2.shared.util;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.LoggingEvent;
+import ch.qos.logback.core.pattern.FormattingConverter;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@ExtendWith(MockitoExtension.class)
+public class MaskingPatternLayoutTest {
+ private static final Logger LOGGER = (Logger) LoggerFactory.getLogger(MaskingPatternLayoutTest.class);
+ private static final MaskingPatternLayout MASKING_PATTERN_LAYOUT = new MaskingPatternLayout();
+
+ @BeforeAll
+ public static void setupAll() {
+ LoggerContext loggerContext = LOGGER.getLoggerContext();
+ MASKING_PATTERN_LAYOUT.setPattern("%msg %ex");
+ MASKING_PATTERN_LAYOUT.setContext(loggerContext);
+ MASKING_PATTERN_LAYOUT.start();
+ }
+
+ @ParameterizedTest
+ @MethodSource("maskedMessagesWithS3")
+ public void testMaskedMessagesWithS3(String message, String maskedMessage) {
+ String log = MASKING_PATTERN_LAYOUT.doLayout(getLoggingEvent(message));
+
+ assertAll(
+ "testMaskingMessageWithS3",
+ () -> assertEquals(maskedMessage, log.trim())
+ );
+ }
+
+ private static Set maskedMessagesWithS3() {
+ String urlWithoutProtocol = "myservice.s3.amazonaws.com/some/path?param1=value1&X-Amz-Security-Token=mysecurityToken¶m3=value3";
+ Map maskedMessages = Map.of(
+ "Error: " + urlWithoutProtocol + " and something else", "Error: REDACTED - S3 and something else",
+ "https://" + urlWithoutProtocol, "REDACTED - S3",
+ "http://" + urlWithoutProtocol, "REDACTED - S3",
+ urlWithoutProtocol, "REDACTED - S3"
+ );
+
+ return maskedMessages.entrySet().stream()
+ .map(entry -> Arguments.of(entry.getKey(), entry.getValue()))
+ .collect(Collectors.toSet());
+ }
+
+ private static ILoggingEvent getLoggingEvent(String msg, Exception ex) {
+ return new LoggingEvent(FormattingConverter.class.getName(), LOGGER, Level.ERROR, msg, ex, null);
+ }
+
+ private static ILoggingEvent getLoggingEvent(String msg) {
+ return getLoggingEvent(msg, null);
+ }
+}