diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/logging.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/logging.adoc index 1488b4626d..1e0792d5cf 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/logging.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/logging.adoc @@ -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. diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcessor.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcessor.java index 79cdc396da..327bd427f9 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcessor.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcessor.java @@ -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 { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLoggingJsonProperties.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLoggingJsonProperties.java index cc1f6fc42e..ff026894c8 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLoggingJsonProperties.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLoggingJsonProperties.java @@ -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 include, Set exclude, Map rename, - Map add, Class> customizer, - Set>> customizers) { + Map add, Set>> customizer) { - Collection> allCustomizers(Instantiator instantiator) { - return allCustomizers().stream().map((customizer) -> instantiateCustomizer(instantiator, customizer)).toList(); + StructuredLoggingJsonProperties { + customizer = (customizer != null) ? customizer : Collections.emptySet(); } - Set>> allCustomizers() { - return merge(customizer(), customizers()); - } - - private Set merge(T element, Set elements) { - if (CollectionUtils.isEmpty(elements)) { - return (element != null) ? Set.of(element) : Collections.emptySet(); - } - if (element == null) { - return elements; - } - Set result = new LinkedHashSet<>(elements.size() + 1); - result.add(element); - result.addAll(elements); - return result; - } - - @SuppressWarnings("unchecked") - private StructuredLoggingJsonMembersCustomizer instantiateCustomizer(Instantiator instantiator, - Class> customizer) { - return (StructuredLoggingJsonMembersCustomizer) instantiator.instantiateType(customizer); + @SuppressWarnings({ "unchecked", "rawtypes" }) + Collection> customizers(Instantiator instantiator) { + return (List) customizer().stream().map(instantiator::instantiateType).toList(); } static StructuredLoggingJsonProperties get(Environment environment) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLoggingJsonPropertiesJsonMembersCustomizer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLoggingJsonPropertiesJsonMembersCustomizer.java index 389a3cc555..8768e8f473 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLoggingJsonPropertiesJsonMembersCustomizer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLoggingJsonPropertiesJsonMembersCustomizer.java @@ -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) { diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json index ea31f4d157..39dd08691b 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -266,15 +266,10 @@ "type": "java.util.Map", "description": "Additional members that should be added to structured logging JSON" }, - { - "name": "logging.structured.json.customizer", - "type": "java.lang.Class>", - "description": "The fully qualified class name of a StructuredLoggingJsonMembersCustomizer" - }, { "name": "logging.structured.json.customizers", "type": "java.util.Set>>", - "description": "The fully qualified class names of a StructuredLoggingJsonMembersCustomizer" + "description": "Fully qualified class names of StructuredLoggingJsonMembersCustomizer implementations" }, { "name": "logging.structured.json.exclude", diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/structured/StructuredLoggingJsonPropertiesJsonMembersCustomizerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/structured/StructuredLoggingJsonPropertiesJsonMembersCustomizerTests.java index 2a34bc7e85..6911d07fd8 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/structured/StructuredLoggingJsonPropertiesJsonMembersCustomizerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/structured/StructuredLoggingJsonPropertiesJsonMembersCustomizerTests.java @@ -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) -> { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/structured/StructuredLoggingJsonPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/structured/StructuredLoggingJsonPropertiesTests.java index 26b557085d..ceeaf63edf 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/structured/StructuredLoggingJsonPropertiesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/structured/StructuredLoggingJsonPropertiesTests.java @@ -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); }