0

I have already defined a datasource in Spring configuration.

<bean id="dataSource"
    class="org.springframework.jdbc.datasource.DriverManagerDataSource">

    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/db" />
    <property name="username" value="root" />
    <property name="password" value="password" />
</bean>

For LOG4J2 JDBCAppender, I know we need to obtain the datasource:

 <JDBC name="databaseAppender" tableName="error_log">
        <ConnectionFactory class="net.example.db"
            method="getDatabaseConnection" />
        <Column name="EVENT_DATE" isEventTimestamp="true" />
        <Column name="LEVEL" pattern="%level" />
        <Column name="LOGGER" pattern="%logger" />
        <Column name="MESSAGE" pattern="%message" />
        <Column name="THROWABLE" pattern="%ex{full}" />
    </JDBC>

I have already added the package into the component-scan:

<context:component-scan base-package="com.test,net.example.db" />

And add annotation at the class ConnectionFactory as a Component

import java.sql.Connection;
import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ConnectionFactory {

    @Autowired
    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public Connection getDatabaseConnection() throws Exception {
        return dataSource.getConnection();
    }

}

However, I am getting null when call getDatabaseConnection()

2017-01-14 14:17:24,579 localhost-startStop-1 ERROR JdbcDatabaseManager jdbcManager{ description=databaseAppender, bufferSize=0, connectionSource=factory{ public static java.sql.Connection net.example.db.ConnectionFactory.getDatabaseConnection() }, tableName=error_log, columns=[ { name=EVENT_DATE, layout=null, literal=null, timestamp=true }, { name=LEVEL, layout=%level, literal=null, timestamp=false }, { name=LOGGER, layout=%logger, literal=null, timestamp=false }, { name=MESSAGE, layout=%message, literal=null, timestamp=false }, { name=THROWABLE, layout=%ex{full}, literal=null, timestamp=false } ] } Could not perform database startup operations: java.sql.SQLException: Failed to obtain connection from factory method. java.sql.SQLException: Failed to obtain connection from factory method.
    at org.apache.logging.log4j.core.appender.db.jdbc.FactoryMethodConnectionSource$1.getConnection(FactoryMethodConnectionSource.java:107)
    at org.apache.logging.log4j.core.appender.db.jdbc.FactoryMethodConnectionSource.getConnection(FactoryMethodConnectionSource.java:53)
    at org.apache.logging.log4j.core.appender.db.jdbc.JdbcDatabaseManager.startupInternal(JdbcDatabaseManager.java:60)
    at org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager.startup(AbstractDatabaseManager.java:65)
    at org.apache.logging.log4j.core.appender.db.AbstractDatabaseAppender.start(AbstractDatabaseAppender.java:89)
    at org.apache.logging.log4j.core.config.AbstractConfiguration.start(AbstractConfiguration.java:255)
    at org.apache.logging.log4j.core.LoggerContext.setConfiguration(LoggerContext.java:530)
    at org.apache.logging.log4j.core.LoggerContext.start(LoggerContext.java:258)
    at org.apache.logging.log4j.core.impl.Log4jContextFactory.getContext(Log4jContextFactory.java:239)
    at org.apache.logging.log4j.core.config.Configurator.initialize(Configurator.java:158)
    at org.apache.logging.log4j.web.Log4jWebInitializerImpl.initializeNonJndi(Log4jWebInitializerImpl.java:168)
    at org.apache.logging.log4j.web.Log4jWebInitializerImpl.start(Log4jWebInitializerImpl.java:110)
    at org.apache.logging.log4j.web.Log4jServletContainerInitializer.onStartup(Log4jServletContainerInitializer.java:57)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5604)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:147)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1571)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1561)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.NullPointerException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.apache.logging.log4j.core.appender.db.jdbc.FactoryMethodConnectionSource$1.getConnection(FactoryMethodConnectionSource.java:105)
    ... 20 more

I am skeptical that whether the bean dataSource has not yet created when LOGJ2 calls getDatabaseConnection(). Please let me know what would be the reason and solution.

user2051823
  • 177
  • 1
  • 7
  • 23
  • why not?? yes you can provided that connection factory class is part of component scanning and you create a bean of it. – Barath Jan 11 '17 at 17:34
  • hi Barath, please see my update in the post. I have already implemented the points you mentioned, but I am getting null for dataSource. – user2051823 Jan 12 '17 at 13:34
  • what is the package name for ConnectionFactory . ensure it is part of component scan base packages – Barath Jan 13 '17 at 07:18
  • I have already added the package name in component scan: the package name is net.example.db as stated in the question. – user2051823 Jan 14 '17 at 05:33
  • can you make sure Datasource is of type javax.sql.DataSource and tell me how you are invoking getDatabaseConnection() ?? – Barath Jan 14 '17 at 05:55
  • I checked the datasource belongs to the type javax.sql.DataSource, I have updated my post for the java class, the method was invoked by LOG4J2, I have attached the exception when I start the web app. Thanks! – user2051823 Jan 14 '17 at 07:06
  • 1
    Log4J doesn't get the ConnectionFactory from the Spring context. So Spring is unaware of this object, and doesn't inject anything in it. BTW, even if log4j used the spring context, your connection factory is in the package `hk.gov.lcsd.mois.logging`, and you're only scanning the packages `com.test,net.example.db`. And finally, you missed an important part of the documentation: *Whichever approach you take, it must be backed by a connection pool. Otherwise, logging performance will suffer greatly*. – JB Nizet Jan 14 '17 at 07:38
  • Thanks for your reply, I have made my datasource to be backed by a connection pool using com.mchange.v2.c3p0.ComboPooledDataSource, and the package path is actually correct for net.example.db. I mixed the configuration with my another project, sorry for the confusion. I just updated in my post. Could you please tell me more how can I let log4j using the spring context so that it can obtain the datasource from Spring context? – user2051823 Jan 14 '17 at 09:32

2 Answers2

4

Please go through this tutorial [ Log4j2 JDBC ] (http://self-learning-java-tutorial.blogspot.in/2015/10/log4j2-jdbcappender-write-log-messages.html)

As mentioned : please see this part

Following are the ConnectionFactory Parameters.

Class : Name of the class, that contains a static factory method for obtaining JDBC connections.
method : The name of a static factory method for obtaining JDBC connections. Method return type must be either java.sql.Connection or DataSource.

Please note that it requires a static factory method. thats why invocation of reflection fails.

Barath
  • 5,093
  • 1
  • 17
  • 42
  • Thanks, but can the ConnectionFactory obtain the datasource from spring bean? – user2051823 Jan 14 '17 at 09:34
  • I am not sure. Can you add constructor and print it out ? or else use @PostContruct public void init { sop(" data source "+dataSource) } to identity whether bean is created or not – Barath Jan 14 '17 at 09:36
  • 1
    Thanks Barath, I followed the answer from another post, the problem is solved now – user2051823 Jan 16 '17 at 06:46
3

Finally I found one solution to my answer. LOG4J is initialized before Spring, therefore the bean Datasource will return null.

I followed the instructions from below URL: How to use Spring BoneCPDataSource bean as data source for Log4j 2 JDBC appender?

And everything is fine now.

Community
  • 1
  • 1
user2051823
  • 177
  • 1
  • 7
  • 23