Database appender with log4j2 and Spring Boot
The goal of this short example is to show how to configure a log4j2 database appender, and making it using the database configuration properties from a Spring properties file.
Config:
- Spring Boot 1.4.0.RELEASE
- Maven
- Oracle 12g
Maven dependency
We need to define the dependency to the log4j2 starter.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
The starter is going to import the following libraries:
- log4j-core
- log4j-slf4j-impl
- log4j-api
Create the log table
Of course, we have to create a table for the logs. This is a simple example, feel free to adapt it to your needs:
CREATE TABLE LOGS
(
APPLICATION VARCHAR2(20) NULL,
LOG_DATE DATE NOT NULL,
LOGGER VARCHAR2(250) NOT NULL,
LOG_LEVEL VARCHAR2(10) NOT NULL,
MESSAGE VARCHAR2(4000) NOT NULL
);
Spring Boot
I found it easier to register the appender programmatically during the startup of the application context. Here how it works, I created a Spring configuration file dedicated to it:
package some.package.config;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.appender.db.jdbc.ColumnConfig;
import org.apache.logging.log4j.core.appender.db.jdbc.JdbcAppender;
import org.apache.logging.log4j.core.filter.ThresholdFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import javax.annotation.PostConstruct;
import some.package.logging.JdbcConnectionSource;
@Configuration
public class LogConfig
{
@Autowired
private Environment env;
@PostConstruct
public void onStartUp()
{
String url = env.getProperty("spring.datasource.url");
String userName = env.getProperty("spring.datasource.username");
String password = env.getProperty("spring.datasource.password");
String validationQuery = env.getProperty("spring.datasource.validation-query");
// Create a new connectionSource build from the Spring properties
JdbcConnectionSource connectionSource = new JdbcConnectionSource(url, userName, password, validationQuery);
// This is the mapping between the columns in the table and what to insert in it.
ColumnConfig[] columnConfigs = new ColumnConfig[5];
columnConfigs[0] = ColumnConfig.createColumnConfig(null, "APPLICATION", "ACCESS", null, null, "false", null);
columnConfigs[1] = ColumnConfig.createColumnConfig(null, "LOG_DATE", null, null, "true", null, null);
columnConfigs[2] = ColumnConfig.createColumnConfig(null, "LOGGER", "%logger", null, null, "false", null);
columnConfigs[3] = ColumnConfig.createColumnConfig(null, "LOG_LEVEL", "%level", null, null, "false", null);
columnConfigs[4] = ColumnConfig.createColumnConfig(null, "MESSAGE", "%message", null, null, "false", null);
// filter for the appender to keep only errors
ThresholdFilter filter = ThresholdFilter.createFilter(Level.ERROR, null, null);
// The creation of the new database appender passing:
// - the name of the appender
// - ignore exceptions encountered when appending events are logged
// - the filter created previously
// - the connectionSource,
// - log buffer size,
// - the name of the table
// - the config of the columns.
JdbcAppender appender = JdbcAppender.createAppender("DB", "true", filter, connectionSource, "1", "LOGS", columnConfigs);
// start the appender, and this is it...
appender.start();
((Logger) LogManager.getRootLogger()).addAppender(appender);
}
}
Connection source
The final step is the connection source. Here are the sources:
package some.package.logging;
import org.apache.commons.dbcp.DriverManagerConnectionFactory;
import org.apache.commons.dbcp.PoolableConnection;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.PoolingDataSource;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.apache.logging.log4j.core.appender.db.jdbc.ConnectionSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class JdbcConnectionSource implements ConnectionSource
{
private DataSource dataSource;
public JdbcConnectionSource(String url, String userName, String password, String validationQuery)
{
Properties properties = new Properties();
properties.setProperty("user", userName);
properties.setProperty("password", password);
GenericObjectPool<PoolableConnection> pool = new GenericObjectPool<>();
DriverManagerConnectionFactory cf = new DriverManagerConnectionFactory(url, properties);
new PoolableConnectionFactory(cf, pool, null, validationQuery, 3, false, false, Connection.TRANSACTION_READ_COMMITTED);
this.dataSource = new PoolingDataSource(pool);
}
@Override
public Connection getConnection() throws SQLException
{
return dataSource.getConnection();
}
}
A datasource is initialized in the constructor of the class, and then the idea is to override the method getConnection.