From f4b4f4f0bf4c1015597a6154a15514041bc6b529 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Wed, 31 Jul 2024 13:33:18 +0200 Subject: [PATCH] Make PID and application version available in the environment Adds the following new properties: - spring.application.pid - spring.application.version Refactors the ResourceBanner and the structured logging support to use the new properties. Closes gh-41604 --- .../context/SpringBootContextLoaderTests.java | 6 +- .../boot/ApplicationInfoPropertySource.java | 77 +++++++++++++++++++ .../springframework/boot/ResourceBanner.java | 26 +++++-- .../boot/SpringApplication.java | 2 + ...ticCommonSchemaStructuredLogFormatter.java | 16 ++-- .../logging/log4j2/StructuredLogLayout.java | 5 +- ...ticCommonSchemaStructuredLogFormatter.java | 17 ++-- .../logging/logback/StructuredLogEncoder.java | 6 +- .../ElasticCommonSchemaService.java | 3 +- .../structured/StructuredLogFormatter.java | 3 - .../StructuredLogFormatterFactory.java | 4 - ...itional-spring-configuration-metadata.json | 5 ++ .../ApplicationInfoPropertySourceTests.java | 76 ++++++++++++++++++ .../boot/ResourceBannerTests.java | 30 +++----- ...mmonSchemaStructuredLogFormatterTests.java | 14 ++-- .../log4j2/StructuredLoggingLayoutTests.java | 13 ++-- ...mmonSchemaStructuredLogFormatterTests.java | 14 ++-- .../StructuredLoggingEncoderTests.java | 15 ++-- .../ElasticCommonSchemaServiceTests.java | 9 +++ .../boot/system/ApplicationPidTests.java | 2 +- .../log4j2/CustomStructuredLogFormatter.java | 10 +-- .../CustomStructuredLogFormatter.java | 10 +-- 22 files changed, 267 insertions(+), 96 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ApplicationInfoPropertySource.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ApplicationInfoPropertySourceTests.java diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringBootContextLoaderTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringBootContextLoaderTests.java index f9cc7f6e69..3f6a4888d1 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringBootContextLoaderTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringBootContextLoaderTests.java @@ -160,11 +160,11 @@ class SpringBootContextLoaderTests { .stream() .map(PropertySource::getName) .collect(Collectors.toCollection(ArrayList::new)); - String last = names.remove(names.size() - 1); + String configResource = names.remove(names.size() - 2); assertThat(names).containsExactly("configurationProperties", "Inlined Test Properties", "commandLineArgs", "servletConfigInitParams", "servletContextInitParams", "systemProperties", "systemEnvironment", - "random"); - assertThat(last).startsWith("Config resource"); + "random", "applicationInfo"); + assertThat(configResource).startsWith("Config resource"); } @Test diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ApplicationInfoPropertySource.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ApplicationInfoPropertySource.java new file mode 100644 index 0000000000..d4b7e62e44 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ApplicationInfoPropertySource.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012-2024 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; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.boot.system.ApplicationPid; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertySource; +import org.springframework.util.StringUtils; + +/** + * {@link PropertySource} which provides information about the application, like the + * process ID (PID) or the version. + * + * @author Moritz Halbritter + */ +class ApplicationInfoPropertySource extends MapPropertySource { + + static final String NAME = "applicationInfo"; + + ApplicationInfoPropertySource(Class mainClass) { + super(NAME, getProperties(readVersion(mainClass))); + } + + ApplicationInfoPropertySource(String applicationVersion) { + super(NAME, getProperties(applicationVersion)); + } + + private static Map getProperties(String applicationVersion) { + Map result = new HashMap<>(); + if (StringUtils.hasText(applicationVersion)) { + result.put("spring.application.version", applicationVersion); + } + ApplicationPid applicationPid = new ApplicationPid(); + if (applicationPid.isAvailable()) { + result.put("spring.application.pid", applicationPid.toLong()); + } + return result; + } + + private static String readVersion(Class applicationClass) { + Package sourcePackage = (applicationClass != null) ? applicationClass.getPackage() : null; + return (sourcePackage != null) ? sourcePackage.getImplementationVersion() : null; + } + + /** + * Moves the {@link ApplicationInfoPropertySource} to the end of the environment's + * property sources. + * @param environment the environment + */ + static void moveToEnd(ConfigurableEnvironment environment) { + MutablePropertySources propertySources = environment.getPropertySources(); + PropertySource propertySource = propertySources.remove(NAME); + if (propertySource != null) { + propertySources.addLast(propertySource); + } + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ResourceBanner.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ResourceBanner.java index 98b2e6dd95..99e79a4af4 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ResourceBanner.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ResourceBanner.java @@ -47,6 +47,7 @@ import org.springframework.util.StreamUtils; * @author Vedran Pavic * @author Toshiaki Maki * @author Krzysztof Krason + * @author Moritz Halbritter * @since 1.2.0 */ public class ResourceBanner implements Banner { @@ -91,7 +92,7 @@ public class ResourceBanner implements Banner { } sources.addLast(getTitleSource(sourceClass)); sources.addLast(getAnsiSource()); - sources.addLast(getVersionSource(sourceClass)); + sources.addLast(getVersionSource(sourceClass, environment)); List resolvers = new ArrayList<>(); resolvers.add(new PropertySourcesPropertyResolver(sources)); return resolvers; @@ -119,12 +120,15 @@ public class ResourceBanner implements Banner { return new AnsiPropertySource("ansi", true); } - private MapPropertySource getVersionSource(Class sourceClass) { - return new MapPropertySource("version", getVersionsMap(sourceClass)); + private MapPropertySource getVersionSource(Class sourceClass, Environment environment) { + return new MapPropertySource("version", getVersionsMap(sourceClass, environment)); } - private Map getVersionsMap(Class sourceClass) { + private Map getVersionsMap(Class sourceClass, Environment environment) { String appVersion = getApplicationVersion(sourceClass); + if (appVersion == null) { + appVersion = getApplicationVersion(environment); + } String bootVersion = getBootVersion(); Map versions = new HashMap<>(); versions.put("application.version", getVersionString(appVersion, false)); @@ -134,9 +138,19 @@ public class ResourceBanner implements Banner { return versions; } + /** + * Returns the application version. + * @param sourceClass the source class + * @return the application version or {@code null} if unknown + * @deprecated since 3.4.0 for removal in 3.6.0 + */ + @Deprecated(since = "3.4.0", forRemoval = true) protected String getApplicationVersion(Class sourceClass) { - Package sourcePackage = (sourceClass != null) ? sourceClass.getPackage() : null; - return (sourcePackage != null) ? sourcePackage.getImplementationVersion() : null; + return null; + } + + private String getApplicationVersion(Environment environment) { + return environment.getProperty("spring.application.version"); } protected String getBootVersion() { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java index 480d9a94d3..b342f6bb3c 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java @@ -368,6 +368,7 @@ public class SpringApplication { configureEnvironment(environment, applicationArguments.getSourceArgs()); ConfigurationPropertySources.attach(environment); listeners.environmentPrepared(bootstrapContext, environment); + ApplicationInfoPropertySource.moveToEnd(environment); DefaultPropertiesPropertySource.moveToEnd(environment); Assert.state(!environment.containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties."); @@ -539,6 +540,7 @@ public class SpringApplication { sources.addFirst(new SimpleCommandLinePropertySource(args)); } } + environment.getPropertySources().addLast(new ApplicationInfoPropertySource(this.mainApplicationClass)); } /** diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatter.java index f42ecfcc26..b883105f1a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatter.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatter.java @@ -16,6 +16,8 @@ package org.springframework.boot.logging.log4j2; +import java.util.Objects; + import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.ThrowableProxy; @@ -28,7 +30,7 @@ import org.springframework.boot.logging.structured.CommonStructuredLogFormat; import org.springframework.boot.logging.structured.ElasticCommonSchemaService; import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter; import org.springframework.boot.logging.structured.StructuredLogFormatter; -import org.springframework.boot.system.ApplicationPid; +import org.springframework.core.env.Environment; import org.springframework.util.ObjectUtils; /** @@ -40,17 +42,17 @@ import org.springframework.util.ObjectUtils; */ class ElasticCommonSchemaStructuredLogFormatter extends JsonWriterStructuredLogFormatter { - ElasticCommonSchemaStructuredLogFormatter(ApplicationPid pid, ElasticCommonSchemaService service) { - super((members) -> jsonMembers(pid, service, members)); + ElasticCommonSchemaStructuredLogFormatter(Environment environment) { + super((members) -> jsonMembers(environment, members)); } - private static void jsonMembers(ApplicationPid pid, ElasticCommonSchemaService service, - JsonWriter.Members members) { + private static void jsonMembers(Environment environment, JsonWriter.Members members) { members.add("@timestamp", LogEvent::getInstant).as(ElasticCommonSchemaStructuredLogFormatter::asTimestamp); members.add("log.level", LogEvent::getLevel).as(Level::name); - members.add("process.pid", pid).when(ApplicationPid::isAvailable).as(ApplicationPid::toLong); + members.add("process.pid", environment.getProperty("spring.application.pid", Long.class)) + .when(Objects::nonNull); members.add("process.thread.name", LogEvent::getThreadName); - service.jsonMembers(members); + ElasticCommonSchemaService.get(environment).jsonMembers(members); members.add("log.logger", LogEvent::getLoggerName); members.add("message", LogEvent::getMessage).as(Message::getFormattedMessage); members.from(LogEvent::getContextData) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/StructuredLogLayout.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/StructuredLogLayout.java index c471905bac..bdb99a9fd8 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/StructuredLogLayout.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/StructuredLogLayout.java @@ -30,11 +30,9 @@ import org.apache.logging.log4j.core.config.plugins.PluginLoggerContext; import org.apache.logging.log4j.core.layout.AbstractStringLayout; import org.springframework.boot.logging.structured.CommonStructuredLogFormat; -import org.springframework.boot.logging.structured.ElasticCommonSchemaService; import org.springframework.boot.logging.structured.StructuredLogFormatter; import org.springframework.boot.logging.structured.StructuredLogFormatterFactory; import org.springframework.boot.logging.structured.StructuredLogFormatterFactory.CommonFormatters; -import org.springframework.boot.system.ApplicationPid; import org.springframework.core.env.Environment; import org.springframework.util.Assert; @@ -106,8 +104,7 @@ final class StructuredLogLayout extends AbstractStringLayout { private void addCommonFormatters(CommonFormatters commonFormatters) { commonFormatters.add(CommonStructuredLogFormat.ELASTIC_COMMON_SCHEMA, (instantiator) -> new ElasticCommonSchemaStructuredLogFormatter( - instantiator.getArg(ApplicationPid.class), - instantiator.getArg(ElasticCommonSchemaService.class))); + instantiator.getArg(Environment.class))); commonFormatters.add(CommonStructuredLogFormat.LOGSTASH, (instantiator) -> new LogstashStructuredLogFormatter()); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/ElasticCommonSchemaStructuredLogFormatter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/ElasticCommonSchemaStructuredLogFormatter.java index 5b34265088..3a70edd733 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/ElasticCommonSchemaStructuredLogFormatter.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/ElasticCommonSchemaStructuredLogFormatter.java @@ -16,6 +16,8 @@ package org.springframework.boot.logging.logback; +import java.util.Objects; + import ch.qos.logback.classic.pattern.ThrowableProxyConverter; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.IThrowableProxy; @@ -27,7 +29,7 @@ import org.springframework.boot.logging.structured.CommonStructuredLogFormat; import org.springframework.boot.logging.structured.ElasticCommonSchemaService; import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter; import org.springframework.boot.logging.structured.StructuredLogFormatter; -import org.springframework.boot.system.ApplicationPid; +import org.springframework.core.env.Environment; /** * Logback {@link StructuredLogFormatter} for @@ -41,18 +43,19 @@ class ElasticCommonSchemaStructuredLogFormatter extends JsonWriterStructuredLogF private static final PairExtractor keyValuePairExtractor = PairExtractor.of((pair) -> pair.key, (pair) -> pair.value); - ElasticCommonSchemaStructuredLogFormatter(ApplicationPid pid, ElasticCommonSchemaService service, + ElasticCommonSchemaStructuredLogFormatter(Environment environment, ThrowableProxyConverter throwableProxyConverter) { - super((members) -> jsonMembers(pid, service, throwableProxyConverter, members)); + super((members) -> jsonMembers(environment, throwableProxyConverter, members)); } - private static void jsonMembers(ApplicationPid pid, ElasticCommonSchemaService service, - ThrowableProxyConverter throwableProxyConverter, JsonWriter.Members members) { + private static void jsonMembers(Environment environment, ThrowableProxyConverter throwableProxyConverter, + JsonWriter.Members members) { members.add("@timestamp", ILoggingEvent::getInstant); members.add("log.level", ILoggingEvent::getLevel); - members.add("process.pid", pid).when(ApplicationPid::isAvailable).as(ApplicationPid::toLong); + members.add("process.pid", environment.getProperty("spring.application.pid", Long.class)) + .when(Objects::nonNull); members.add("process.thread.name", ILoggingEvent::getThreadName); - service.jsonMembers(members); + ElasticCommonSchemaService.get(environment).jsonMembers(members); members.add("log.logger", ILoggingEvent::getLoggerName); members.add("message", ILoggingEvent::getFormattedMessage); members.addMapEntries(ILoggingEvent::getMDCPropertyMap); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/StructuredLogEncoder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/StructuredLogEncoder.java index c4539e9cb0..b8f1d15cb6 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/StructuredLogEncoder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/StructuredLogEncoder.java @@ -25,11 +25,9 @@ import ch.qos.logback.core.encoder.Encoder; import ch.qos.logback.core.encoder.EncoderBase; import org.springframework.boot.logging.structured.CommonStructuredLogFormat; -import org.springframework.boot.logging.structured.ElasticCommonSchemaService; import org.springframework.boot.logging.structured.StructuredLogFormatter; import org.springframework.boot.logging.structured.StructuredLogFormatterFactory; import org.springframework.boot.logging.structured.StructuredLogFormatterFactory.CommonFormatters; -import org.springframework.boot.system.ApplicationPid; import org.springframework.boot.util.Instantiator.AvailableParameters; import org.springframework.core.env.Environment; import org.springframework.util.Assert; @@ -82,9 +80,7 @@ public class StructuredLogEncoder extends EncoderBase { private void addCommonFormatters(CommonFormatters commonFormatters) { commonFormatters.add(CommonStructuredLogFormat.ELASTIC_COMMON_SCHEMA, - (instantiator) -> new ElasticCommonSchemaStructuredLogFormatter( - instantiator.getArg(ApplicationPid.class), - instantiator.getArg(ElasticCommonSchemaService.class), + (instantiator) -> new ElasticCommonSchemaStructuredLogFormatter(instantiator.getArg(Environment.class), instantiator.getArg(ThrowableProxyConverter.class))); commonFormatters.add(CommonStructuredLogFormat.LOGSTASH, (instantiator) -> new LogstashStructuredLogFormatter( instantiator.getArg(ThrowableProxyConverter.class))); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/ElasticCommonSchemaService.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/ElasticCommonSchemaService.java index 9d448ef759..2f7810dd67 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/ElasticCommonSchemaService.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/ElasticCommonSchemaService.java @@ -38,7 +38,8 @@ public record ElasticCommonSchemaService(String name, String version, String env private ElasticCommonSchemaService withDefaults(Environment environment) { String name = withFallbackProperty(environment, this.name, "spring.application.name"); - return new ElasticCommonSchemaService(name, this.version, this.environment, this.nodeName); + String version = withFallbackProperty(environment, this.version, "spring.application.version"); + return new ElasticCommonSchemaService(name, version, this.environment, this.nodeName); } private String withFallbackProperty(Environment environment, String value, String property) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLogFormatter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLogFormatter.java index eb5193a770..c5bbdd5d0f 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLogFormatter.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLogFormatter.java @@ -20,7 +20,6 @@ import java.nio.charset.Charset; import ch.qos.logback.classic.pattern.ThrowableProxyConverter; -import org.springframework.boot.system.ApplicationPid; import org.springframework.core.env.Environment; /** @@ -29,8 +28,6 @@ import org.springframework.core.env.Environment; * Implementing classes can declare the following parameter types in the constructor: *
    *
  • {@link Environment}
  • - *
  • {@link ApplicationPid}
  • - *
  • {@link ElasticCommonSchemaService}
  • *
* When using Logback, implementing classes can also use the following parameter types in * the constructor: diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLogFormatterFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLogFormatterFactory.java index 71fa9d23ba..4958f6b988 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLogFormatterFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLogFormatterFactory.java @@ -21,7 +21,6 @@ import java.util.Map; import java.util.TreeMap; import java.util.function.Consumer; -import org.springframework.boot.system.ApplicationPid; import org.springframework.boot.util.Instantiator; import org.springframework.boot.util.Instantiator.AvailableParameters; import org.springframework.boot.util.Instantiator.FailureHandler; @@ -68,9 +67,6 @@ public class StructuredLogFormatterFactory { this.logEventType = logEventType; this.instantiator = new Instantiator<>(StructuredLogFormatter.class, (allAvailableParameters) -> { allAvailableParameters.add(Environment.class, environment); - allAvailableParameters.add(ApplicationPid.class, (type) -> new ApplicationPid()); - allAvailableParameters.add(ElasticCommonSchemaService.class, - (type) -> ElasticCommonSchemaService.get(environment)); if (availableParameters != null) { availableParameters.accept(allAvailableParameters); } diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json index abe86991ad..50eed03b92 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -285,6 +285,11 @@ "sourceType": "org.springframework.boot.context.ContextIdApplicationContextInitializer", "description": "Application name." }, + { + "name": "spring.application.version", + "type": "java.lang.String", + "description": "Application version (defaults to 'Implementation-Version' from the manifest)." + }, { "name": "spring.banner.charset", "type": "java.nio.charset.Charset", diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ApplicationInfoPropertySourceTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ApplicationInfoPropertySourceTests.java new file mode 100644 index 0000000000..965ecafec5 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ApplicationInfoPropertySourceTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012-2024 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; + +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.system.ApplicationPid; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.mock.env.MockEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ApplicationInfoPropertySource}. + * + * @author Moritz Halbritter + */ +class ApplicationInfoPropertySourceTests { + + @Test + void shouldAddVersion() { + MockEnvironment environment = new MockEnvironment(); + environment.getPropertySources().addLast(new ApplicationInfoPropertySource("1.2.3")); + assertThat(environment.getProperty("spring.application.version")).isEqualTo("1.2.3"); + } + + @Test + void shouldNotAddVersionIfVersionIsNotAvailable() { + MockEnvironment environment = new MockEnvironment(); + environment.getPropertySources().addLast(new ApplicationInfoPropertySource((String) null)); + assertThat(environment.containsProperty("spring.application.version")).isFalse(); + } + + @Test + void shouldAddPid() { + MockEnvironment environment = new MockEnvironment(); + environment.getPropertySources().addLast(new ApplicationInfoPropertySource("1.2.3")); + assertThat(environment.getProperty("spring.application.pid", Long.class)) + .isEqualTo(new ApplicationPid().toLong()); + } + + @Test + void shouldMoveToEnd() { + MockEnvironment environment = new MockEnvironment(); + environment.getPropertySources().addFirst(new MapPropertySource("first", Collections.emptyMap())); + environment.getPropertySources().addAfter("first", new MapPropertySource("second", Collections.emptyMap())); + environment.getPropertySources().addFirst(new ApplicationInfoPropertySource("1.2.3")); + List propertySources = environment.getPropertySources().stream().map(PropertySource::getName).toList(); + assertThat(propertySources).containsExactly("applicationInfo", "first", "second", "mockProperties"); + ApplicationInfoPropertySource.moveToEnd(environment); + List propertySourcesAfterMove = environment.getPropertySources() + .stream() + .map(PropertySource::getName) + .toList(); + assertThat(propertySourcesAfterMove).containsExactly("first", "second", "mockProperties", "applicationInfo"); + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ResourceBannerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ResourceBannerTests.java index 8ba272032d..b7de1635a5 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ResourceBannerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ResourceBannerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -28,7 +28,6 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.ansi.AnsiOutput; import org.springframework.boot.ansi.AnsiOutput.Enabled; import org.springframework.core.env.AbstractPropertyResolver; -import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.PropertyResolver; @@ -143,18 +142,20 @@ class ResourceBannerTests { @Test void renderWithMutation() { Resource resource = new ByteArrayResource("banner ${foo}".getBytes()); - String banner = printBanner(new MutatingResourceBanner(resource, "1", "2", null)); + String banner = printBanner(new MutatingResourceBanner(resource, "1", null), "2"); assertThat(banner).startsWith("banner bar"); - } private String printBanner(Resource resource, String bootVersion, String applicationVersion, String applicationTitle) { - return printBanner(new MockResourceBanner(resource, bootVersion, applicationVersion, applicationTitle)); + return printBanner(new MockResourceBanner(resource, bootVersion, applicationTitle), applicationVersion); } - private String printBanner(ResourceBanner banner) { - ConfigurableEnvironment environment = new MockEnvironment(); + private String printBanner(ResourceBanner banner, String applicationVersion) { + MockEnvironment environment = new MockEnvironment(); + if (applicationVersion != null) { + environment.setProperty("spring.application.version", applicationVersion); + } Map source = Collections.singletonMap("a", "1"); environment.getPropertySources().addLast(new MapPropertySource("map", source)); ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -166,14 +167,11 @@ class ResourceBannerTests { private final String bootVersion; - private final String applicationVersion; - private final String applicationTitle; - MockResourceBanner(Resource resource, String bootVersion, String applicationVersion, String applicationTitle) { + MockResourceBanner(Resource resource, String bootVersion, String applicationTitle) { super(resource); this.bootVersion = bootVersion; - this.applicationVersion = applicationVersion; this.applicationTitle = applicationTitle; } @@ -182,11 +180,6 @@ class ResourceBannerTests { return this.bootVersion; } - @Override - protected String getApplicationVersion(Class sourceClass) { - return this.applicationVersion; - } - @Override protected String getApplicationTitle(Class sourceClass) { return this.applicationTitle; @@ -196,9 +189,8 @@ class ResourceBannerTests { static class MutatingResourceBanner extends MockResourceBanner { - MutatingResourceBanner(Resource resource, String bootVersion, String applicationVersion, - String applicationTitle) { - super(resource, bootVersion, applicationVersion, applicationTitle); + MutatingResourceBanner(Resource resource, String bootVersion, String applicationTitle) { + super(resource, bootVersion, applicationTitle); } @Override diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatterTests.java index ff7dc5113a..5171a11bb8 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatterTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatterTests.java @@ -23,9 +23,7 @@ import org.apache.logging.log4j.core.impl.MutableLogEvent; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.boot.logging.structured.ElasticCommonSchemaService; -import org.springframework.boot.system.ApplicationPid; -import org.springframework.boot.system.MockApplicationPid; +import org.springframework.mock.env.MockEnvironment; import static org.assertj.core.api.Assertions.assertThat; @@ -40,9 +38,13 @@ class ElasticCommonSchemaStructuredLogFormatterTests extends AbstractStructuredL @BeforeEach void setUp() { - ApplicationPid pid = MockApplicationPid.of(1L); - ElasticCommonSchemaService service = new ElasticCommonSchemaService("name", "1.0.0", "test", "node-1"); - this.formatter = new ElasticCommonSchemaStructuredLogFormatter(pid, service); + MockEnvironment environment = new MockEnvironment(); + environment.setProperty("logging.structured.ecs.service.name", "name"); + environment.setProperty("logging.structured.ecs.service.version", "1.0.0"); + environment.setProperty("logging.structured.ecs.service.environment", "test"); + environment.setProperty("logging.structured.ecs.service.node-name", "node-1"); + environment.setProperty("spring.application.pid", "1"); + this.formatter = new ElasticCommonSchemaStructuredLogFormatter(environment); } @Test diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/StructuredLoggingLayoutTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/StructuredLoggingLayoutTests.java index e7e7110895..64e56d6a77 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/StructuredLoggingLayoutTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/StructuredLoggingLayoutTests.java @@ -27,7 +27,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.logging.log4j2.StructuredLogLayout.Builder; import org.springframework.boot.logging.structured.StructuredLogFormatter; -import org.springframework.boot.system.ApplicationPid; +import org.springframework.core.env.Environment; import org.springframework.mock.env.MockEnvironment; import org.springframework.test.util.ReflectionTestUtils; @@ -83,11 +83,12 @@ class StructuredLoggingLayoutTests extends AbstractStructuredLoggingTests { @Test void shouldInjectCustomFormatConstructorParameters() { + this.environment.setProperty("spring.application.pid", "42"); StructuredLogLayout layout = newBuilder() .setFormat(CustomLog4j2StructuredLoggingFormatterWithInjection.class.getName()) .build(); String format = layout.toSerializable(createEvent()); - assertThat(format).isEqualTo("custom-format-with-injection pid=" + new ApplicationPid()); + assertThat(format).isEqualTo("custom-format-with-injection pid=42"); } @Test @@ -129,15 +130,15 @@ class StructuredLoggingLayoutTests extends AbstractStructuredLoggingTests { static final class CustomLog4j2StructuredLoggingFormatterWithInjection implements StructuredLogFormatter { - private final ApplicationPid pid; + private final Environment environment; - CustomLog4j2StructuredLoggingFormatterWithInjection(ApplicationPid pid) { - this.pid = pid; + CustomLog4j2StructuredLoggingFormatterWithInjection(Environment environment) { + this.environment = environment; } @Override public String format(LogEvent event) { - return "custom-format-with-injection pid=" + this.pid; + return "custom-format-with-injection pid=" + this.environment.getProperty("spring.application.pid"); } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/ElasticCommonSchemaStructuredLogFormatterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/ElasticCommonSchemaStructuredLogFormatterTests.java index 4e27000fb4..d59bed968f 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/ElasticCommonSchemaStructuredLogFormatterTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/ElasticCommonSchemaStructuredLogFormatterTests.java @@ -24,9 +24,7 @@ import ch.qos.logback.classic.spi.ThrowableProxy; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.boot.logging.structured.ElasticCommonSchemaService; -import org.springframework.boot.system.ApplicationPid; -import org.springframework.boot.system.MockApplicationPid; +import org.springframework.mock.env.MockEnvironment; import static org.assertj.core.api.Assertions.assertThat; @@ -43,9 +41,13 @@ class ElasticCommonSchemaStructuredLogFormatterTests extends AbstractStructuredL @BeforeEach void setUp() { super.setUp(); - ApplicationPid pid = MockApplicationPid.of(1L); - ElasticCommonSchemaService service = new ElasticCommonSchemaService("name", "1.0.0", "test", "node-1"); - this.formatter = new ElasticCommonSchemaStructuredLogFormatter(pid, service, getThrowableProxyConverter()); + MockEnvironment environment = new MockEnvironment(); + environment.setProperty("logging.structured.ecs.service.name", "name"); + environment.setProperty("logging.structured.ecs.service.version", "1.0.0"); + environment.setProperty("logging.structured.ecs.service.environment", "test"); + environment.setProperty("logging.structured.ecs.service.node-name", "node-1"); + environment.setProperty("spring.application.pid", "1"); + this.formatter = new ElasticCommonSchemaStructuredLogFormatter(environment, getThrowableProxyConverter()); } @Test diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/StructuredLoggingEncoderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/StructuredLoggingEncoderTests.java index b9180a1f7f..7015ec3bae 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/StructuredLoggingEncoderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/StructuredLoggingEncoderTests.java @@ -30,7 +30,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.logging.structured.StructuredLogFormatter; -import org.springframework.boot.system.ApplicationPid; import org.springframework.core.env.Environment; import org.springframework.mock.env.MockEnvironment; @@ -103,13 +102,13 @@ class StructuredLoggingEncoderTests extends AbstractStructuredLoggingTests { @Test void shouldInjectCustomFormatConstructorParameters() { + this.environment.setProperty("spring.application.pid", "42"); this.encoder.setFormat(CustomLogbackStructuredLoggingFormatterWithInjection.class.getName()); this.encoder.start(); LoggingEvent event = createEvent(); event.setMDCPropertyMap(Collections.emptyMap()); String format = encode(event); - assertThat(format) - .isEqualTo("custom-format-with-injection pid=" + new ApplicationPid() + " hasThrowableProxyConverter=true"); + assertThat(format).isEqualTo("custom-format-with-injection pid=42 hasThrowableProxyConverter=true"); } @Test @@ -154,21 +153,21 @@ class StructuredLoggingEncoderTests extends AbstractStructuredLoggingTests { static final class CustomLogbackStructuredLoggingFormatterWithInjection implements StructuredLogFormatter { - private final ApplicationPid pid; + private final Environment environment; private final ThrowableProxyConverter throwableProxyConverter; - CustomLogbackStructuredLoggingFormatterWithInjection(ApplicationPid pid, + CustomLogbackStructuredLoggingFormatterWithInjection(Environment environment, ThrowableProxyConverter throwableProxyConverter) { - this.pid = pid; + this.environment = environment; this.throwableProxyConverter = throwableProxyConverter; } @Override public String format(ILoggingEvent event) { boolean hasThrowableProxyConverter = this.throwableProxyConverter != null; - return "custom-format-with-injection pid=" + this.pid + " hasThrowableProxyConverter=" - + hasThrowableProxyConverter; + return "custom-format-with-injection pid=" + this.environment.getProperty("spring.application.pid") + + " hasThrowableProxyConverter=" + hasThrowableProxyConverter; } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/structured/ElasticCommonSchemaServiceTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/structured/ElasticCommonSchemaServiceTests.java index a47635aa92..c327c949e8 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/structured/ElasticCommonSchemaServiceTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/structured/ElasticCommonSchemaServiceTests.java @@ -27,6 +27,7 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link ElasticCommonSchemaService}. * * @author Phillip Webb + * @author Moritz Halbritter */ class ElasticCommonSchemaServiceTests { @@ -49,6 +50,14 @@ class ElasticCommonSchemaServiceTests { assertThat(service).isEqualTo(new ElasticCommonSchemaService("spring", null, null, null)); } + @Test + void getWhenNoServiceVersionUsesApplicationVersion() { + MockEnvironment environment = new MockEnvironment(); + environment.setProperty("spring.application.version", "1.2.3"); + ElasticCommonSchemaService service = ElasticCommonSchemaService.get(environment); + assertThat(service).isEqualTo(new ElasticCommonSchemaService(null, "1.2.3", null, null)); + } + @Test void getWhenNoPropertiesToBind() { MockEnvironment environment = new MockEnvironment(); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/system/ApplicationPidTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/system/ApplicationPidTests.java index 7f1a783efd..0574777ce6 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/system/ApplicationPidTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/system/ApplicationPidTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-structure-logging-log4j2/src/main/java/smoketest/structuredlogging/log4j2/CustomStructuredLogFormatter.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-structure-logging-log4j2/src/main/java/smoketest/structuredlogging/log4j2/CustomStructuredLogFormatter.java index 31c585c6f3..24a5f7ff45 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-structure-logging-log4j2/src/main/java/smoketest/structuredlogging/log4j2/CustomStructuredLogFormatter.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-structure-logging-log4j2/src/main/java/smoketest/structuredlogging/log4j2/CustomStructuredLogFormatter.java @@ -20,21 +20,21 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.ThrowableProxy; import org.springframework.boot.logging.structured.StructuredLogFormatter; -import org.springframework.boot.system.ApplicationPid; +import org.springframework.core.env.Environment; public class CustomStructuredLogFormatter implements StructuredLogFormatter { - private final ApplicationPid pid; + private final Long pid; - public CustomStructuredLogFormatter(ApplicationPid pid) { - this.pid = pid; + public CustomStructuredLogFormatter(Environment environment) { + this.pid = environment.getProperty("spring.application.pid", Long.class); } @Override public String format(LogEvent event) { StringBuilder result = new StringBuilder(); result.append("epoch=").append(event.getInstant().getEpochMillisecond()); - if (this.pid.isAvailable()) { + if (this.pid != null) { result.append(" pid=").append(this.pid); } result.append(" msg=\"").append(event.getMessage().getFormattedMessage()).append('"'); diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-structure-logging/src/main/java/smoketest/structuredlogging/CustomStructuredLogFormatter.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-structure-logging/src/main/java/smoketest/structuredlogging/CustomStructuredLogFormatter.java index 8ea3f2cdf8..fd43a06adb 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-structure-logging/src/main/java/smoketest/structuredlogging/CustomStructuredLogFormatter.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-structure-logging/src/main/java/smoketest/structuredlogging/CustomStructuredLogFormatter.java @@ -21,16 +21,16 @@ import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.IThrowableProxy; import org.springframework.boot.logging.structured.StructuredLogFormatter; -import org.springframework.boot.system.ApplicationPid; +import org.springframework.core.env.Environment; public class CustomStructuredLogFormatter implements StructuredLogFormatter { - private final ApplicationPid pid; + private final Long pid; private final ThrowableProxyConverter throwableProxyConverter; - public CustomStructuredLogFormatter(ApplicationPid pid, ThrowableProxyConverter throwableProxyConverter) { - this.pid = pid; + public CustomStructuredLogFormatter(Environment environment, ThrowableProxyConverter throwableProxyConverter) { + this.pid = environment.getProperty("spring.application.pid", Long.class); this.throwableProxyConverter = throwableProxyConverter; } @@ -38,7 +38,7 @@ public class CustomStructuredLogFormatter implements StructuredLogFormatter