I am implementing log4j custom pattern converter. During runtime when it tries to invoke "newInstance" method of converter class, it fails with error :
"Error creating converter for cm java.lang.IllegalAccessException: Class org.apache.logging.log4j.core.pattern.PatternParser can not access a member of class com.test.plugin.LogMaskingConverter with modifiers "public static""
After debugging I found that the "newInstance" is invoked with Reflection and in Reflection class before invoking the method it calls "isSameClassPackage" and returns false.
one possible reason could be different class loaders are used for loading main and converter class.
I tried the solution given in below thread and also adding plugin congfiguration in pom.xml but none of it worked.
log4j2 configuration will not load custom pattern converter
LogMaskingConverter.java
package com.test.plugin;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.pattern.ConverterKeys;
import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
@Plugin(name = "LogMaskingConverter", category = "Converter")
@ConverterKeys({"cm"})
class LogMaskingConverter extends LogEventPatternConverter {
private static final String NAME = "cm";
private static final String JSON_REPLACEMENT_REGEX = "\"\\$1\": \"****\"";
private static final String JSON_KEYS = String.join("|", new String[]{"ssn", "private", "creditCard"});
private static final Pattern JSON_PATTERN = Pattern.compile("(${ssn|card}): ([^]+)");
private static final LogMaskingConverter INSTANCE = new LogMaskingConverter();
private LogMaskingConverter() {
super(NAME, NAME);
}
public static LogMaskingConverter newInstance(final String[] options) {
return INSTANCE;
}
@Override
public void format(LogEvent event, StringBuilder outputMessage) {
String message = event.getMessage().getFormattedMessage();
String maskedMessage = message;
if (event.getMarker().getName() == LoggingMarkers.JSON.getName()) {
try {
maskedMessage = mask(message);
} catch (Exception e) {
maskedMessage = message; // Although if this fails, it may be better to not log the message
}
}
outputMessage.append(maskedMessage);
}
private String mask(String message) {
StringBuffer buffer = new StringBuffer();
Matcher matcher = JSON_PATTERN.matcher(message);
while (matcher.find()) {
matcher.appendReplacement(buffer, JSON_REPLACEMENT_REGEX);
}
matcher.appendTail(buffer);
return buffer.toString();
}
}
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration xmlns="http://logging.apache.org/log4j/2.0/config"
status="all" packages="com.test.plugin">
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout pattern=" %-6level %cm %d{HH:mm:ss.SSS} %msg%n" />
</Console>
</Appenders>
<Loggers>
<Logger level="all" />
<root level="all">
<appender-ref ref="console" level="TRACE" />
</root>
</Loggers>
</Configuration>
main class
package com.test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.test.plugin.LoggingMarkers;
public class TestLogging {
public static final Logger logger = LoggerFactory.getLogger(TestLogging.class);
public TestLogging() {
logger.info("test info");
logger.debug(LoggingMarkers.JSON, "{\"ssn\": \"1234567890\"}");
}
public static void main(String[] args) {
new TestLogging();
}
}