Andrea Girardi - It's my blog!

Tag: spring-data

How to use Spring DataSource bean as data source for Log4j 2 JDBC appender

I would like to log log4j2 messages into a relational database using the datasource defined on application context and initialized using spring using log4j 2.10.

One possibility is to add a JDBC appender inside log4j2 xml configuration but, Log4j is initialized before Spring so, dataSource won’t be available at runtime so the only solution is to add an appender programmatically. Of course, it is possible to use a log4j2 jdbc appender but, this approach allow to have the possibility to use spring.properties file to override the spring application context with proper environment settings in according to my profile.

This is the datasource defined on application context xml:

<!--  ############ SQLSERVER DATABASE SECTION ############ -->
<bean id="dataSourceMSSqlServer" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
    <property name="url" value="jdbc:sqlserver://${sqlserver.hostname};databaseName=${sqlserver.database};" />
    <property name="username" value="${sqlserver.user}" />
    <property name="password" value="${sqlserver.pass}" />
</bean>

this allow me to configure different database for every environment.
This is the table on which I want to log the entries:

[Id] [INT] IDENTITY(1,1) NOT NULL,
[CreatedTimeStamp] [datetimeoffset](7) NOT NULL,
[Level] [INT] NOT NULL,
[SOURCE] [nvarchar](MAX) NULL,
[Message] [nvarchar](MAX) NULL,
[Content] [nvarchar](MAX) NULL,
[ProductName] [nvarchar](MAX) NULL,
[Version] [nvarchar](MAX) NULL,
[LogType] [INT] NOT NULL DEFAULT ((0)),
[AuditEventType] [INT] NULL,
[UserId] [nvarchar](128) NULL,

The plan is to create a spring bean, inject the DataSource bean, and add JDBC Appender configuration dynamically in @PostConstruct method.

package com.afm.web.utility;
 
import java.sql.Connection;
import java.sql.SQLException;
 
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
 
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.db.ColumnMapping;
import org.apache.logging.log4j.core.appender.db.jdbc.ColumnConfig;
import org.apache.logging.log4j.core.appender.db.jdbc.ConnectionSource;
import org.apache.logging.log4j.core.appender.db.jdbc.JdbcAppender;
import org.apache.logging.log4j.core.config.AppenderRef;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
@Component
public class JDBCLog {
 
    @Autowired
    private DataSource dataSourceMSSqlServer;
 
    // Inner class
    class Connect implements ConnectionSource {
 
	private DataSource dsource;
 
	public Connect(DataSource dsource) {
	    this.dsource = dsource;
	}
 
	@Override
	public Connection getConnection() throws SQLException {
	    return this.dsource.getConnection();
	}
 
    }
 
    public JDBCLog() {}
 
    @PostConstruct
    private void init(){
 
	System.out.println("####### JDBCLog init() ########");      
	final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); 
	final Configuration config = ctx.getConfiguration();
 
	// Here I define the columns I want to log. 
	ColumnConfig[] columnConfigs = new ColumnConfig[] {
	    ColumnConfig.newBuilder()
                .setName("CreatedTimeStamp")
                .setPattern(null)
                .setLiteral(null)
                .setEventTimestamp(true)
                .setUnicode(false)
                .setClob(false).build(),
	    ColumnConfig.newBuilder()
                .setName("Source")
                .setPattern("%K{className}")
                .setLiteral(null)
                .setEventTimestamp(false)
                .setUnicode(false)
                .setClob(false).build(),
	    ColumnConfig.newBuilder()
                .setName("Level")
                .setPattern("%level")
                .setLiteral(null)
                .setEventTimestamp(false)
                .setUnicode(false)
                .setClob(false).build(),
	    ColumnConfig.newBuilder()
                .setName("Message")
                .setPattern("%K{message}")
                .setLiteral(null)
                .setEventTimestamp(false)
                .setUnicode(false)
                .setClob(false).build(),
	    ColumnConfig.newBuilder()
                .setName("Content")
                .setPattern("%K{exception}")
                .setLiteral(null)
                .setEventTimestamp(false)
                .setUnicode(false)
                .setClob(false).build(),
	    ColumnConfig.newBuilder()
                .setName("ProductName")
                .setPattern(null)
                .setLiteral("'DHC'")
                .setEventTimestamp(false)
                .setUnicode(false)
                .setClob(false).build(),
	    ColumnConfig.newBuilder()
                .setName("Version")
                .setPattern(null)
                .setLiteral("'1.0'")
                .setEventTimestamp(false)
                .setUnicode(false)
                .setClob(false).build(),
	    ColumnConfig.newBuilder()
                .setName("AuditEventType")
                .setPattern("%K{eventId}")
                .setLiteral(null)
                .setEventTimestamp(false)
                .setUnicode(false)
                .setClob(false).build(),
	    ColumnConfig.newBuilder()
                .setName("UserId"
                .setPattern("%K{userId}")
                .setLiteral(null)
                .setEventTimestamp(false)
                .setUnicode(false)
                .setClob(false).build(),
	    ColumnConfig.newBuilder()
                .setName("LogType")
                .setPattern("%K{logType}")
                .setLiteral(null)
                .setEventTimestamp(false)
                .setUnicode(false)
                .setClob(false).build()
        };
 
	Appender jdbcAppender = JdbcAppender.newBuilder()
		.setBufferSize(0)
                .setColumnConfigs(columnConfigs)
                .setColumnMappings(new ColumnMapping[]{})
                .setConnectionSource(new Connect(dataSourceMSSqlServer))
                .setTableName("dhc.LogItems")
                .withName("databaseAppender")
                .withIgnoreExceptions(true)
                .withFilter(null)
                .build();
 
	jdbcAppender.start();
	config.addAppender(jdbcAppender);
 
	// Create an Appender reference.
	// @param ref The name of the Appender.
	// @param level The Level to filter against.
	// @param filter The filter(s) to use.
	// @return The name of the Appender.
	AppenderRef ref= AppenderRef.createAppenderRef("JDBC_Appender", null, null);
        AppenderRef[] refs = new AppenderRef[] {ref};
 
        /*
         * Factory method to create a LoggerConfig.
         *
         * @param additivity true if additive, false otherwise.
         * @param level The Level to be associated with the Logger.
         * @param loggerName The name of the Logger.
         * @param includeLocation whether location should be passed downstream
         * @param refs An array of Appender names.
         * @param properties Properties to pass to the Logger.
         * @param config The Configuration.
         * @param filter A Filter.
         * @return A new LoggerConfig.
         * @since 2.6
         */
        LoggerConfig loggerConfig = LoggerConfig.createLogger(
                false, Level.DEBUG, "JDBC_Logger", null, refs, null, config, null);        
        loggerConfig.addAppender(jdbcAppender, null, null);
 
        config.addLogger("JDBC_Logger", loggerConfig);       
        ctx.updateLoggers();  
 
        System.out.println("####### JDBCLog init() - DONE ########");  
 
    }
 
