Search code examples
springspring-bootlog4j2

pb Logging in db with spring boot 3 and log4j2


I try to save in my db the logs of the console for querying easily on it. It's almost working. Log is ok but I have error log before the end of the spring boot context initialisation. After it's ok.

The errors are it try to write on database before the connection is ok (after spring init because datasources variables are in the application.yml.

I would like maybe block the write of logs before end of init, or load the log4j2-spring file after init ?

Here are my files.

Log4j2-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>

    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout
                    pattern="%d{yyyy-MMM-dd hh:mm:ss a} %level %c - %m %n" />
        </Console>
        <JDBC name="dbLogsStore" tableName="EVENT_LOGS">

            <ConnectionFactory
                    class="com.toto.configuration.log.LogsStoreConnectionFactory"
                    method="getConnection" />
            <Column name="ID" pattern="%u" isUnicode="false" />
            <Column name="DATE_TIME" isEventTimestamp="true" />
            <Column name="CLASS" pattern="%logger" isUnicode="false"  />
            <Column name="LEVEL" pattern="%level" isUnicode="false" />
            <Column name="MESSAGE" pattern="%message" isUnicode="false" />
            <Column name="EXCEPTION" pattern="%ex{full}" isUnicode="false"/>
        </JDBC>
    </Appenders>

    <Loggers>
        <Root level="INFO">
            <AppenderRef ref="Console" />
            <AppenderRef ref="dbLogsStore" />
        </Root>
    </Loggers>

</Configuration>

Here is datasource init files

Configuration
public class LogsStoreConnectionConfig {

    @Value("${spring.datasource.v3.url}")
    private String dbUrl;

    @Value("${spring.datasource.v3.username}")
    private String dbUsername;

    @Value("${spring.datasource.v3.password}")
    private String dbPassword;

    @Value("${spring.datasource.v3.driver-class-name}")
    private String dbDriverClassName;


    @PostConstruct
    public void configureLog4j2() {
        LogsStoreConnectionFactory.init(dbUrl, dbUsername, dbPassword, dbDriverClassName);

    }

And finally the factory :

public class LogsStoreConnectionFactory {
    private static BasicDataSource dataSource;

    private LogsStoreConnectionFactory() {

    }

    public static void init(String dbUrl, String dbUsername, String dbPassword, String dbDriverClassName) {
        if (dataSource == null) {
            dataSource = new BasicDataSource();
            dataSource.setUrl(dbUrl);
            dataSource.setDriverClassName(dbDriverClassName);
            dataSource.setUsername(dbUsername);
            dataSource.setPassword(dbPassword);
        }
    }

    public static synchronized boolean isInitialized() {
        return dataSource != null;
    }

    public static Connection getConnection() throws SQLException {
        if (dataSource == null) {
            return null; // Retourner null au lieu de lancer une exception
        }

        return dataSource.getConnection();
    }

I try a lot of things ! Thanks if you can help !


Solution

  • Spring Boot initializes Log4j very early in the startup process, when it receives the ApplicationEnvironmentPreparedEvent. Your beans are initialized later and a ready after the ApplicationStartedEvent (see Application Events and Listeners).

    Although you can not rely on your beans being ready, you can rely on a working Environment Properties Lookup so you can use the DBCP2-based PoolingDriver connection source to initialize the connection:

    <PoolingDriver connectionString="${spring:spring.datasource.v3.url}"
                   driverClassName="${spring:spring.datasource.v3.driver-class-name}"
                   userName="${spring:spring.datasource.v3.username}"
                   password="${spring:spring.datasource.v3.password}"
                   poolName="logging"/>
    

    Warning: For the general public's sake it is worth noting that the ${spring:...} lookup works, since the OP is using a log4j2-spring.xml configuration file. A log4j2.xml file is used even earlier in the startup sequence, so it can not rely on the lookup. In that file one can still use arbiters like the SpringProfile arbiter to provide an alternative appender for the pre-ApplicationEnvironmentPreparedEvent period.