From 4d5e20c095772e4be567d69479334f9c9e2c4d25 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Wed, 31 Jul 2024 13:33:18 +0200 Subject: [PATCH] Make PID, application version and title available in the environment Adds the following new properties: - spring.application.pid - spring.application.version - spring.application.title Refactors the ResourceBanner and the structured logging support to use the new properties. --- .../context/SpringBootContextLoaderTests.java | 2 +- .../boot/ApplicationInfoPropertySource.java | 59 +++++++++++++++++ .../springframework/boot/ResourceBanner.java | 38 +++-------- .../boot/SpringApplication.java | 1 + ...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 -- .../boot/system/ApplicationPid.java | 2 +- .../boot/system/ApplicationTitle.java | 63 +++++++++++++++++++ .../boot/system/ApplicationVersion.java | 63 +++++++++++++++++++ .../boot/ResourceBannerTests.java | 40 +++++------- ...mmonSchemaStructuredLogFormatterTests.java | 14 +++-- .../log4j2/StructuredLoggingLayoutTests.java | 13 ++-- ...mmonSchemaStructuredLogFormatterTests.java | 14 +++-- .../StructuredLoggingEncoderTests.java | 15 +++-- .../boot/system/ApplicationPidTests.java | 10 +-- .../log4j2/CustomStructuredLogFormatter.java | 12 ++-- .../CustomStructuredLogFormatter.java | 12 ++-- 22 files changed, 282 insertions(+), 130 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/main/java/org/springframework/boot/system/ApplicationTitle.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/system/ApplicationVersion.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 f9cc7f6e69ca..a060583f55a9 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 @@ -163,7 +163,7 @@ void propertySourceOrdering() { String last = names.remove(names.size() - 1); assertThat(names).containsExactly("configurationProperties", "Inlined Test Properties", "commandLineArgs", "servletConfigInitParams", "servletContextInitParams", "systemProperties", "systemEnvironment", - "random"); + "random", "applicationInfo"); assertThat(last).startsWith("Config resource"); } 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 000000000000..dac5eb7bb01b --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ApplicationInfoPropertySource.java @@ -0,0 +1,59 @@ +/* + * 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.boot.system.ApplicationTitle; +import org.springframework.boot.system.ApplicationVersion; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.PropertySource; + +/** + * {@link PropertySource} which provides information about the application, like the + * title, the PID or the version. + * + * @author Moritz Halbritter + */ +class ApplicationInfoPropertySource extends MapPropertySource { + + static final String NAME = "applicationInfo"; + + ApplicationInfoPropertySource(Class mainClass) { + super(NAME, getProperties(mainClass)); + } + + private static Map getProperties(Class mainClass) { + Map result = new HashMap<>(); + ApplicationVersion applicationVersion = new ApplicationVersion(mainClass); + if (applicationVersion.isAvailable()) { + result.put("spring.application.version", applicationVersion.asString()); + } + ApplicationTitle applicationTitle = new ApplicationTitle(mainClass); + if (applicationTitle.isAvailable()) { + result.put("spring.application.title", applicationTitle.asString()); + } + ApplicationPid applicationPid = new ApplicationPid(); + if (applicationPid.isAvailable()) { + result.put("spring.application.pid", applicationPid.asLong()); + } + return result; + } + +} 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 98b2e6dd95c1..e9fa167a5d92 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 @@ -20,7 +20,6 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -47,6 +46,7 @@ * @author Vedran Pavic * @author Toshiaki Maki * @author Krzysztof Krason + * @author Moritz Halbritter * @since 1.2.0 */ public class ResourceBanner implements Banner { @@ -89,42 +89,29 @@ protected List getPropertyResolvers(Environment environment, C if (environment instanceof ConfigurableEnvironment configurableEnvironment) { configurableEnvironment.getPropertySources().forEach(sources::addLast); } - sources.addLast(getTitleSource(sourceClass)); + sources.addLast(getTitleSource(environment)); sources.addLast(getAnsiSource()); - sources.addLast(getVersionSource(sourceClass)); + sources.addLast(getVersionSource(environment)); List resolvers = new ArrayList<>(); resolvers.add(new PropertySourcesPropertyResolver(sources)); return resolvers; } - private MapPropertySource getTitleSource(Class sourceClass) { - String applicationTitle = getApplicationTitle(sourceClass); - Map titleMap = Collections.singletonMap("application.title", - (applicationTitle != null) ? applicationTitle : ""); - return new MapPropertySource("title", titleMap); - } - - /** - * Return the application title that should be used for the source class. By default - * will use {@link Package#getImplementationTitle()}. - * @param sourceClass the source class - * @return the application title - */ - protected String getApplicationTitle(Class sourceClass) { - Package sourcePackage = (sourceClass != null) ? sourceClass.getPackage() : null; - return (sourcePackage != null) ? sourcePackage.getImplementationTitle() : null; + private MapPropertySource getTitleSource(Environment environment) { + String title = environment.getProperty("spring.application.title"); + return new MapPropertySource("title", Map.of("application.title", (title != null) ? title : "")); } private AnsiPropertySource getAnsiSource() { return new AnsiPropertySource("ansi", true); } - private MapPropertySource getVersionSource(Class sourceClass) { - return new MapPropertySource("version", getVersionsMap(sourceClass)); + private MapPropertySource getVersionSource(Environment environment) { + return new MapPropertySource("version", getVersionsMap(environment)); } - private Map getVersionsMap(Class sourceClass) { - String appVersion = getApplicationVersion(sourceClass); + private Map getVersionsMap(Environment environment) { + String appVersion = environment.getProperty("spring.application.version"); String bootVersion = getBootVersion(); Map versions = new HashMap<>(); versions.put("application.version", getVersionString(appVersion, false)); @@ -134,11 +121,6 @@ private Map getVersionsMap(Class sourceClass) { return versions; } - protected String getApplicationVersion(Class sourceClass) { - Package sourcePackage = (sourceClass != null) ? sourceClass.getPackage() : null; - return (sourcePackage != null) ? sourcePackage.getImplementationVersion() : null; - } - protected String getBootVersion() { return SpringBootVersion.getVersion(); } 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 480d9a94d3e9..7e80d53c38b3 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 @@ -525,6 +525,7 @@ protected void configurePropertySources(ConfigurableEnvironment environment, Str if (!CollectionUtils.isEmpty(this.defaultProperties)) { DefaultPropertiesPropertySource.addOrMerge(this.defaultProperties, sources); } + sources.addLast(new ApplicationInfoPropertySource(this.mainApplicationClass)); if (this.addCommandLineProperties && args.length > 0) { String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME; if (sources.contains(name)) { 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 f42ecfcc26dd..b883105f1a55 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.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 @@ */ 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 c471905bac40..bdb99a9fd8b7 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.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 @@ public StructuredLogLayout build() { 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 5b342650884e..3a70edd73339 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.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 c4539e9cb053..b8f1d15cb69d 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.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 @@ private void addAvailableParameters(AvailableParameters availableParameters) { 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 9d448ef759ef..2f7810dd67e5 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 eb5193a770cb..c5bbdd5d0f55 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 ch.qos.logback.classic.pattern.ThrowableProxyConverter; -import org.springframework.boot.system.ApplicationPid; import org.springframework.core.env.Environment; /** @@ -29,8 +28,6 @@ * 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 71fa9d23baf6..4958f6b98801 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.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 StructuredLogFormatterFactory(Class logEventType, Environment environm 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/java/org/springframework/boot/system/ApplicationPid.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/system/ApplicationPid.java index 88d998c3d66c..117c33438920 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/system/ApplicationPid.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/system/ApplicationPid.java @@ -71,7 +71,7 @@ public boolean isAvailable() { * @return the application PID or {@code null} * @since 3.4.0 */ - public Long toLong() { + public Long asLong() { return this.pid; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/system/ApplicationTitle.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/system/ApplicationTitle.java new file mode 100644 index 000000000000..92e8473a9e73 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/system/ApplicationTitle.java @@ -0,0 +1,63 @@ +/* + * 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.system; + +/** + * Application title. + * + * @author Moritz Halbritter + * @since 3.4.0 + */ +public class ApplicationTitle { + + private final String title; + + public ApplicationTitle(Class applicationClass) { + this(readTitle(applicationClass)); + } + + protected ApplicationTitle(String title) { + this.title = title; + } + + /** + * Returns the application title or {@code null}. + * @return the application title or {@code null} + */ + public String asString() { + return this.title; + } + + /** + * Whether the application title is available. + * @return whether the application title is available + */ + public boolean isAvailable() { + return this.title != null; + } + + @Override + public String toString() { + return (this.title != null) ? this.title : "???"; + } + + private static String readTitle(Class applicationClass) { + Package sourcePackage = (applicationClass != null) ? applicationClass.getPackage() : null; + return (sourcePackage != null) ? sourcePackage.getImplementationTitle() : null; + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/system/ApplicationVersion.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/system/ApplicationVersion.java new file mode 100644 index 000000000000..532caa587fe2 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/system/ApplicationVersion.java @@ -0,0 +1,63 @@ +/* + * 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.system; + +/** + * Application version. + * + * @author Moritz Halbritter + * @since 3.4.0 + */ +public class ApplicationVersion { + + private final String version; + + public ApplicationVersion(Class applicationClass) { + this(readVersion(applicationClass)); + } + + protected ApplicationVersion(String version) { + this.version = version; + } + + /** + * Returns the application version or {@code null}. + * @return the application version or {@code null} + */ + public String asString() { + return this.version; + } + + /** + * Whether the application version is available. + * @return whether the application version is available + */ + public boolean isAvailable() { + return this.version != null; + } + + @Override + public String toString() { + return (this.version != null) ? this.version : "???"; + } + + private static String readVersion(Class applicationClass) { + Package sourcePackage = (applicationClass != null) ? applicationClass.getPackage() : null; + return (sourcePackage != null) ? sourcePackage.getImplementationVersion() : null; + } + +} 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 8ba272032d46..9eac992ab0dc 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.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,24 @@ void renderWithDefaultValues() { @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"), "2", null); 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), applicationVersion, applicationTitle); } - private String printBanner(ResourceBanner banner) { - ConfigurableEnvironment environment = new MockEnvironment(); + private String printBanner(ResourceBanner banner, String applicationVersion, String applicationTitle) { + MockEnvironment environment = new MockEnvironment(); + if (applicationVersion != null) { + environment.setProperty("spring.application.version", applicationVersion); + } + if (applicationTitle != null) { + environment.setProperty("spring.application.title", applicationTitle); + } Map source = Collections.singletonMap("a", "1"); environment.getPropertySources().addLast(new MapPropertySource("map", source)); ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -166,15 +171,9 @@ static class MockResourceBanner extends ResourceBanner { 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) { super(resource); this.bootVersion = bootVersion; - this.applicationVersion = applicationVersion; - this.applicationTitle = applicationTitle; } @Override @@ -182,23 +181,12 @@ protected String getBootVersion() { return this.bootVersion; } - @Override - protected String getApplicationVersion(Class sourceClass) { - return this.applicationVersion; - } - - @Override - protected String getApplicationTitle(Class sourceClass) { - return this.applicationTitle; - } - } static class MutatingResourceBanner extends MockResourceBanner { - MutatingResourceBanner(Resource resource, String bootVersion, String applicationVersion, - String applicationTitle) { - super(resource, bootVersion, applicationVersion, applicationTitle); + MutatingResourceBanner(Resource resource, String bootVersion) { + super(resource, bootVersion); } @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 ff7dc5113aa3..5171a11bb882 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.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 e7e71108953a..64e56d6a7756 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.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 @@ void shouldSupportCustomFormat() { @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 @@ public String format(LogEvent event) { 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 4e27000fb457..d59bed968fa4 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 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 b9180a1f7fa6..7015ec3bae60 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.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 @@ void shouldSupportCustomFormat() { @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 @@ public String format(ILoggingEvent event) { 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/system/ApplicationPidTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/system/ApplicationPidTests.java index 7f1a783efd07..2a01708e8a8b 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. @@ -71,15 +71,15 @@ void writeNewPid() throws Exception { } @Test - void toLong() { + void asLong() { ApplicationPid pid = new ApplicationPid(123L); - assertThat(pid.toLong()).isEqualTo(123L); + assertThat(pid.asLong()).isEqualTo(123L); } @Test - void toLongWhenNotAvailable() { + void asLongWhenNotAvailable() { ApplicationPid pid = new ApplicationPid(null); - assertThat(pid.toLong()).isNull(); + assertThat(pid.asLong()).isNull(); } @Test 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 31c585c6f37a..227ef612420a 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,22 +20,22 @@ 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 Environment environment; - public CustomStructuredLogFormatter(ApplicationPid pid) { - this.pid = pid; + public CustomStructuredLogFormatter(Environment environment) { + this.environment = environment; } @Override public String format(LogEvent event) { StringBuilder result = new StringBuilder(); result.append("epoch=").append(event.getInstant().getEpochMillisecond()); - if (this.pid.isAvailable()) { - result.append(" pid=").append(this.pid); + if (this.environment.containsProperty("spring.application.pid")) { + result.append(" pid=").append(this.environment.getProperty("spring.application.pid")); } result.append(" msg=\"").append(event.getMessage().getFormattedMessage()).append('"'); ThrowableProxy throwable = event.getThrownProxy(); 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 8ea3f2cdf801..5df7d3c3bf7c 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.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 Environment environment; private final ThrowableProxyConverter throwableProxyConverter; - public CustomStructuredLogFormatter(ApplicationPid pid, ThrowableProxyConverter throwableProxyConverter) { - this.pid = pid; + public CustomStructuredLogFormatter(Environment environment, ThrowableProxyConverter throwableProxyConverter) { + this.environment = environment; this.throwableProxyConverter = throwableProxyConverter; } @@ -38,8 +38,8 @@ public CustomStructuredLogFormatter(ApplicationPid pid, ThrowableProxyConverter public String format(ILoggingEvent event) { StringBuilder result = new StringBuilder(); result.append("epoch=").append(event.getInstant().toEpochMilli()); - if (this.pid.isAvailable()) { - result.append(" pid=").append(this.pid); + if (this.environment.containsProperty("spring.application.pid")) { + result.append(" pid=").append(this.environment.getProperty("spring.application.pid")); } result.append(" msg=\"").append(event.getFormattedMessage()).append('"'); IThrowableProxy throwable = event.getThrowableProxy();