    public DataSource getDataSource() {
	return dataSourceMSSqlServer;
    }
 
    public void setDataSource(DataSource dataSourceMSSqlServer) {
	this.dataSourceMSSqlServer = dataSourceMSSqlServer;
    }	  
 
}

At this point, is possible to call the logger from the code in this way:

Logger jdbcLogger = LogManager.getContext(false).getLogger("JDBC_Logger"); 
jdbcLogger.info(new StringMapMessage()
    .with("eventId", AuditEventType.Logger_General.toString())
    .with("exception", "")
    .with("userId", "TESTUSER")
    .with("message", "TEST!!")
    .with("className", 
        this.getClass().getPackage().toString().replaceAll("package ", "") 
        + "." + this.getClass().getSimpleName() 
        + "." + new Object() {}.getClass().getEnclosingMethod().getName())
);

If you are using Log4j in a Servlet 2.5 web application, or if you have disabled auto-initialization with the isLog4jAutoInitializationDisabled context parameter, you must configure the Log4jServletContextListener and Log4jServletFilter in the deployment descriptor or programmatically. The filter should match all requests of any type. The listener should be the very first listener defined in your application, and the filter should be the very first filter defined and mapped in your application. This is easily accomplished using the following web.xml code:

<listener>
    <listener-class>
        org.apache.logging.log4j.web.Log4jServletContextListener
    </listener-class>
</listener>
 
<filter>
    <filter-name>log4jServletFilter</filter-name>
    <filter-class>
        org.apache.logging.log4j.web.Log4jServletFilter
    </filter-class>
</filter>
<filter-mapping>
    <filter-name>log4jServletFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
    <dispatcher>ERROR</dispatcher>
    <!-- 
        Servlet 3.0 w/ disabled auto-initialization only; 
        not supported in 2.5 
    -->
    <dispatcher>ASYNC</dispatcher>
</filter-mapping>

This dependency must be added to pom.xml:

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-web</artifactId>
    <version>2.10.0</version>
</dependency>

Of course, if you are not already included, add it to the component-scan into Spring application-context:

<context:component-scan base-package="com.afm.web.utility" />

PS: if you like it please, click on the banner :)

Parse an unknown JSON with Jquery

Sometime it happens to receive a JSON string than need to be visualized without knowing the structure. Suppose we have an HTML table:

<tbody id="reportTable">					
</tbody>

To populate this table with jQuery it’s possible to use this simple code:

var rows = ${reportRows};
var html = $.each(rows, function(key, value){
 
	$("#reportTable").append("<tr>");
 
	$.each(value, function (key, data) {
		$("#reportTable").append("<td>" + data + "</td>");
	});
 
	$("#reportTable").append("</tr>");
 
});

${reportRows} comes from a Spring MVC Controller and it’s a Java string generates using Jackson (or Gson) in this way:

ColumnMapRowMapper rowMapper = new ColumnMapRowMapper();
List<Map<String, Object>> reportDataList =  getJdbcTemplate().query(sqlComplete, rowMapper);
 
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
String json = ow.writeValueAsString(reportDataList);

So, we have a query that return a not know number of column (suppose your code dynamically generate the query), we translate the results in JSON and we use jQuery to render the results.

MongoDB query with logical and condition in Java

Suppose you need to apply some filters to your MongoDB query, for example to extract some _ids that match a regex condition. This is the way to do that:

Query query;
query.addCriteria(Criteria.where("_id").in(IDs).and(query_field).regex(".*" + query_value + ".*", "i"));

In this example I used the Query (see here) and Criteria (see here) classes

And, this is the query you can use in mongoDB (Robomongo* or command line):

Query: { 
	"_id" : { "$in" : [ "ID1" , "ID2" , "ID3" ]} , 
	"detail.medicationBrandName" : { "$regex" : ".*x.*" , "$options" : "i"}}, 
	Fields: null, Sort: { "medicationGenericName" : -1}
}

*Robomongo: is a shell-centric cross-platform open source MongoDB management tool (i.e. Admin GUI). Robomongo embeds the same JavaScript engine that powers MongoDB’s mongo shell. You can download it here.

Copyright © 2018 Andrea Girardi – It's my blog!

Theme by Anders NorenUp ↑