Samuel Masue

Yet another tech blog! Let's talk about Alfresco, Java, Linux, ...

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.

comments powered by Disqus