I'm trying to implement a regex-based replacement of sensitive log data, using the default Quarkus logging solution.
For example, if in the logged information appears <password>secret</
, would like it to be saved into the logfile as <password>***</
. I had this working in other apps using Logback and defining in the logback.xml a conversionRule
and a pattern
:
<conversionRule conversionWord="replaceConverter"
converterClass="org.something.logger.CustomFieldCompositeConverter" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>logs/replaced.log</file>
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp/>
<logLevel/>
<logLevelValue/>
<loggerName/>
<threadName/>
<pattern>
<pattern>
{
"message":"%replaceConverter(%message){'$1***$2', '(:password>)(?:.*)(</.*:password>)'}",
}
</pattern>
</pattern>
I would like to know if something similar can be done if I use the Quarkus default JBoss logger-based logging solution, or using the Logback extension is the only option. So far I have found nothing related to log message replacing/processing in the Quarkus logging guide. Do you know if this can be done?
I'm also using Slf4j, if that is relevant.
I was facing a similar issue. I've been able to solve it without logback. I'm using the default JBoss LoggerManager with SLF4J. For this, I've added the following dependencies
<dependency>
<groupId>org.jboss.slf4j</groupId>
<artifactId>slf4j-jboss-logmanager</artifactId>
</dependency>
And for test, I had to add a system property in my pom.xml to the surefire plugin like this
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
<argLine>-Dfile.encoding=UTF-8</argLine>
</configuration>
</plugin>
After that, I created a custom implementation for org.jboss.logmanager.handlers.ConsoleHandler
interface
import org.jboss.logmanager.handlers.ConsoleHandler;
import java.util.logging.LogRecord;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CustomConsoleHandler extends ConsoleHandler {
@Override
public void publish(LogRecord record) {
record.setMessage(maskPassword(record.getMessage()));
super.publish(record);
}
private String maskPassword(String body) {
Matcher matcher = Pattern.compile("(((pwd|pw|pass|password|pwrd)([=: ])+\\s*['\"]?)([^*]+?))(,|\\s|'|\")", Pattern.CASE_INSENSITIVE).matcher(body);
while (matcher.find()) {
body = new StringBuilder(body).replace(matcher.start(5), matcher.end(5), "******").toString();
matcher.reset(body);
}
return body;
}
}
To use this handler with the default handlers that quarkus generates I had to add them to the delayed handlers in io.quarkus.bootstrap.logging.InitialConfigurator
!!! The order of the handlers is important, they are chained after each other, so the second handler will work with the output of the first handler, so we have to put our CustomConsoleHandler in the first place in the array!!!
I don't need to mask liquibase and other logs at initialization time (before bean creations) so I've added my handler in a post construct method of a bean annotated with @Startup annotation.
import io.quarkus.runtime.Startup;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import java.util.logging.Handler;
import static io.quarkus.bootstrap.logging.InitialConfigurator.DELAYED_HANDLER;
@ApplicationScoped
@Slf4j
@Startup(1)
public class LoggerConfig {
@PostConstruct
public void initLogger() {
Handler[] oldHandlers = DELAYED_HANDLER.clearHandlers();
DELAYED_HANDLER.addHandler(new CustomConsoleHandler());
for (Handler oldHandler : oldHandlers) {
DELAYED_HANDLER.addHandler(oldHandler);
}
}
}
It is important to use DELAYED_HANDLER's thread-safe public methods to clear and add handlers.
To try this you can add a request body logger like this
import lombok.extern.slf4j.Slf4j;
import javax.enterprise.context.RequestScoped;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.ReaderInterceptor;
import javax.ws.rs.ext.ReaderInterceptorContext;
import java.io.IOException;
@Slf4j
@Provider
@RequestScoped
public class logServletFilter implements ReaderInterceptor {
@Override
public Object aroundReadFrom(ReaderInterceptorContext context)
throws IOException, WebApplicationException {
log.info("Reading request with type: " + context.getGenericType());
Object o = context.proceed();
log.info("Request body " + o.toString());
return o;
}
}
In application.yaml I have the following configuration for logs
quarkus:
console:
color: true
log:
console:
format: "Level={%-5p} Elapsed time={%r} Source class={%C} Date={%d{yyyy-MM-dd HH:mm:ss}} Thread={%t} ThreadId={%t{id}} Host={%h} MDC={%X} Nested={%x} %nMessage={%m} Ex={%e}%n"
level: DEBUG