Skip to content

Commit f4b4f4f

Browse files
committed
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
1 parent 518bc69 commit f4b4f4f

File tree

22 files changed

+267
-96
lines changed

22 files changed

+267
-96
lines changed

spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringBootContextLoaderTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,11 @@ void propertySourceOrdering() {
160160
.stream()
161161
.map(PropertySource::getName)
162162
.collect(Collectors.toCollection(ArrayList::new));
163-
String last = names.remove(names.size() - 1);
163+
String configResource = names.remove(names.size() - 2);
164164
assertThat(names).containsExactly("configurationProperties", "Inlined Test Properties", "commandLineArgs",
165165
"servletConfigInitParams", "servletContextInitParams", "systemProperties", "systemEnvironment",
166-
"random");
167-
assertThat(last).startsWith("Config resource");
166+
"random", "applicationInfo");
167+
assertThat(configResource).startsWith("Config resource");
168168
}
169169

170170
@Test
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot;
18+
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
22+
import org.springframework.boot.system.ApplicationPid;
23+
import org.springframework.core.env.ConfigurableEnvironment;
24+
import org.springframework.core.env.MapPropertySource;
25+
import org.springframework.core.env.MutablePropertySources;
26+
import org.springframework.core.env.PropertySource;
27+
import org.springframework.util.StringUtils;
28+
29+
/**
30+
* {@link PropertySource} which provides information about the application, like the
31+
* process ID (PID) or the version.
32+
*
33+
* @author Moritz Halbritter
34+
*/
35+
class ApplicationInfoPropertySource extends MapPropertySource {
36+
37+
static final String NAME = "applicationInfo";
38+
39+
ApplicationInfoPropertySource(Class<?> mainClass) {
40+
super(NAME, getProperties(readVersion(mainClass)));
41+
}
42+
43+
ApplicationInfoPropertySource(String applicationVersion) {
44+
super(NAME, getProperties(applicationVersion));
45+
}
46+
47+
private static Map<String, Object> getProperties(String applicationVersion) {
48+
Map<String, Object> result = new HashMap<>();
49+
if (StringUtils.hasText(applicationVersion)) {
50+
result.put("spring.application.version", applicationVersion);
51+
}
52+
ApplicationPid applicationPid = new ApplicationPid();
53+
if (applicationPid.isAvailable()) {
54+
result.put("spring.application.pid", applicationPid.toLong());
55+
}
56+
return result;
57+
}
58+
59+
private static String readVersion(Class<?> applicationClass) {
60+
Package sourcePackage = (applicationClass != null) ? applicationClass.getPackage() : null;
61+
return (sourcePackage != null) ? sourcePackage.getImplementationVersion() : null;
62+
}
63+
64+
/**
65+
* Moves the {@link ApplicationInfoPropertySource} to the end of the environment's
66+
* property sources.
67+
* @param environment the environment
68+
*/
69+
static void moveToEnd(ConfigurableEnvironment environment) {
70+
MutablePropertySources propertySources = environment.getPropertySources();
71+
PropertySource<?> propertySource = propertySources.remove(NAME);
72+
if (propertySource != null) {
73+
propertySources.addLast(propertySource);
74+
}
75+
}
76+
77+
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ResourceBanner.java

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
* @author Vedran Pavic
4848
* @author Toshiaki Maki
4949
* @author Krzysztof Krason
50+
* @author Moritz Halbritter
5051
* @since 1.2.0
5152
*/
5253
public class ResourceBanner implements Banner {
@@ -91,7 +92,7 @@ protected List<PropertyResolver> getPropertyResolvers(Environment environment, C
9192
}
9293
sources.addLast(getTitleSource(sourceClass));
9394
sources.addLast(getAnsiSource());
94-
sources.addLast(getVersionSource(sourceClass));
95+
sources.addLast(getVersionSource(sourceClass, environment));
9596
List<PropertyResolver> resolvers = new ArrayList<>();
9697
resolvers.add(new PropertySourcesPropertyResolver(sources));
9798
return resolvers;
@@ -119,12 +120,15 @@ private AnsiPropertySource getAnsiSource() {
119120
return new AnsiPropertySource("ansi", true);
120121
}
121122

122-
private MapPropertySource getVersionSource(Class<?> sourceClass) {
123-
return new MapPropertySource("version", getVersionsMap(sourceClass));
123+
private MapPropertySource getVersionSource(Class<?> sourceClass, Environment environment) {
124+
return new MapPropertySource("version", getVersionsMap(sourceClass, environment));
124125
}
125126

126-
private Map<String, Object> getVersionsMap(Class<?> sourceClass) {
127+
private Map<String, Object> getVersionsMap(Class<?> sourceClass, Environment environment) {
127128
String appVersion = getApplicationVersion(sourceClass);
129+
if (appVersion == null) {
130+
appVersion = getApplicationVersion(environment);
131+
}
128132
String bootVersion = getBootVersion();
129133
Map<String, Object> versions = new HashMap<>();
130134
versions.put("application.version", getVersionString(appVersion, false));
@@ -134,9 +138,19 @@ private Map<String, Object> getVersionsMap(Class<?> sourceClass) {
134138
return versions;
135139
}
136140

141+
/**
142+
* Returns the application version.
143+
* @param sourceClass the source class
144+
* @return the application version or {@code null} if unknown
145+
* @deprecated since 3.4.0 for removal in 3.6.0
146+
*/
147+
@Deprecated(since = "3.4.0", forRemoval = true)
137148
protected String getApplicationVersion(Class<?> sourceClass) {
138-
Package sourcePackage = (sourceClass != null) ? sourceClass.getPackage() : null;
139-
return (sourcePackage != null) ? sourcePackage.getImplementationVersion() : null;
149+
return null;
150+
}
151+
152+
private String getApplicationVersion(Environment environment) {
153+
return environment.getProperty("spring.application.version");
140154
}
141155

142156
protected String getBootVersion() {

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners
368368
configureEnvironment(environment, applicationArguments.getSourceArgs());
369369
ConfigurationPropertySources.attach(environment);
370370
listeners.environmentPrepared(bootstrapContext, environment);
371+
ApplicationInfoPropertySource.moveToEnd(environment);
371372
DefaultPropertiesPropertySource.moveToEnd(environment);
372373
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
373374
"Environment prefix cannot be set via properties.");
@@ -539,6 +540,7 @@ protected void configurePropertySources(ConfigurableEnvironment environment, Str
539540
sources.addFirst(new SimpleCommandLinePropertySource(args));
540541
}
541542
}
543+
environment.getPropertySources().addLast(new ApplicationInfoPropertySource(this.mainApplicationClass));
542544
}
543545

544546
/**

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatter.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.boot.logging.log4j2;
1818

19+
import java.util.Objects;
20+
1921
import org.apache.logging.log4j.Level;
2022
import org.apache.logging.log4j.core.LogEvent;
2123
import org.apache.logging.log4j.core.impl.ThrowableProxy;
@@ -28,7 +30,7 @@
2830
import org.springframework.boot.logging.structured.ElasticCommonSchemaService;
2931
import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter;
3032
import org.springframework.boot.logging.structured.StructuredLogFormatter;
31-
import org.springframework.boot.system.ApplicationPid;
33+
import org.springframework.core.env.Environment;
3234
import org.springframework.util.ObjectUtils;
3335

3436
/**
@@ -40,17 +42,17 @@
4042
*/
4143
class ElasticCommonSchemaStructuredLogFormatter extends JsonWriterStructuredLogFormatter<LogEvent> {
4244

43-
ElasticCommonSchemaStructuredLogFormatter(ApplicationPid pid, ElasticCommonSchemaService service) {
44-
super((members) -> jsonMembers(pid, service, members));
45+
ElasticCommonSchemaStructuredLogFormatter(Environment environment) {
46+
super((members) -> jsonMembers(environment, members));
4547
}
4648

47-
private static void jsonMembers(ApplicationPid pid, ElasticCommonSchemaService service,
48-
JsonWriter.Members<LogEvent> members) {
49+
private static void jsonMembers(Environment environment, JsonWriter.Members<LogEvent> members) {
4950
members.add("@timestamp", LogEvent::getInstant).as(ElasticCommonSchemaStructuredLogFormatter::asTimestamp);
5051
members.add("log.level", LogEvent::getLevel).as(Level::name);
51-
members.add("process.pid", pid).when(ApplicationPid::isAvailable).as(ApplicationPid::toLong);
52+
members.add("process.pid", environment.getProperty("spring.application.pid", Long.class))
53+
.when(Objects::nonNull);
5254
members.add("process.thread.name", LogEvent::getThreadName);
53-
service.jsonMembers(members);
55+
ElasticCommonSchemaService.get(environment).jsonMembers(members);
5456
members.add("log.logger", LogEvent::getLoggerName);
5557
members.add("message", LogEvent::getMessage).as(Message::getFormattedMessage);
5658
members.from(LogEvent::getContextData)

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/StructuredLogLayout.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,9 @@
3030
import org.apache.logging.log4j.core.layout.AbstractStringLayout;
3131

3232
import org.springframework.boot.logging.structured.CommonStructuredLogFormat;
33-
import org.springframework.boot.logging.structured.ElasticCommonSchemaService;
3433
import org.springframework.boot.logging.structured.StructuredLogFormatter;
3534
import org.springframework.boot.logging.structured.StructuredLogFormatterFactory;
3635
import org.springframework.boot.logging.structured.StructuredLogFormatterFactory.CommonFormatters;
37-
import org.springframework.boot.system.ApplicationPid;
3836
import org.springframework.core.env.Environment;
3937
import org.springframework.util.Assert;
4038

@@ -106,8 +104,7 @@ public StructuredLogLayout build() {
106104
private void addCommonFormatters(CommonFormatters<LogEvent> commonFormatters) {
107105
commonFormatters.add(CommonStructuredLogFormat.ELASTIC_COMMON_SCHEMA,
108106
(instantiator) -> new ElasticCommonSchemaStructuredLogFormatter(
109-
instantiator.getArg(ApplicationPid.class),
110-
instantiator.getArg(ElasticCommonSchemaService.class)));
107+
instantiator.getArg(Environment.class)));
111108
commonFormatters.add(CommonStructuredLogFormat.LOGSTASH,
112109
(instantiator) -> new LogstashStructuredLogFormatter());
113110
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/ElasticCommonSchemaStructuredLogFormatter.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.boot.logging.logback;
1818

19+
import java.util.Objects;
20+
1921
import ch.qos.logback.classic.pattern.ThrowableProxyConverter;
2022
import ch.qos.logback.classic.spi.ILoggingEvent;
2123
import ch.qos.logback.classic.spi.IThrowableProxy;
@@ -27,7 +29,7 @@
2729
import org.springframework.boot.logging.structured.ElasticCommonSchemaService;
2830
import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter;
2931
import org.springframework.boot.logging.structured.StructuredLogFormatter;
30-
import org.springframework.boot.system.ApplicationPid;
32+
import org.springframework.core.env.Environment;
3133

3234
/**
3335
* Logback {@link StructuredLogFormatter} for
@@ -41,18 +43,19 @@ class ElasticCommonSchemaStructuredLogFormatter extends JsonWriterStructuredLogF
4143
private static final PairExtractor<KeyValuePair> keyValuePairExtractor = PairExtractor.of((pair) -> pair.key,
4244
(pair) -> pair.value);
4345

44-
ElasticCommonSchemaStructuredLogFormatter(ApplicationPid pid, ElasticCommonSchemaService service,
46+
ElasticCommonSchemaStructuredLogFormatter(Environment environment,
4547
ThrowableProxyConverter throwableProxyConverter) {
46-
super((members) -> jsonMembers(pid, service, throwableProxyConverter, members));
48+
super((members) -> jsonMembers(environment, throwableProxyConverter, members));
4749
}
4850

49-
private static void jsonMembers(ApplicationPid pid, ElasticCommonSchemaService service,
50-
ThrowableProxyConverter throwableProxyConverter, JsonWriter.Members<ILoggingEvent> members) {
51+
private static void jsonMembers(Environment environment, ThrowableProxyConverter throwableProxyConverter,
52+
JsonWriter.Members<ILoggingEvent> members) {
5153
members.add("@timestamp", ILoggingEvent::getInstant);
5254
members.add("log.level", ILoggingEvent::getLevel);
53-
members.add("process.pid", pid).when(ApplicationPid::isAvailable).as(ApplicationPid::toLong);
55+
members.add("process.pid", environment.getProperty("spring.application.pid", Long.class))
56+
.when(Objects::nonNull);
5457
members.add("process.thread.name", ILoggingEvent::getThreadName);
55-
service.jsonMembers(members);
58+
ElasticCommonSchemaService.get(environment).jsonMembers(members);
5659
members.add("log.logger", ILoggingEvent::getLoggerName);
5760
members.add("message", ILoggingEvent::getFormattedMessage);
5861
members.addMapEntries(ILoggingEvent::getMDCPropertyMap);

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/StructuredLogEncoder.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,9 @@
2525
import ch.qos.logback.core.encoder.EncoderBase;
2626

2727
import org.springframework.boot.logging.structured.CommonStructuredLogFormat;
28-
import org.springframework.boot.logging.structured.ElasticCommonSchemaService;
2928
import org.springframework.boot.logging.structured.StructuredLogFormatter;
3029
import org.springframework.boot.logging.structured.StructuredLogFormatterFactory;
3130
import org.springframework.boot.logging.structured.StructuredLogFormatterFactory.CommonFormatters;
32-
import org.springframework.boot.system.ApplicationPid;
3331
import org.springframework.boot.util.Instantiator.AvailableParameters;
3432
import org.springframework.core.env.Environment;
3533
import org.springframework.util.Assert;
@@ -82,9 +80,7 @@ private void addAvailableParameters(AvailableParameters availableParameters) {
8280

8381
private void addCommonFormatters(CommonFormatters<ILoggingEvent> commonFormatters) {
8482
commonFormatters.add(CommonStructuredLogFormat.ELASTIC_COMMON_SCHEMA,
85-
(instantiator) -> new ElasticCommonSchemaStructuredLogFormatter(
86-
instantiator.getArg(ApplicationPid.class),
87-
instantiator.getArg(ElasticCommonSchemaService.class),
83+
(instantiator) -> new ElasticCommonSchemaStructuredLogFormatter(instantiator.getArg(Environment.class),
8884
instantiator.getArg(ThrowableProxyConverter.class)));
8985
commonFormatters.add(CommonStructuredLogFormat.LOGSTASH, (instantiator) -> new LogstashStructuredLogFormatter(
9086
instantiator.getArg(ThrowableProxyConverter.class)));

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/ElasticCommonSchemaService.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ public record ElasticCommonSchemaService(String name, String version, String env
3838

3939
private ElasticCommonSchemaService withDefaults(Environment environment) {
4040
String name = withFallbackProperty(environment, this.name, "spring.application.name");
41-
return new ElasticCommonSchemaService(name, this.version, this.environment, this.nodeName);
41+
String version = withFallbackProperty(environment, this.version, "spring.application.version");
42+
return new ElasticCommonSchemaService(name, version, this.environment, this.nodeName);
4243
}
4344

4445
private String withFallbackProperty(Environment environment, String value, String property) {

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLogFormatter.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020

2121
import ch.qos.logback.classic.pattern.ThrowableProxyConverter;
2222

23-
import org.springframework.boot.system.ApplicationPid;
2423
import org.springframework.core.env.Environment;
2524

2625
/**
@@ -29,8 +28,6 @@
2928
* Implementing classes can declare the following parameter types in the constructor:
3029
* <ul>
3130
* <li>{@link Environment}</li>
32-
* <li>{@link ApplicationPid}</li>
33-
* <li>{@link ElasticCommonSchemaService}</li>
3431
* </ul>
3532
* When using Logback, implementing classes can also use the following parameter types in
3633
* the constructor:

0 commit comments

Comments
 (0)