Use a single customizer property for JSON logging

Update `StructuredLoggingJsonProperties` no that only a single
`customizer` property is supported.

See gh-43368
This commit is contained in:
Phillip Webb
2025-01-29 11:09:24 -08:00
parent 035c80aa5b
commit c1cb4c51ed
7 changed files with 21 additions and 61 deletions

View File

@@ -642,7 +642,7 @@ logging:
----
TIP: For more advanced customizations, you can use the javadoc:org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer[] interface.
You can reference a single implementation using the configprop:logging.structured.json.customizer[] property, or use configprop:logging.structured.json.customizers[] if you have more than one.
You can reference a one or more implementations using the configprop:logging.structured.json.customizer[] property.
You can also declare implementations by listing them in a `META-INF/spring.factories` file.

View File

@@ -45,7 +45,7 @@ class StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcesso
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
Environment environment = beanFactory.getBean(ENVIRONMENT_BEAN_NAME, Environment.class);
StructuredLoggingJsonProperties properties = StructuredLoggingJsonProperties.get(environment);
return (properties != null) ? AotContribution.get(properties.allCustomizers()) : null;
return (properties != null) ? AotContribution.get(properties.customizer()) : null;
}
private static final class AotContribution implements BeanFactoryInitializationAotContribution {

View File

@@ -18,7 +18,7 @@ package org.springframework.boot.logging.structured;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -26,7 +26,6 @@ import org.springframework.boot.context.properties.bind.BindableRuntimeHintsRegi
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.util.Instantiator;
import org.springframework.core.env.Environment;
import org.springframework.util.CollectionUtils;
/**
* Properties that can be used to customize structured logging JSON.
@@ -35,42 +34,21 @@ import org.springframework.util.CollectionUtils;
* @param exclude the paths that should be excluded. An empty set excludes nothing
* @param rename a map of path to replacement names
* @param add a map of additional elements {@link StructuredLoggingJsonMembersCustomizer}
* @param customizer the fully qualified name of a
* {@link StructuredLoggingJsonMembersCustomizer} implementation
* @param customizers the fully qualified names of
* @param customizer the fully qualified names of
* {@link StructuredLoggingJsonMembersCustomizer} implementations
* @author Phillip Webb
* @author Yanming Zhou
*/
record StructuredLoggingJsonProperties(Set<String> include, Set<String> exclude, Map<String, String> rename,
Map<String, String> add, Class<? extends StructuredLoggingJsonMembersCustomizer<?>> customizer,
Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizers) {
Map<String, String> add, Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizer) {
Collection<StructuredLoggingJsonMembersCustomizer<Object>> allCustomizers(Instantiator<?> instantiator) {
return allCustomizers().stream().map((customizer) -> instantiateCustomizer(instantiator, customizer)).toList();
StructuredLoggingJsonProperties {
customizer = (customizer != null) ? customizer : Collections.emptySet();
}
Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> allCustomizers() {
return merge(customizer(), customizers());
}
private <T> Set<T> merge(T element, Set<T> elements) {
if (CollectionUtils.isEmpty(elements)) {
return (element != null) ? Set.of(element) : Collections.emptySet();
}
if (element == null) {
return elements;
}
Set<T> result = new LinkedHashSet<>(elements.size() + 1);
result.add(element);
result.addAll(elements);
return result;
}
@SuppressWarnings("unchecked")
private StructuredLoggingJsonMembersCustomizer<Object> instantiateCustomizer(Instantiator<?> instantiator,
Class<? extends StructuredLoggingJsonMembersCustomizer<?>> customizer) {
return (StructuredLoggingJsonMembersCustomizer<Object>) instantiator.instantiateType(customizer);
@SuppressWarnings({ "unchecked", "rawtypes" })
Collection<StructuredLoggingJsonMembersCustomizer<Object>> customizers(Instantiator<?> instantiator) {
return (List) customizer().stream().map(instantiator::instantiateType).toList();
}
static StructuredLoggingJsonProperties get(Environment environment) {

View File

@@ -50,7 +50,7 @@ class StructuredLoggingJsonPropertiesJsonMembersCustomizer implements Structured
if (!CollectionUtils.isEmpty(add)) {
add.forEach(members::add);
}
this.properties.allCustomizers(this.instantiator).forEach((customizer) -> customizer.customize(members));
this.properties.customizers(this.instantiator).forEach((customizer) -> customizer.customize(members));
}
String renameJsonMembers(MemberPath path, String existingName) {

View File

@@ -266,15 +266,10 @@
"type": "java.util.Map<java.lang.String,java.lang.String>",
"description": "Additional members that should be added to structured logging JSON"
},
{
"name": "logging.structured.json.customizer",
"type": "java.lang.Class<? extends org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer<?>>",
"description": "The fully qualified class name of a StructuredLoggingJsonMembersCustomizer"
},
{
"name": "logging.structured.json.customizers",
"type": "java.util.Set<java.lang.Class<? extends org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer<?>>>",
"description": "The fully qualified class names of a StructuredLoggingJsonMembersCustomizer"
"description": "Fully qualified class names of StructuredLoggingJsonMembersCustomizer implementations"
},
{
"name": "logging.structured.json.exclude",

View File

@@ -48,7 +48,7 @@ class StructuredLoggingJsonPropertiesJsonMembersCustomizerTests {
@Test
void customizeWhenHasExcludeFiltersMember() {
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Collections.emptySet(),
Set.of("a"), Collections.emptyMap(), Collections.emptyMap(), null, null);
Set.of("a"), Collections.emptyMap(), Collections.emptyMap(), null);
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
this.instantiator, properties);
assertThat(writeSampleJson(customizer)).doesNotContain("a").contains("b");
@@ -57,7 +57,7 @@ class StructuredLoggingJsonPropertiesJsonMembersCustomizerTests {
@Test
void customizeWhenHasIncludeFiltersOtherMembers() {
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Set.of("a"),
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), null, null);
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), null);
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
this.instantiator, properties);
assertThat(writeSampleJson(customizer)).contains("a")
@@ -69,7 +69,7 @@ class StructuredLoggingJsonPropertiesJsonMembersCustomizerTests {
@Test
void customizeWhenHasIncludeAndExcludeFiltersMembers() {
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Set.of("a", "b"), Set.of("b"),
Collections.emptyMap(), Collections.emptyMap(), null, null);
Collections.emptyMap(), Collections.emptyMap(), null);
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
this.instantiator, properties);
assertThat(writeSampleJson(customizer)).contains("a")
@@ -81,7 +81,7 @@ class StructuredLoggingJsonPropertiesJsonMembersCustomizerTests {
@Test
void customizeWhenHasRenameRenamesMember() {
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Collections.emptySet(),
Collections.emptySet(), Map.of("a", "z"), Collections.emptyMap(), null, null);
Collections.emptySet(), Map.of("a", "z"), Collections.emptyMap(), null);
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
this.instantiator, properties);
assertThat(writeSampleJson(customizer)).contains("\"z\":\"a\"");
@@ -90,7 +90,7 @@ class StructuredLoggingJsonPropertiesJsonMembersCustomizerTests {
@Test
void customizeWhenHasAddAddsMemeber() {
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Collections.emptySet(),
Collections.emptySet(), Collections.emptyMap(), Map.of("z", "z"), null, null);
Collections.emptySet(), Collections.emptyMap(), Map.of("z", "z"), null);
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
this.instantiator, properties);
assertThat(writeSampleJson(customizer)).contains("\"z\":\"z\"");
@@ -103,7 +103,7 @@ class StructuredLoggingJsonPropertiesJsonMembersCustomizerTests {
.applyingNameProcessor(NameProcessor.of(String::toUpperCase));
given(((Instantiator) this.instantiator).instantiateType(TestCustomizer.class)).willReturn(uppercaseCustomizer);
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Collections.emptySet(),
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), TestCustomizer.class, null);
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), Set.of(TestCustomizer.class));
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
this.instantiator, properties);
assertThat(writeSampleJson(customizer)).contains("\"A\":\"a\"");
@@ -115,26 +115,13 @@ class StructuredLoggingJsonPropertiesJsonMembersCustomizerTests {
given(((Instantiator) this.instantiator).instantiateType(FooCustomizer.class)).willReturn(new FooCustomizer());
given(((Instantiator) this.instantiator).instantiateType(BarCustomizer.class)).willReturn(new BarCustomizer());
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Collections.emptySet(),
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), null,
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(),
Set.of(FooCustomizer.class, BarCustomizer.class));
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
this.instantiator, properties);
assertThat(writeSampleJson(customizer)).contains("\"foo\":\"foo\"").contains("\"bar\":\"bar\"");
}
@Test
@SuppressWarnings({ "rawtypes", "unchecked" })
void customizeWhenHasCustomizerAndCustomizersCustomizesMember() {
given(((Instantiator) this.instantiator).instantiateType(FooCustomizer.class)).willReturn(new FooCustomizer());
given(((Instantiator) this.instantiator).instantiateType(BarCustomizer.class)).willReturn(new BarCustomizer());
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Collections.emptySet(),
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), FooCustomizer.class,
Set.of(BarCustomizer.class));
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
this.instantiator, properties);
assertThat(writeSampleJson(customizer)).contains("\"foo\":\"foo\"").contains("\"bar\":\"bar\"");
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private String writeSampleJson(StructuredLoggingJsonMembersCustomizer customizer) {
return JsonWriter.of((members) -> {

View File

@@ -48,7 +48,7 @@ class StructuredLoggingJsonPropertiesTests {
environment.setProperty("logging.structured.json.customizer", TestCustomizer.class.getName());
StructuredLoggingJsonProperties properties = StructuredLoggingJsonProperties.get(environment);
assertThat(properties).isEqualTo(new StructuredLoggingJsonProperties(Set.of("a", "b"), Set.of("c", "d"),
Map.of("e", "f"), Map.of("g", "h"), TestCustomizer.class, null));
Map.of("e", "f"), Map.of("g", "h"), Set.of(TestCustomizer.class)));
}
@Test
@@ -64,7 +64,7 @@ class StructuredLoggingJsonPropertiesTests {
assertThat(RuntimeHintsPredicates.reflection().onType(StructuredLoggingJsonProperties.class)).accepts(hints);
assertThat(RuntimeHintsPredicates.reflection()
.onConstructor(StructuredLoggingJsonProperties.class.getDeclaredConstructor(Set.class, Set.class, Map.class,
Map.class, Class.class, Set.class))
Map.class, Set.class))
.invoke()).accepts(hints);
}