Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Log @ConfigurationProperties in SpringBoot prior to startup?

I'm trying to find a way to log my application properties (injected into my beans via @ConfigurationProperties) prior to the context startup so that I can see what the exact properties are before all the beans are initialized.

I've tried to create a Listener on an ApplicationEnvironmentPreparedEvent, but there is no way to retrieve all the properties defined short of retrieving individual properties one at a time.

Is there an easy way to do this? Some way to initialize @ConfigurationProperties first and log their contents, or retrieve all the application startup properties prior to the context creation?

like image 526
Eric B. Avatar asked Oct 12 '15 02:10

Eric B.


2 Answers

You can see application properties simply with the help of a custom class implementing the ApplicationListener and defining it as one of the startupup class in spring-factories entry so they will execute before the application load. Steps are as follows :-

a) Create a file called spring.factories in resource classpath i.e. src\main\resources\META-INF\spring.factories with content -

# Application Listeners
org.springframework.context.ApplicationListener=demo.CustomConfigListener

b) Create a custom listener class in your proj like here CustomConfigListener

package demo;

import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource;


public class CustomConfigListener implements ApplicationListener<ApplicationEvent> {

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationEnvironmentPreparedEvent) {
            for(PropertySource<?> source : ((ApplicationEnvironmentPreparedEvent) event).getEnvironment().getPropertySources()){
                if(source.getName().equals("applicationConfigurationProperties")){              
                    if (source instanceof EnumerablePropertySource) {
                        for(String name : ((EnumerablePropertySource) source).getPropertyNames()){
                            System.out.println(name+" :: "+ ((EnumerablePropertySource) source).getProperty(name));
                        }
                    }
                }
            }           
        }       
    }

}

c) Your custom ConfigurationProperties class

package demo;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(ignoreUnknownFields = false, prefix = "mail")
public class MailProperties {    
    private String host;
    private int port;
    private Smtp smtp;    
    //Getters & Setters

    public static class Smtp {    
        private boolean auth;
        private boolean starttlsEnable;    
        //Getters & Setters
    }
}

d) Finally your application.properties

mail.host=localhost
mail.port=25
mail.smtp.auth=false
mail.smtp.starttls-enable=false
like image 119
Avis Avatar answered Oct 25 '22 03:10

Avis


As a followup to @Avis' answer, I realized that the snippet didn't include any command line args, etc, so I have updated his concept a little. I am attaching my logger class in case it has value for anyone who lands on this question in the future.

public class ConfigurationLogger implements ApplicationListener<ApplicationEvent> {
    // slf4j logger
    private static final Logger logger = LoggerFactory.getLogger(ConfigurationLogger.class);

    // used to sanitize any password sensitive keys (copied from Spring Boot's Sanitizer() class
    private Sanitizer sanitizer = new Sanitizer();

    // store the config keys in a sorted map
    private Map<String, Object> configurationProperties = new TreeMap<>();


    /**
     * Trigger upon all events during startup.  Both ApplicatoinEnvironmentPrepareEvent and
     * ApplicationPreparedEvent need access to the same configurationProperties object.  Could
     * have done this through separate events, both extending an abstract base class with a static
     * hash map, but not worth the effort.  Instead have the same class listen for all events, and 
     * delegate to the appropriate method.
     */
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationEnvironmentPreparedEvent) {
            // store the values
            onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
        }else if( event instanceof ApplicationPreparedEvent){
            // display the values
            logConfigurationProperties( (ApplicationPreparedEvent)event);
        }
    }

    /**
     * Store the properties in the hash map for logging once all property sources have been read
     * 
     * @param event
     */
    private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
        for (PropertySource<?> source : event.getEnvironment().getPropertySources()) {
            if (source instanceof EnumerablePropertySource) {
                for (String key : ((EnumerablePropertySource) source).getPropertyNames()) {
                    Object value = ((EnumerablePropertySource) source).getProperty(key);
                    if (!configurationProperties.containsKey(key)) {
                        configurationProperties.put(key, sanitizer.sanitize(key, value));
                    }
                }
            }
        }
    }

    /**
     * Print all the config properties to the logger
     */
    private void logConfigurationProperties( ApplicationPreparedEvent event) {
        logger.debug("Application started with following parameters: ");
        for( Map.Entry<String, Object> entry : configurationProperties.entrySet()){
            logger.debug("{} :: {}", entry.getKey(), entry.getValue());
        }

    }
}

And the listener is initialized in the SpringApplication main class:

@SpringBootApplication
public class Application{
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        app.setShowBanner(false);
        // add configuration properties logger
        app.addListeners(new ConfigurationLogger());
        app.run(args);
    }
}
like image 34
Eric B. Avatar answered Oct 25 '22 05:10

Eric B.