Skip to content
Advertisement

How to log errors in a EnvironmentPostProcessor execution

I have created an EnvironmentPostProcessor in SpringBoot to fetch properties from database and attached it to the Spring’s Environment as a PropertySource.

This is the code I have:

@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
    Map<String, Object> propertySource = new HashMap<>();
    // LOG SOMETHING HERE *******************
    logger.error("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
    String[] activeProfiles = environment.getActiveProfiles();
    String[] defaultProfiles = environment.getDefaultProfiles();

    // Do not pull db configuration when 'default' profile (used by Jenkins only) is run 
    if (activeProfiles.length == 0 && defaultProfiles[0] == "default") { 
        return;
    }

    // Load properties for Config schema
    String dataSourceUrl = environment.getProperty("service.datasource.url");
    String username = environment.getProperty("service.datasource.username");
    String password = environment.getProperty("service.datasource.password");
    String driver = environment.getProperty("service.datasource.driverClassName");

    try {
        // Build manually datasource to Config
        DataSource ds = DataSourceBuilder
                .create()
                .username(username)
                .password(password)
                .url(dataSourceUrl)
                .driverClassName(driver)
                .build();

        // Fetch all properties
        PreparedStatement preparedStatement = ds.getConnection().prepareStatement("SELECT name, value FROM propertyConfig WHERE service = ?");
        preparedStatement.setString(1, APP_NAME);

        ResultSet rs = preparedStatement.executeQuery();

        // Populate all properties into the property source
        while (rs.next()) {
            String propName = rs.getString("name");
            propertySource.put(propName, rs.getString("value"));
        }

        // Create a custom property source with the highest precedence and add it to Spring Environment 
        environment.getPropertySources().addFirst(new MapPropertySource(PROPERTY_SOURCE_NAME, propertySource));

    } catch (Exception e) {
        throw new Exception("Error fetching properties from ServiceConfig");
    }
}

And this is the main/META-INF/spring-factories file had to be created:

# Environment Post Processor
org.springframework.boot.env.EnvironmentPostProcessor=com.blabla.config.ReadDbPropertiesPostProcessor

The code works well, it fetches from the db what I need. However, I would like to log information about this in case something wrong occurs, for instance if db is down I want to log an error and stop the app to start. My app is configured to use logger and not the console.

I have tried logging the error, throwing exceptions, also printing out something but my log is never logging this information.

How can I do to use the logger during this early spring stage? Is it possible to do this in anyway? Am I using EnvironmentPostProcessor wrongly?

Advertisement

Answer

The problem here is that logging system initialized only after spring context is initialized. When the log method is invoked the log system does not know what to do with the information and it does nothing.

There is no elegant way to solve this issue. You either get rid of spring-managed log system or use deferred log mechanisms (just like spring does internally).

To be able to use DeferredLog you have to make sure that after context initialization the system will request to replay logs.

Here is one of the ways how it could be achieved:

@Component
public class MyEnvironmentPostProcessor implements
        EnvironmentPostProcessor, ApplicationListener<ApplicationEvent> {

    private static final DeferredLog log = new DeferredLog();

    @Override
    public void postProcessEnvironment(
            ConfigurableEnvironment env, SpringApplication app) {
        log.error("This should be printed");
    }

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        log.replayTo(MyEnvironmentPostProcessor.class);
    }
}

In this example every log message is cached in the DeferredLog. And once the context initialized the system will call onApplicationEvent. This method will replay all the cached log-events to the standard logger.

NOTE: I used ApplicationListener here but you can use every convenient way. The idea is to call DeferredLog.replayTo() once context initialized and it does not matter from which place you call it.

PS: The location of spring.factories should be src/main/resources/META-INF otherwise postProcessEnvironment might not be invoked.

User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement