My application runs on top of Spring-Boot (Vaadin) and uses a configuration class (see below) to programmatically configure Log4j's JdbcAppender.
In addition to the JdbcAppender a ConsoleAppender is configured via the XML file (see below).
Both appenders use a lookup plugin (see below) which should trigger the user name. The ConsoleAppender works fine and the username is printed in the log after login.
Problem: the JdbcAppender unfortunately does not resolve the lookup. In the database column there is only "$${app:username}".
- configuration class:
@Configuration
public class LogConfiguration {
private final Environment environment;
public LogConfiguration(Environment environment) {
this.environment = environment;
}
@PostConstruct
public void onStartUp() {
String url = Objects.requireNonNull(environment.getProperty("database.url"));
String username = Objects.requireNonNull(environment.getProperty("database.username"));
String password = Objects.requireNonNull(environment.getProperty("database.password"));
ColumnConfig[] columnConfigs = new ColumnConfig[5];
columnConfigs[0] = ColumnConfig.newBuilder()
.setName("logger")
.setPattern("%logger")
.setUnicode(false)
.build();
columnConfigs[1] = ColumnConfig.newBuilder()
.setName("level")
.setPattern("%level")
.setUnicode(false)
.build();
columnConfigs[2] = ColumnConfig.newBuilder()
.setName("message")
.setPattern("%message")
.setUnicode(false)
.build();
columnConfigs[3] = ColumnConfig.newBuilder()
.setName("exception")
.setPattern("%ex{full}")
.setUnicode(false)
.build();
columnConfigs[4] = ColumnConfig.newBuilder()
.setName("revision_id")
.setPattern("%X{rev.id}")
.setUnicode(false)
.build();
columnConfigs[8] = ColumnConfig.newBuilder()
.setName("username")
.setPattern("$${app:username}")
.setUnicode(false)
.build();
ConnectionSource connectionSource = DriverManagerConnectionSource.newBuilder()
.setDriverClassName("org.postgresql.Driver")
.setConnectionString(url)
.setUserName(username.toCharArray())
.setPassword(password.toCharArray())
.build();
ColumnMapping[] columnMappings = new ColumnMapping[1];
columnMappings[0] = ColumnMapping.newBuilder()
.setName("created_at")
.setLiteral("now()")
.setType(OffsetDateTime.class)
.build();
JdbcAppender appender = JdbcAppender.newBuilder()
.setTableName("logs")
.setConnectionSource(connectionSource)
.setName("Database")
.setIgnoreExceptions(true)
.setBufferSize(1)
.setColumnConfigs(columnConfigs)
.setColumnMappings(columnMappings)
.setFilter(ThresholdFilter.createFilter(Level.INFO, Filter.Result.ACCEPT, Filter.Result.DENY))
.build();
appender.start();
Logger logger = (Logger) LogManager.getRootLogger();
logger.addAppender(appender);
}
}
- xml configuration
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss} %-5level - %msg$${app:username}%n"/><!-- %logger{36} -->
</Console>
</Appenders>
<Loggers>
<Logger name="com.stackoverflow" level="info" additivity="true"/>
<Root level="warn">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
- lookup plugin
@Plugin(name = "app", category = StrLookup.CATEGORY)
public class UsernameLookup implements StrLookup {
@Override
public String lookup(String key) {
return username();
}
@Override
public String lookup(LogEvent event, String key) {
return username();
}
private String username() {
User user = SecurityUtils.getUser();
if(user == null) {
return "";
}
return " (" + user.getUsername() + ")";
}
}
The reason for the programmatic configuration was that some data is only available at runtime. I have now circumvented this problem with two configuration files. One configuration file is loaded automatically as default and the other one is then loaded within a Spring Configuration. Additionally, a new Maven dependency is used to load values from the Spring Boot Configuration.