Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 613
Ortomala Lokni Avatar asked Jan 11 '18 17:01

Ortomala Lokni


People also ask

How do you read all properties from application properties in spring boot?

Another very simple way to read application properties is to use @Value annotation. Simply annotation the class field with @Value annotation providing the name of the property you want to read from application. properties file and class field variable will be assigned that value.

How do I check spring boot properties?

To see all properties in your Spring Boot application, enable the Actuator endpoint called env . This enables an HTTP endpoint which shows all the properties of your application's environment. You can do this by setting the property management. endpoints.

What spring boot property is used to set the logging level in application properties?

hibernate = DEBUG will set logging level for classes of Spring framework web and Hibernate only.

Can you control logging with spring boot How?

React Full Stack Web Development With Spring BootSpring Boot uses Apache Commons logging for all internal logging. Spring Boot's default configurations provides a support for the use of Java Util Logging, Log4j2, and Logback. Using these, we can configure the console logging as well as file logging.


2 Answers

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;
  }
}
like image 87
Ortomala Lokni Avatar answered Oct 12 '22 09:10

Ortomala Lokni


📝 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 [email protected]
 */
@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] 
like image 35
Marcello de Sales Avatar answered Oct 12 '22 10:10

Marcello de Sales