21

There is already a question asking for logging the active configuration, there is a correct answer but the problem is that the configuration is logged only if all beans are correctly instantiated. I would like to log all properties even (mainly) if the application crash at startup. My question is more specific:

How to log all active properties of a spring boot application before the beans instantiation?

Ortomala Lokni
  • 56,620
  • 24
  • 188
  • 240

2 Answers2

35

For doing this you need to register an ApplicationListener. The event to catch is the ApplicationPreparedEvent, according to the documentation:

ApplicationPreparedEvent is an event published when a SpringApplication is starting up and the ApplicationContext is fully prepared but not refreshed. The bean definitions will be loaded and the Environment is ready for use at this stage.

The main method would look like this:

public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(MyApplication.class);
        springApplication.addListeners(new PropertiesLogger());
        springApplication.run(args);        
}

I've reused the code of the answer cited in the current question but I've modified it because the context you get is not already refreshed and the structure of the environment is not exactly the same as after the startup of the application. I've also printed the properties by property sources: one for the the system environment, one for the system properties, one for the application configuration properties, etc... Note also that the ApplicationPreparedEvent can be triggered multiple times, and that properties are printed only the first time. See Spring Boot issue #8899 for details.

package com.toto.myapp.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource;

import java.util.LinkedList;
import java.util.List;

public class PropertiesLogger implements ApplicationListener<ApplicationPreparedEvent> {
  private static final Logger log = LoggerFactory.getLogger(PropertiesLogger.class);

  private ConfigurableEnvironment environment;
  private boolean isFirstRun = true;

  @Override
  public void onApplicationEvent(ApplicationPreparedEvent event) {
    if (isFirstRun) {
      environment = event.getApplicationContext().getEnvironment();
      printProperties();
    }
    isFirstRun = false;
  }

  public void printProperties() {
    for (EnumerablePropertySource propertySource : findPropertiesPropertySources()) {
      log.info("******* " + propertySource.getName() + " *******");
      String[] propertyNames = propertySource.getPropertyNames();
      Arrays.sort(propertyNames);
      for (String propertyName : propertyNames) {
        String resolvedProperty = environment.getProperty(propertyName);
        String sourceProperty = propertySource.getProperty(propertyName).toString();
        if(resolvedProperty.equals(sourceProperty)) {
          log.info("{}={}", propertyName, resolvedProperty);
        }else {
          log.info("{}={} OVERRIDDEN to {}", propertyName, sourceProperty, resolvedProperty);
        }
      }
    }
  }

  private List<EnumerablePropertySource> findPropertiesPropertySources() {
    List<EnumerablePropertySource> propertiesPropertySources = new LinkedList<>();
    for (PropertySource<?> propertySource : environment.getPropertySources()) {
      if (propertySource instanceof EnumerablePropertySource) {
        propertiesPropertySources.add((EnumerablePropertySource) propertySource);
      }
    }
    return propertiesPropertySources;
  }
}
Ortomala Lokni
  • 56,620
  • 24
  • 188
  • 240
  • An important WARNING: The output of this as written logs the names of properties from various sources, but the value shown is the final value after overriding. I suggest replacing the main log.info with this block for clarity: – KC Baltz Jun 27 '18 at 18:18
  • See my previous comment: if(environment.getProperty(propertyName).equals(propertySource.getProperty(propertyName).toString())) { log.debug("{}={}", propertyName, environment.getProperty(propertyName)); } else { log.debug("{}={} OVERRIDEN to {}", propertyName, propertySource.getProperty(propertyName), environment.getProperty(propertyName)); } – KC Baltz Jun 27 '18 at 18:19
  • It was my intention to display the values of the properties after all have of them have been overwritten, because that are the values that will be used by the application. – Ortomala Lokni Jun 27 '18 at 18:50
  • I just found it confusing because it seems to show which values come from which files and it looked like the values I was setting weren't getting read correctly (as opposed to being overridden). I wasted a bunch of time checking my edits before I figured out it was showing the effective value. – KC Baltz Jun 27 '18 at 21:56
  • 1
    Ok, I understand the confusion, I've added your suggestion to the answer. Thanks for the commit ;) – Ortomala Lokni Jun 28 '18 at 07:30
  • @OrtomalaLokni Your solution works on embedded tomcat. On External tomcat the `ApplicationPreparedEvent does not works` any suggestion for making it work? I ran on external tomcat by doing the following three steps [1] Annotated the PropertiesLogger class as component and [2] `replaced ApplicationPreparedEvent by ApplicationReadyEvent` [3] Registered the PropertiesLogger bean in main class using ConfigurableApplicationContext – Akki Nov 25 '20 at 05:12
  • I believe this can be done earlier in the lifecycle by listening for `ApplicationEnvironmentPreparedEvent` instead, if desired. – M. Justin May 13 '21 at 05:25
  • PropertiesLogger can be much more concise if streams are used. – user18619318 Feb 15 '23 at 09:33
