The Only Guide You’ll Ever Need to Sanitise Java Logs

Reading Time: 5 minutes

Ensuring data privacy in logs is crucial for compliance, security, and user trust. This guide explains how to mask and sanitise data in Java logs using Logback, Log4j, and Java Util Logging (JUL). We’ll include both configuration-based and custom code implementations, providing examples of masked log outputs and discussing best practices.

Why Mask or Sanitise Logs?

  • Compliance: Align with regulations like GDPR, CCPA, PCI-DSS.
  • Security: Mitigate data breach risks.
  • Privacy: Maintain user trust by protecting sensitive information.

Logback (Spring Boot 2.x)

Dependencies

Maven:

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.11</version>
</dependency>

Gradle:

implementation 'ch.qos.logback:logback-classic:1.2.11'

Configuration-Based Masking

Logback allows integration with filters for data sanitisation.

logback.xml Configuration:

<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
            <post-encoder>
                <filter class="com.example.logging.DataMaskingFilter"/>
            </post-encoder>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

Custom Data Masking Filter

For more tailored masking logic, create a custom filter:

Java Implementation:

public class DataMaskingFilter extends Filter<ILoggingEvent> {
    @Override
    public FilterReply decide(ILoggingEvent event) {
        String maskedMessage = maskSensitiveData(event.getFormattedMessage());
        event.getLoggerContextVO().getPropertyMap().put("formattedMessage", maskedMessage);
        return FilterReply.NEUTRAL;
    }

    private String maskSensitiveData(String message) {
        return message.replaceAll("\b\d{4}-?\d{4}-?\d{4}-?(\d{4})\b", "**** **** **** $1");
    }
}

Example Log Output:

  • Before: User's credit card: 1234-5678-9101-1121
  • After: User's credit card: **** **** **** 1121

Log4j

Dependencies

Maven:

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.17.1</version>
</dependency>

Gradle:

implementation 'org.apache.logging.log4j:log4j-core:2.17.1'

Configuration-Based Masking

Log4j supports configuration-based solutions for sanitisation.

log4j2.xml Configuration:

<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d [%t] %-5level %logger{36} - %msg%n">
                <RegexFilter regex="(\d{4}-?\d{4}-?\d{4}-?(\d{4}))" replacement="**** **** **** $2" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
            </PatternLayout>
        </Console>
    </Appenders>

    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

Custom DataMaskingLayout

For advanced masking needs, implement a custom layout:

Java Implementation:

public class DataMaskingLayout extends AbstractStringLayout {
    protected DataMaskingLayout(Charset charset) {
        super(charset);
    }

    @Override
    public String toSerializable(LogEvent event) {
        String message = event.getMessage().getFormattedMessage();
        return maskSensitiveData(message);
    }

    private String maskSensitiveData(String message) {
        return message.replaceAll("\b\d{3}-?\d{2}-?\d{4}\b", "***-**-$1");
    }
}

Configure the Custom Layout in Log4j

To use the custom DataMaskingLayout class in your Log4j configuration, you need to declare it in the log4j2.xml file:

log4j2.xml Configuration:

<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <com.example.logging.DataMaskingLayout charset="UTF-8"/>
        </Console>
    </Appenders>

    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

Example Log Output:

  • Before: SSN: 123-45-6789
  • After: SSN: ***-**-6789

JUL (Java Util Logging)

Custom Formatter Example

JUL supports custom formatters for data masking.

Java Implementation:

public class DataMaskingFormatter extends Formatter {
    @Override
    public String format(LogRecord record) {
        String message = record.getMessage();
        return maskSensitiveData(message);
    }

    private String maskSensitiveData(String message) {
        return message.replaceAll("\b\d{4}-?\d{4}-?\d{4}-?(\d{4})\b", "**** **** **** $1");
    }
}

Configuration

To use the custom DataMaskingFormatter, configure JUL by updating the logging.properties file as follows:

logging.properties Configuration:

handlers=java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.level=INFO
java.util.logging.ConsoleHandler.formatter=com.example.logging.DataMaskingFormatter

Example Log Output:

  • Before: Customer's card: 5678-9101-1121-3145
  • After: Customer's card: **** **** **** 3145

Common Regex Patterns By Region

Here’s a reference table that includes sensitive documents and phone numbers for the US, UK, Canada, and Australia. The table is organised by region and data type:

RegionSensitive Data TypeRegex PatternExample Mask
GlobalCredit Card Number\b(?:\d[ -]*?){13,16}\b**** **** **** 1234
GlobalBank Account Number\b\d{8,17}\b
GlobalIBAN\b[A-Z]{2}\d{2}[A-Z0-9]{1,30}\b**** **** **** 1234
GlobalEmail Address\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b****@example.com
GlobalPassport Number\b[A-Z0-9]{6,9}\b*
United StatesSocial Security Number (SSN)\b\d{3}-\d{2}-\d{4}\b-*-
United StatesDriver’s License\b[A-Z0-9]{1,12}\b
United StatesPhone Number\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}(***) *****
United StatesRouting Number\b\d{9}\b*
United StatesHealth Insurance Number\b[A-Z0-9]{8,15}\b
United KingdomNational Insurance Number (NIN)\b[A-CEGHJPR-TW-Z]{2}\d{6}[A-D]\b** *
United KingdomPassport Number\b\d{9}\b*
United KingdomDriver’s License\b[A-Z9]{5}\d{6}[A-Z]{2}\b***
United KingdomPhone Number (Mobile)\b07\d{9}\b07*
United KingdomPhone Number (Landline)\b01\d{8,9}\b01
CanadaSocial Insurance Number (SIN)\b\d{3}-\d{3}-\d{3}\b-***
CanadaPassport Number\b[A-Z]{2}\d{6}\b
CanadaDriver’s License\b[A-Z0-9]{5,12}\b
CanadaPhone Number (Mobile)\b\d{3}-\d{3}-\d{4}\b-****
CanadaPhone Number (Landline)\b\d{3}-\d{3}-\d{4}\b-****
AustraliaTFN\b\d{3} \d{3} \d{3}\b*** *** ***
AustraliaPassport Number\b[A-Z]{2}\d{7}\b*
AustraliaDriver’s License\b[A-Z0-9]{6,10}\b**
AustraliaPhone Number (Mobile)\b04\d{8}\b04
AustraliaPhone Number (Landline)\b\d{2} \d{4} \d{4}\b** **** ****
GlobalIP Address (IPv4)\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b..***.123

Implementing log sanitisation or masking helps ensure your Java applications remain compliant and secure.