【问题标题】:How to log the active configuration in a Spring Boot application?如何在 Spring Boot 应用程序中记录活动配置?
【发布时间】:2014-08-18 09:27:18
【问题描述】:

我真的很想为 Spring Boot 使用 YAML 配置,因为我发现有一个文件显示在我的不同配置文件中哪些属性处于活动状态是非常可读和有用的。不幸的是,我发现在application.yml 中设置属性可能相当脆弱。

使用制表符而不是空格之类的操作会导致属性不存在(据我所知,没有警告),而且我经常发现我的活动配置文件没有被设置,因为我的一些未知问题YAML。

所以我想知道是否有任何挂钩可以让我掌握当前活动的配置文件和属性,以便我可以记录它们。

同样,如果application.yml 包含错误,有没有办法导致启动失败?要么是这样,要么是我自己验证 YAML 的一种方法,这样我就可以终止启动过程。

【问题讨论】:

    标签: java spring spring-boot kotlin yaml


    【解决方案1】:

    除了其他答案:在上下文刷新事件上记录活动属性。

    Java 8

    package mypackage;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.context.event.ContextRefreshedEvent;
    import org.springframework.context.event.EventListener;
    import org.springframework.core.env.ConfigurableEnvironment;
    import org.springframework.core.env.MapPropertySource;
    import org.springframework.stereotype.Component;
    
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.List;
    
    @Slf4j
    @Component
    public class AppContextEventListener {
    
        @EventListener
        public void handleContextRefreshed(ContextRefreshedEvent event) {
            printActiveProperties((ConfigurableEnvironment) event.getApplicationContext().getEnvironment());
        }
    
        private void printActiveProperties(ConfigurableEnvironment env) {
    
            System.out.println("************************* ACTIVE APP PROPERTIES ******************************");
    
            List<MapPropertySource> propertySources = new ArrayList<>();
    
            env.getPropertySources().forEach(it -> {
                if (it instanceof MapPropertySource && it.getName().contains("applicationConfig")) {
                    propertySources.add((MapPropertySource) it);
                }
            });
    
            propertySources.stream()
                    .map(propertySource -> propertySource.getSource().keySet())
                    .flatMap(Collection::stream)
                    .distinct()
                    .sorted()
                    .forEach(key -> {
                        try {
                            System.out.println(key + "=" + env.getProperty(key));
                        } catch (Exception e) {
                            log.warn("{} -> {}", key, e.getMessage());
                        }
                    });
            System.out.println("******************************************************************************");
        }
    }
    

    科特林

    package mypackage
    
    import mu.KLogging
    import org.springframework.context.event.ContextRefreshedEvent
    import org.springframework.context.event.EventListener
    import org.springframework.core.env.ConfigurableEnvironment
    import org.springframework.core.env.MapPropertySource
    import org.springframework.stereotype.Component
    
    @Component
    class AppContextEventListener {
    
        companion object : KLogging()
    
        @EventListener
        fun handleContextRefreshed(event: ContextRefreshedEvent) {
            printActiveProperties(event.applicationContext.environment as ConfigurableEnvironment)
        }
    
        fun printActiveProperties(env: ConfigurableEnvironment) {
            println("************************* ACTIVE APP PROPERTIES ******************************")
            env.propertySources
                    .filter { it.name.contains("applicationConfig") }
                    .map { it as EnumerablePropertySource<*> }
                    .map { it -> it.propertyNames.toList() }
                    .flatMap { it }
                    .distinctBy { it }
                    .sortedBy { it }
                    .forEach { it ->
                        try {
                            println("$it=${env.getProperty(it)}")
                        } catch (e: Exception) {
                            logger.warn("$it -> ${e.message}")
                        }
                    }
            println("******************************************************************************")
        }
    }
    

    输出如下:

    ************************* ACTIVE APP PROPERTIES ******************************
    server.port=3000
    spring.application.name=my-app
    ...
    2017-12-29 13:13:32.843  WARN 36252 --- [           main] m.AppContextEventListener        : spring.boot.admin.client.service-url -> Could not resolve placeholder 'management.address' in value "http://${management.address}:${server.port}"
    ...
    spring.datasource.password=
    spring.datasource.url=jdbc:postgresql://localhost/my_db?currentSchema=public
    spring.datasource.username=db_user
    ...
    ******************************************************************************
    

    【讨论】:

    • 这些解决方案都不适合我使用@ConfigurationProperties 获得任何东西
    • @berlinguyinca 嗨!你用的是什么版本的spring boot?你为什么要谈论@ConfigurationProperties?此方法用于在运行时从合并的application.yml &lt;- application-some-profile.yml &lt;- etcsame with application.properties 文件中显示应用的配置属性。而且我不明白它是如何连接到@ConfigurationProperties。
    • 2.x 版,我们基本上希望看到所有已定义的 ConfigurationPropertie 注释的默认值。
    • @berlinguyinca 刚刚在 Spring Boot 2.1.5.RELEASE 项目上尝试过 - 按预期工作¯_(ツ)_/¯
    • 是的,问题是,它不打印默认值,只打印在 yaml 文件中明确设置或在命令行上设置的值。但我想打印所有可能的配置属性。包括默认值,而不仅仅是明确指定的。
    【解决方案2】:

    如果您想在初始化 bean/应用程序之前获取活动配置文件,我发现的唯一方法是在 SpringBootServletInitializer/SpringApplication 中注册自定义横幅(即 JHipster 应用程序中的 ApplicationWebXml)。

    例如

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder)
    {
        // set a default to use when no profile is configured.
        DefaultProfileUtil.addDefaultProfile(builder.application());
        return builder.sources(MyApp.class).banner(this::printBanner);
    }
    
    /** Custom 'banner' to obtain early access to the Spring configuration to validate and debug it. */
    private void printBanner(Environment env, Class<?> sourceClass, PrintStream out)
    {
        if (env.getProperty("spring.datasource.url") == null)
        {
            throw new RuntimeException(
                "'spring.datasource.url' is not configured! Check your configuration files and the value of 'spring.profiles.active' in your launcher.");
        }
        ...
    }
    

    【讨论】:

    • 编辑了我的回复并添加了示例代码。如果您想知道,对于我的应用程序,我不会枚举那里的所有属性,只需检查关键属性即可。但是您在 Banner 中收到的 Environment 是一个 ConfigurableEnvironment,因此您可以遍历 getPropertySources() 并从实现 EnumerablePropertySource 的源中枚举。
    【解决方案3】:

    Actuator /env 服务显示属性,但不显示哪个属性值实际处于活动状态。很多时候,您可能希望使用

    覆盖您的应用程序属性
    • 配置文件特定的应用程序属性
    • 命令行参数
    • 操作系统环境变量

    因此,您将在多个来源中拥有相同的属性和不同的值。

    下面的片段在启动时打印活动的应用程序属性值:

    @Configuration
    public class PropertiesLogger {
        private static final Logger log = LoggerFactory.getLogger(PropertiesLogger.class);
    
        @Autowired
        private AbstractEnvironment environment;
    
        @PostConstruct
        public void printProperties() {
    
            log.info("**** APPLICATION PROPERTIES SOURCES ****");
    
            Set<String> properties = new TreeSet<>();
            for (PropertiesPropertySource p : findPropertiesPropertySources()) {
                log.info(p.toString());
                properties.addAll(Arrays.asList(p.getPropertyNames()));
            }
    
            log.info("**** APPLICATION PROPERTIES VALUES ****");
            print(properties);
    
        }
    
        private List<PropertiesPropertySource> findPropertiesPropertySources() {
            List<PropertiesPropertySource> propertiesPropertySources = new LinkedList<>();
            for (PropertySource<?> propertySource : environment.getPropertySources()) {
                if (propertySource instanceof PropertiesPropertySource) {
                    propertiesPropertySources.add((PropertiesPropertySource) propertySource);
                }
            }
            return propertiesPropertySources;
        }
    
        private void print(Set<String> properties) {
            for (String propertyName : properties) {
                log.info("{}={}", propertyName, environment.getProperty(propertyName));
            }
        }
    
    }
    

    【讨论】:

    • 这对我来说什么也没打印出来。
    • 什么都没有?您的意思是在@PostConstruct 上甚至没有打印“**** APPLICATION PROPERTIES SOURCES ****”?首先,我要确保在您的应用程序中完全创建了 PropertiesLogger 对象。也许对@EnableAutoConfiguration 进行一些挖掘可能会有所帮助。
    • 我的意思是它打印出“**** APPLICATION PROPERTIES SOURCES ****”,然后什么都没有,然后是“**** APPLICATION PROPERTIES VALUES ****”,然后什么都没有。跨度>
    • 有效,但这样做的一个问题是这仅在构建所有 bean 之后才有效。如果某些 bean 在构造时抛出异常,则属性日志不可用。
    • @DanielHári 我已经就这个问题提出并回答了一个问题:stackoverflow.com/questions/48212761/…
    【解决方案4】:

    我遇到了同样的问题,希望有一个调试标志可以告诉配置文件处理系统吐出一些有用的日志记录。一种可能的方法是为您的应用程序上下文注册一个事件侦听器,并从环境中打印出配置文件。我自己没有尝试过这样做,所以你的里程可能会有所不同。我认为可能类似于此处概述的内容:

    How to add a hook to the application context initialization event?

    然后你会在你的监听器中做这样的事情:

    System.out.println("Active profiles: " + Arrays.toString(ctxt.getEnvironment().getActiveProfiles()));
    

    可能值得一试。您可能会这样做的另一种方法是在需要打印配置文件的代码中声明要注入的环境。即:

    @Component
    public class SomeClass {
      @Autowired
      private Environment env;
      ...
      private void dumpProfiles() {
        // Print whatever needed from env here
      }
    }
    

    【讨论】:

    • 我采用了记录getEnvironment().getActiveProfiles()的结果的方法,作为我的应用程序main方法的启动记录的一部分。
    • 现在看来 spring 正在默认这样做:INFO 22067 --- [ main] com.example.MainApplication : The following profiles are active: dev
    【解决方案5】:

    如果application.yml 包含错误,则会导致启动失败。我想这取决于你所说的“错误”是什么意思。如果 YAML 格式不正确,它肯定会失败。此外,如果您正在设置标记为ignoreInvalidFields=true@ConfigurationProperties,或者如果您设置了一个无法转换的值。这是一个相当广泛的错误。

    Environment 实现可能会在启动时记录活动配置文件(但无论如何,您很容易抓住它并将其记录在启动器代码中 - EnvironmenttoString() 将列出我认为活跃的个人资料)。如果添加 Actuator,则 /env 端点中也可以使用活动配置文件(以及更多)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-12-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-09
      • 1970-01-01
      • 2016-08-26
      相关资源
      最近更新 更多