2

Show the Properties BEFORE application is ready

  • In my case, I needed to show the properties before the context is loaded. While debugging the app, I would like to log all the properties so that I know what's going on...

☕ Kotlin Implementation

As described at https://www.baeldung.com/spring-boot-environmentpostprocessor, the properties can be collected before the context is loaded through the use of EnvironmentPostProcessor, which is instantiated as part of Spring Factories from the call ConfigFileApplicationListener.loadPostProcessors(). At this point, you can collect all the properties and show in any specific way.

NOTE: While loading properties during this event, the context isn't ready. So, are the loggers. For this reason, the properties can be loaded before the App Banner (if any)

  • Also, the entry for the spring factory must be present, so create it first
org.springframework.boot.env.EnvironmentPostProcessor=\
cash.app.PropertiesLoggerEnvironmentPostProcessor
  • Then, create the logger
package cash.app

import org.springframework.boot.SpringApplication
import org.springframework.boot.env.EnvironmentPostProcessor
import org.springframework.core.Ordered
import org.springframework.core.annotation.Order
import org.springframework.core.env.ConfigurableEnvironment
import org.springframework.core.env.EnumerablePropertySource
import java.util.*

/**
 * This is to log the properties (config and system) before the app loads. This way, we know what will be loaded
 * on the app.
 * Note that we can't use the logger because the context hasn't built yet at the time it loads the properties twice.
 *
 * As an event consumer, the method ConfigFileApplicationListener.onApplicationEnvironmentPreparedEvent is called
 * while the context is building. The process is described at https://www.baeldung.com/spring-boot-environmentpostprocessor
 * and one important aspect is that this class is an EnvironmentPostProcessor, only loaded before the App is loaded
 * with the assistance of the "src/main/resources/META-INF/spring.factories". It is loaded by the
 * ConfigFileApplicationListener.loadPostProcessors(), which looks for the list of classses in the factories.
 *
 * https://www.baeldung.com/spring-boot-environmentpostprocessor explains how to create AutoConfiguration classes for
 * shared libraries. For the case of config, the reload of properties is detailed and explained on the docs at
 * https://www.baeldung.com/spring-reloading-properties
 *
 * TODO: We need to hide the secrets, if they are defined here.
 *
 * @author Marcello.DeSales@gmail.com
 */
@Order(Ordered.LOWEST_PRECEDENCE)
class PropertiesLoggerEnvironmentPostProcessor : EnvironmentPostProcessor {

    companion object {
        /**
         * Sharing is started immediately and never stops.
         */
        private var numberOfPasses: Int = 0
        private var systemProperties: MutableMap<String, String> = mutableMapOf()
    }

    override fun postProcessEnvironment(environment: ConfigurableEnvironment, application: SpringApplication) {
        for (propertySource in findPropertiesPropertySources(environment)) {
            // Avoid printing the systemProperties twice
            if (propertySource.name.equals("systemProperties")) {
                numberOfPasses = numberOfPasses?.inc()

            } else {
                System.out.println("******* \" + ${propertySource.getName()} + \" *******" )
            }

            // Adaptation of https://stackoverflow.com/questions/48212761/how-to-log-all-active-properties-of-a-spring-boot-application-before-the-beans-i/48212783#48212783
            val propertyNames = propertySource.propertyNames
            Arrays.sort(propertyNames)
            for (propertyName in propertyNames) {
                val resolvedProperty = environment!!.getProperty(propertyName!!)
                val sourceProperty = propertySource.getProperty(propertyName).toString()

                if (resolvedProperty == sourceProperty) {
                    if (propertySource.name.equals("systemProperties")) {
                        systemProperties.put(propertyName, resolvedProperty)
                    } else {
                        System.out.println( "${propertyName}=${resolvedProperty}" )
                    }

                } else {
                    if (propertySource.name.equals("systemProperties")) {
                        systemProperties.put(propertyName, resolvedProperty ?: "")

                    } else {
                        System.out.println( "${propertyName}=${sourceProperty} ----- OVERRIDDEN =>>>>>> ${propertyName}=${resolvedProperty}" )
                    }
                }
            }
        }

        // The system properties show up twice in the process... The class is called twice and we only print it in the end.
        if (numberOfPasses == 2) {
            System.out.println("******* \" System Properties \" *******")
            val sysPropertyNames = systemProperties.keys.sorted()
            for (sysPropertyName in sysPropertyNames) {
                val sysPropertyValue = systemProperties!!.get(sysPropertyName!!)
                System.out.println( "${sysPropertyName}=${sysPropertyValue}" )
            }
        }
    }

