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
This commit is contained in:
Moritz Halbritter
2024-07-31 13:33:18 +02:00
parent 518bc69bb7
commit f4b4f4f0bf
22 changed files with 267 additions and 96 deletions

View File

@@ -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

View File

@@ -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<String, Object> getProperties(String applicationVersion) {
Map<String, Object> 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);
}
}
}

View File

@@ -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<PropertyResolver> 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<String, Object> getVersionsMap(Class<?> sourceClass) {
private Map<String, Object> getVersionsMap(Class<?> sourceClass, Environment environment) {
String appVersion = getApplicationVersion(sourceClass);
if (appVersion == null) {
appVersion = getApplicationVersion(environment);
}
String bootVersion = getBootVersion();
Map<String, Object> 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() {

View File

@@ -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));
}
/**

View File

@@ -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<LogEvent> {
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<LogEvent> members) {
private static void jsonMembers(Environment environment, JsonWriter.Members<LogEvent> 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)

View File

@@ -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<LogEvent> 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());
}

View File

@@ -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<KeyValuePair> 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<ILoggingEvent> members) {
private static void jsonMembers(Environment environment, ThrowableProxyConverter throwableProxyConverter,
JsonWriter.Members<ILoggingEvent> 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);

View File

@@ -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<ILoggingEvent> {
private void addCommonFormatters(CommonFormatters<ILoggingEvent> 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)));

View File

@@ -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) {

View File

@@ -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:
* <ul>
* <li>{@link Environment}</li>
* <li>{@link ApplicationPid}</li>
* <li>{@link ElasticCommonSchemaService}</li>
* </ul>
* When using Logback, implementing classes can also use the following parameter types in
* the constructor:

View File

@@ -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<E> {
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);
}

View File

@@ -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",

View File

@@ -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<String> propertySources = environment.getPropertySources().stream().map(PropertySource::getName).toList();
assertThat(propertySources).containsExactly("applicationInfo", "first", "second", "mockProperties");
ApplicationInfoPropertySource.moveToEnd(environment);
List<String> propertySourcesAfterMove = environment.getPropertySources()
.stream()
.map(PropertySource::getName)
.toList();
assertThat(propertySourcesAfterMove).containsExactly("first", "second", "mockProperties", "applicationInfo");
}
}

View File

@@ -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<String, Object> 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

View File

@@ -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

View File

@@ -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<LogEvent> {
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");
}
}

View File

@@ -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

View File

@@ -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<ILoggingEvent> {
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;
}
}

View File

@@ -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();

View File

@@ -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.

View File

@@ -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<LogEvent> {
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('"');

View File

@@ -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<ILoggingEvent> {
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<ILog
public String format(ILoggingEvent event) {
StringBuilder result = new StringBuilder();
result.append("epoch=").append(event.getInstant().toEpochMilli());
if (this.pid.isAvailable()) {
if (this.pid != null) {
result.append(" pid=").append(this.pid);
}
result.append(" msg=\"").append(event.getFormattedMessage()).append('"');