From c366908db2c35482980faa3f3f6d6de5bfdafbcc Mon Sep 17 00:00:00 2001 From: huanghu Date: Wed, 3 Dec 2025 19:30:46 +0800 Subject: [PATCH] CN.1875084792839892:9376a8f5cdbd84c02ee5939b99566c24_693016fa3893af23b22a8404.693017423893af23b22a8408.693017429cfd2bfc0de4d707:Trae CN.T(2025/12/3 18:56:02) --- ...onfigurationSnapshotAutoConfiguration.java | 39 ++++++ .../ConfigurationSnapshotListener.java | 128 ++++++++++++++++++ .../ConfigurationSnapshotProperties.java | 74 ++++++++++ ...itional-spring-configuration-metadata.json | 18 +++ .../main/resources/META-INF/spring.factories | 4 + ...urationSnapshotAutoConfigurationTests.java | 74 ++++++++++ 6 files changed, 337 insertions(+) create mode 100644 core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/ConfigurationSnapshotAutoConfiguration.java create mode 100644 core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/ConfigurationSnapshotListener.java create mode 100644 core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/ConfigurationSnapshotProperties.java create mode 100644 core/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/context/ConfigurationSnapshotAutoConfigurationTests.java diff --git a/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/ConfigurationSnapshotAutoConfiguration.java b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/ConfigurationSnapshotAutoConfiguration.java new file mode 100644 index 00000000000..7387b4263cd --- /dev/null +++ b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/ConfigurationSnapshotAutoConfiguration.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.context; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +/** + * Auto-configuration for configuration snapshot logging. + * + * @author Your Name + * @since 3.2.0 + */ +@AutoConfiguration +@ConditionalOnProperty(prefix = "spring.config-snapshot", name = "enabled", havingValue = "true") +@EnableConfigurationProperties(ConfigurationSnapshotProperties.class) +public class ConfigurationSnapshotAutoConfiguration { + + @Bean + public ConfigurationSnapshotListener configurationSnapshotListener(ConfigurationSnapshotProperties properties) { + return new ConfigurationSnapshotListener(properties); + } +} diff --git a/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/ConfigurationSnapshotListener.java b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/ConfigurationSnapshotListener.java new file mode 100644 index 00000000000..122ffa144be --- /dev/null +++ b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/ConfigurationSnapshotListener.java @@ -0,0 +1,128 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.context; + +import java.io.FileWriter; +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; +import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.env.PropertySource; + +/** + * Application listener that logs a snapshot of the application configuration when the + * context is refreshed. + * + * @author Your Name + * @since 3.2.0 + */ +public class ConfigurationSnapshotListener implements ApplicationListener { + + private static final Logger logger = LoggerFactory.getLogger(ConfigurationSnapshotListener.class); + + private final ConfigurationSnapshotProperties properties; + + public ConfigurationSnapshotListener(ConfigurationSnapshotProperties properties) { + this.properties = properties; + } + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + Environment environment = event.getApplicationContext().getEnvironment(); + if (!(environment instanceof ConfigurableEnvironment configurableEnvironment)) { + return; + } + + // Build configuration snapshot + Map snapshot = new HashMap<>(); + snapshot.put("activeProfiles", environment.getActiveProfiles()); + + Map includedProperties = new HashMap<>(); + if (this.properties.getInclude() != null && !this.properties.getInclude().isEmpty()) { + for (String propertyName : this.properties.getInclude()) { + if (environment.containsProperty(propertyName)) { + includedProperties.put(propertyName, environment.getProperty(propertyName)); + } + } + } + snapshot.put("properties", includedProperties); + + // Convert to JSON + String json = toJson(snapshot); + + // Log to console or file + if (this.properties.getLogTo() == ConfigurationSnapshotProperties.LogTo.CONSOLE) { + logger.info("Configuration snapshot: {}", json); + } else { + writeToFile(json); + } + } + + private String toJson(Map snapshot) { + StringBuilder sb = new StringBuilder(); + sb.append("{"); + + // Active profiles + sb.append("\"activeProfiles\": ["); + String[] activeProfiles = (String[]) snapshot.get("activeProfiles"); + for (int i = 0; i < activeProfiles.length; i++) { + if (i > 0) { + sb.append(","); + } + sb.append("\"").append(activeProfiles[i]).append("\""); + } + sb.append("]"); + + // Properties + sb.append(",\"properties\": {"); + Map properties = (Map) snapshot.get("properties"); + int i = 0; + for (Map.Entry entry : properties.entrySet()) { + if (i > 0) { + sb.append(","); + } + sb.append("\"").append(entry.getKey()).append("\":\"").append(entry.getValue()).append("\""); + i++; + } + sb.append("}"); + + sb.append("}"); + return sb.toString(); + } + + private void writeToFile(String json) { + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); + String filename = "config-snapshot-" + timestamp + ".log"; + + try (FileWriter writer = new FileWriter(filename)) { + writer.write(json); + logger.info("Configuration snapshot written to file: {}", filename); + } catch (IOException e) { + logger.error("Failed to write configuration snapshot to file: {}", filename, e); + } + } +} diff --git a/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/ConfigurationSnapshotProperties.java b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/ConfigurationSnapshotProperties.java new file mode 100644 index 00000000000..a1ee9586b1e --- /dev/null +++ b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/context/ConfigurationSnapshotProperties.java @@ -0,0 +1,74 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.context; + +import java.util.List; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for configuration snapshot logging. + * + * @author Your Name + * @since 3.2.0 + */ +@ConfigurationProperties(prefix = "spring.config-snapshot") +public class ConfigurationSnapshotProperties { + + /** + * Whether to enable configuration snapshot logging. + */ + private boolean enabled = false; + + /** + * Where to log the configuration snapshot. + */ + private LogTo logTo = LogTo.CONSOLE; + + /** + * List of properties to include in the snapshot. + */ + private List include; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public LogTo getLogTo() { + return this.logTo; + } + + public void setLogTo(LogTo logTo) { + this.logTo = logTo; + } + + public List getInclude() { + return this.include; + } + + public void setInclude(List include) { + this.include = include; + } + + public enum LogTo { + CONSOLE, FILE + } +} diff --git a/core/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/core/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 34a5b9630c2..4c0b54ef9e4 100644 --- a/core/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/core/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -1,6 +1,24 @@ { "groups": [], "properties": [ + { + "name": "spring.config-snapshot.enabled", + "type": "java.lang.Boolean", + "description": "Whether to enable configuration snapshot logging.", + "defaultValue": false + }, + { + "name": "spring.config-snapshot.log-to", + "type": "org.springframework.boot.autoconfigure.context.ConfigurationSnapshotProperties.LogTo", + "description": "Where to log the configuration snapshot.", + "defaultValue": "CONSOLE" + }, + { + "name": "spring.config-snapshot.include", + "type": "java.util.List", + "description": "List of properties to include in the snapshot.", + "defaultValue": [] + }, { "name": "spring.aop.auto", "type": "java.lang.Boolean", diff --git a/core/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/core/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 0f4ed80b64a..46353c95815 100644 --- a/core/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/core/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -26,3 +26,7 @@ org.springframework.boot.autoconfigure.preinitialize.ConversionServiceBackground org.springframework.boot.diagnostics.FailureAnalyzer=\ org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\ org.springframework.boot.autoconfigure.ssl.BundleContentNotWatchableFailureAnalyzer + +# Auto Configure +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.springframework.boot.autoconfigure.context.ConfigurationSnapshotAutoConfiguration diff --git a/core/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/context/ConfigurationSnapshotAutoConfigurationTests.java b/core/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/context/ConfigurationSnapshotAutoConfigurationTests.java new file mode 100644 index 00000000000..9805953e271 --- /dev/null +++ b/core/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/context/ConfigurationSnapshotAutoConfigurationTests.java @@ -0,0 +1,74 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.context; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ConfigurationSnapshotAutoConfiguration}. + * + * @author Your Name + * @since 3.2.0 + */ +class ConfigurationSnapshotAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration( + AutoConfigurations.of(ConfigurationSnapshotAutoConfiguration.class)); + + @Test + void configurationSnapshotAutoConfigurationIsDisabledByDefault() { + this.contextRunner.run((context) -> { + assertThat(context.getBeansOfType(ConfigurationSnapshotListener.class)).isEmpty(); + }); + } + + @Test + void configurationSnapshotAutoConfigurationIsEnabledWhenPropertyIsSetToTrue() { + this.contextRunner.withPropertyValues("spring.config-snapshot.enabled=true") + .run((context) -> { + assertThat(context.getBeansOfType(ConfigurationSnapshotListener.class)).isNotEmpty(); + }); + } + + @Test + void configurationSnapshotAutoConfigurationIsDisabledWhenPropertyIsSetToFalse() { + this.contextRunner.withPropertyValues("spring.config-snapshot.enabled=false") + .run((context) -> { + assertThat(context.getBeansOfType(ConfigurationSnapshotListener.class)).isEmpty(); + }); + } + + @Test + void configurationSnapshotPropertiesAreBoundCorrectly() { + this.contextRunner.withPropertyValues( + "spring.config-snapshot.enabled=true", + "spring.config-snapshot.log-to=FILE", + "spring.config-snapshot.include=spring.datasource.url,spring.cache.enabled") + .run((context) -> { + ConfigurationSnapshotProperties properties = context.getBean(ConfigurationSnapshotProperties.class); + assertThat(properties.isEnabled()).isTrue(); + assertThat(properties.getLogTo()).isEqualTo(ConfigurationSnapshotProperties.LogTo.FILE); + assertThat(properties.getInclude()).containsExactly("spring.datasource.url", "spring.cache.enabled"); + }); + } +}