    private fun findPropertiesPropertySources(environment: ConfigurableEnvironment): List<EnumerablePropertySource<*>> {
        val propertiesPropertySources: MutableList<EnumerablePropertySource<*>> = LinkedList()
        for (propertySource in environment!!.propertySources) {
            if (propertySource is EnumerablePropertySource<*>) {
                if (propertySource.name.equals("systemProperties") || propertySource.name.contains("applicationConfig:")) {
                    propertiesPropertySources.add(propertySource)
                }
            }
        }

        return propertiesPropertySources.asReversed()
    }
}

Example Logs

  • Here's the loggers during the bootstrap of one of my services
/Users/marcellodesales/.gradle/jdks/jdk-14.0.2+12/Contents/Home/bin/java -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always 
....
....
2022-02-22T21:24:39  INFO [app=springAppName_IS_UNDEFINED,prof=observed,db,ppd_dev][tid=,sid=,sxp=][uid=] 74720 --- [  restartedMain] o.s.b.devtools.restart.ChangeableUrls    : The Class-Path manifest attribute in /Users/marcellodesales/.gradle/caches/modules-2/files-2.1/com.sun.xml.bind/jaxb-core/2.2.7s-codec-1.11.jar
2022-02-22T21:24:39  INFO [app=springAppName_IS_UNDEFINED,prof=observed,db,ppd_dev][tid=,sid=,sxp=][uid=] 74720 --- [  restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
******* " + applicationConfig: [classpath:/application.yaml] + " *******

management.endpoint.health.show-details=always
management.endpoints.web.base-path=/actuator ==========>>>>>> OVERRIDDEN =========>>>>>> management.endpoints.web.base-path=/orchestrator/actuator
management.endpoints.web.exposure.include=*
management.metrics.web.server.request.autotime.enabled=true
spring.application.name=orchestrator-service
spring.boot.admin.client.enabled=false ==========>>>>>> OVERRIDDEN =========>>>>>> spring.boot.admin.client.enabled=true
spring.cloud.discovery.client.composite-indicator.enabled=false

spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
******* " + applicationConfig: [classpath:/application-ppd_dev.yaml] + " *******
spring.datasource.url=jdbc:postgresql://localhost:6433/supercash?createDatabaseIfNotExist=true ==========>>>>>> OVERRIDDEN 

=========>>>>>> spring.datasource.url=jdbc:postgresql://localhost:6433/supercash?createDatabaseIfNotExist\=true
spring.devtools.livereload.enabled=true
spring.mail.properties.mail.smtp.starttls.required=true
spring.mail.test-connection=true

******* " System Properties " *******
LOG_LEVEL_PATTERN=%5p [,%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]
PID=74720
com.sun.management.jmxremote=
file.encoding=UTF-8
ftion
java.vm.specification.version=14
java.vm.vendor=AdoptOpenJDK
java.vm.version=14.0.2+12
jboss.modules.system.pkgs=com.intellij.rt
jdk.debug=release
line.separator=

os.arch=x86_64
os.name=Mac OS X
os.version=10.16

user.name=marcellodesales
user.timezone=America/Los_Angeles

2022-02-22T21:25:16 DEBUG [app=orchestrator-service,prof=observed,db,ppd_dev][tid=,sid=,sxp=][uid=] 74720 --- [  restartedMain] o.s.b.c.c.ConfigFileApplicationListener  : Activated activeProfiles observed,db,ppd_dev

   _____                        _____          _     
  / ____|                      / ____|        | |    
 | (___  _   _ _ __   ___ _ __| |     __ _ ___| |__  


2022-02-22T20:41:08  INFO [app=orchestrator-service,prof=observed,db,ppd_dev][tid=,sid=,sxp=][uid=] 74181 --- [  restartedMain] 
Marcello DeSales
  • 21,361
  • 14
  • 77
  • 80