Skip to content

Commit d68df32

Browse files
philwebbarefbehboudi
authored andcommitted
Polish 'Add support for multiple StructuredLoggingJsonMembersCustomizers'
See spring-projectsgh-43368 Signed-off-by: arefbehboudi <[email protected]>
1 parent a34ca77 commit d68df32

File tree

7 files changed

+84
-39
lines changed

7 files changed

+84
-39
lines changed

Diff for: spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/logging.adoc

+2-1
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,8 @@ logging:
641641
corpname: mycorp
642642
----
643643

644-
TIP: For more advanced customizations, you can write your own class that implements the javadoc:org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer[] interface and declare it using the configprop:logging.structured.json.customizer[] property.
644+
TIP: For more advanced customizations, you can use the javadoc:org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer[] interface.
645+
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.
645646
You can also declare implementations by listing them in a `META-INF/spring.factories` file.
646647

647648

Diff for: spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcessor.java

+14-12
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616

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

19-
import java.util.Optional;
2019
import java.util.Set;
2120

2221
import org.springframework.aot.generate.GenerationContext;
2322
import org.springframework.aot.hint.MemberCategory;
23+
import org.springframework.aot.hint.ReflectionHints;
2424
import org.springframework.aot.hint.RuntimeHints;
2525
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
2626
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
@@ -34,6 +34,7 @@
3434
*
3535
* @author Dmytro Nosan
3636
* @author Yanming Zhou
37+
* @author Phillip Webb
3738
*/
3839
class StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcessor
3940
implements BeanFactoryInitializationAotProcessor {
@@ -43,27 +44,28 @@ class StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcesso
4344
@Override
4445
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
4546
Environment environment = beanFactory.getBean(ENVIRONMENT_BEAN_NAME, Environment.class);
46-
return Optional.ofNullable(StructuredLoggingJsonProperties.get(environment))
47-
.map(StructuredLoggingJsonProperties::customizer)
48-
.map(AotContribution::new)
49-
.orElse(null);
47+
StructuredLoggingJsonProperties properties = StructuredLoggingJsonProperties.get(environment);
48+
return (properties != null) ? AotContribution.get(properties.allCustomizers()) : null;
5049
}
5150

5251
private static final class AotContribution implements BeanFactoryInitializationAotContribution {
5352

54-
private final Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizer;
53+
private final Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizers;
5554

56-
private AotContribution(Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizer) {
57-
this.customizer = customizer;
55+
private AotContribution(Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizers) {
56+
this.customizers = customizers;
5857
}
5958

6059
@Override
6160
public void applyTo(GenerationContext generationContext,
6261
BeanFactoryInitializationCode beanFactoryInitializationCode) {
63-
this.customizer.forEach((it) -> generationContext.getRuntimeHints()
64-
.reflection()
65-
.registerType(it, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
66-
MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));
62+
ReflectionHints reflection = generationContext.getRuntimeHints().reflection();
63+
this.customizers.forEach((customizer) -> reflection.registerType(customizer,
64+
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));
65+
}
66+
67+
static AotContribution get(Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizers) {
68+
return (!customizers.isEmpty()) ? new AotContribution(customizers) : null;
6769
}
6870

6971
}

Diff for: spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLoggingJsonProperties.java

+37-2
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,17 @@
1616

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

19+
import java.util.Collection;
20+
import java.util.Collections;
21+
import java.util.LinkedHashSet;
1922
import java.util.Map;
2023
import java.util.Set;
2124

2225
import org.springframework.boot.context.properties.bind.BindableRuntimeHintsRegistrar;
2326
import org.springframework.boot.context.properties.bind.Binder;
27+
import org.springframework.boot.util.Instantiator;
2428
import org.springframework.core.env.Environment;
29+
import org.springframework.util.CollectionUtils;
2530

2631
/**
2732
* Properties that can be used to customize structured logging JSON.
@@ -31,12 +36,42 @@
3136
* @param rename a map of path to replacement names
3237
* @param add a map of additional elements {@link StructuredLoggingJsonMembersCustomizer}
3338
* @param customizer the fully qualified name of a
34-
* {@link StructuredLoggingJsonMembersCustomizer}
39+
* {@link StructuredLoggingJsonMembersCustomizer} implementation
40+
* @param customizers the fully qualified names of
41+
* {@link StructuredLoggingJsonMembersCustomizer} implementations
3542
* @author Phillip Webb
3643
* @author Yanming Zhou
3744
*/
3845
record StructuredLoggingJsonProperties(Set<String> include, Set<String> exclude, Map<String, String> rename,
39-
Map<String, String> add, Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizer) {
46+
Map<String, String> add, Class<? extends StructuredLoggingJsonMembersCustomizer<?>> customizer,
47+
Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizers) {
48+
49+
Collection<StructuredLoggingJsonMembersCustomizer<Object>> allCustomizers(Instantiator<?> instantiator) {
50+
return allCustomizers().stream().map((customizer) -> instantiateCustomizer(instantiator, customizer)).toList();
51+
}
52+
53+
Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> allCustomizers() {
54+
return merge(customizer(), customizers());
55+
}
56+
57+
private <T> Set<T> merge(T element, Set<T> elements) {
58+
if (CollectionUtils.isEmpty(elements)) {
59+
return (element != null) ? Set.of(element) : Collections.emptySet();
60+
}
61+
if (element == null) {
62+
return elements;
63+
}
64+
Set<T> result = new LinkedHashSet<>(elements.size() + 1);
65+
result.add(element);
66+
result.addAll(elements);
67+
return result;
68+
}
69+
70+
@SuppressWarnings("unchecked")
71+
private StructuredLoggingJsonMembersCustomizer<Object> instantiateCustomizer(Instantiator<?> instantiator,
72+
Class<? extends StructuredLoggingJsonMembersCustomizer<?>> customizer) {
73+
return (StructuredLoggingJsonMembersCustomizer<Object>) instantiator.instantiateType(customizer);
74+
}
4075

4176
static StructuredLoggingJsonProperties get(Environment environment) {
4277
return Binder.get(environment)

Diff for: spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLoggingJsonPropertiesJsonMembersCustomizer.java

+2-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
1717
package org.springframework.boot.logging.structured;
1818

1919
import java.util.Map;
20-
import java.util.Set;
2120

2221
import org.springframework.boot.json.JsonWriter.MemberPath;
2322
import org.springframework.boot.json.JsonWriter.Members;
@@ -51,10 +50,7 @@ public void customize(Members<Object> members) {
5150
if (!CollectionUtils.isEmpty(add)) {
5251
add.forEach(members::add);
5352
}
54-
Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizer = this.properties.customizer();
55-
if (customizer != null) {
56-
customizer.forEach((c) -> createAndApplyCustomizer(members, c));
57-
}
53+
this.properties.allCustomizers(this.instantiator).forEach((customizer) -> customizer.customize(members));
5854
}
5955

6056
String renameJsonMembers(MemberPath path, String existingName) {
@@ -71,11 +67,4 @@ boolean filterPath(MemberPath path) {
7167
return (!included || excluded);
7268
}
7369

74-
@SuppressWarnings({ "unchecked", "rawtypes" })
75-
private void createAndApplyCustomizer(Members<Object> members,
76-
Class<? extends StructuredLoggingJsonMembersCustomizer<?>> customizerClass) {
77-
((StructuredLoggingJsonMembersCustomizer) this.instantiator.instantiateType(customizerClass))
78-
.customize(members);
79-
}
80-
8170
}

Diff for: spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json

+5
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,11 @@
268268
},
269269
{
270270
"name": "logging.structured.json.customizer",
271+
"type": "java.lang.Class<? extends org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer<?>>",
272+
"description": "The fully qualified class name of a StructuredLoggingJsonMembersCustomizer"
273+
},
274+
{
275+
"name": "logging.structured.json.customizers",
271276
"type": "java.util.Set<java.lang.Class<? extends org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer<?>>>",
272277
"description": "The fully qualified class names of a StructuredLoggingJsonMembersCustomizer"
273278
},

Diff for: spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/structured/StructuredLoggingJsonPropertiesJsonMembersCustomizerTests.java

+22-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -48,7 +48,7 @@ class StructuredLoggingJsonPropertiesJsonMembersCustomizerTests {
4848
@Test
4949
void customizeWhenHasExcludeFiltersMember() {
5050
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Collections.emptySet(),
51-
Set.of("a"), Collections.emptyMap(), Collections.emptyMap(), null);
51+
Set.of("a"), Collections.emptyMap(), Collections.emptyMap(), null, null);
5252
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
5353
this.instantiator, properties);
5454
assertThat(writeSampleJson(customizer)).doesNotContain("a").contains("b");
@@ -57,7 +57,7 @@ void customizeWhenHasExcludeFiltersMember() {
5757
@Test
5858
void customizeWhenHasIncludeFiltersOtherMembers() {
5959
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Set.of("a"),
60-
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), null);
60+
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), null, null);
6161
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
6262
this.instantiator, properties);
6363
assertThat(writeSampleJson(customizer)).contains("a")
@@ -69,7 +69,7 @@ void customizeWhenHasIncludeFiltersOtherMembers() {
6969
@Test
7070
void customizeWhenHasIncludeAndExcludeFiltersMembers() {
7171
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Set.of("a", "b"), Set.of("b"),
72-
Collections.emptyMap(), Collections.emptyMap(), null);
72+
Collections.emptyMap(), Collections.emptyMap(), null, null);
7373
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
7474
this.instantiator, properties);
7575
assertThat(writeSampleJson(customizer)).contains("a")
@@ -81,7 +81,7 @@ void customizeWhenHasIncludeAndExcludeFiltersMembers() {
8181
@Test
8282
void customizeWhenHasRenameRenamesMember() {
8383
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Collections.emptySet(),
84-
Collections.emptySet(), Map.of("a", "z"), Collections.emptyMap(), null);
84+
Collections.emptySet(), Map.of("a", "z"), Collections.emptyMap(), null, null);
8585
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
8686
this.instantiator, properties);
8787
assertThat(writeSampleJson(customizer)).contains("\"z\":\"a\"");
@@ -90,7 +90,7 @@ void customizeWhenHasRenameRenamesMember() {
9090
@Test
9191
void customizeWhenHasAddAddsMemeber() {
9292
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Collections.emptySet(),
93-
Collections.emptySet(), Collections.emptyMap(), Map.of("z", "z"), null);
93+
Collections.emptySet(), Collections.emptyMap(), Map.of("z", "z"), null, null);
9494
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
9595
this.instantiator, properties);
9696
assertThat(writeSampleJson(customizer)).contains("\"z\":\"z\"");
@@ -103,25 +103,38 @@ void customizeWhenHasCustomizerCustomizesMember() {
103103
.applyingNameProcessor(NameProcessor.of(String::toUpperCase));
104104
given(((Instantiator) this.instantiator).instantiateType(TestCustomizer.class)).willReturn(uppercaseCustomizer);
105105
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Collections.emptySet(),
106-
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), Set.of(TestCustomizer.class));
106+
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), TestCustomizer.class, null);
107107
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
108108
this.instantiator, properties);
109109
assertThat(writeSampleJson(customizer)).contains("\"A\":\"a\"");
110110
}
111111

112112
@Test
113113
@SuppressWarnings({ "rawtypes", "unchecked" })
114-
void multipleCustomizers() {
114+
void customizeWhenHasCustomizersCustomizesMember() {
115115
given(((Instantiator) this.instantiator).instantiateType(FooCustomizer.class)).willReturn(new FooCustomizer());
116116
given(((Instantiator) this.instantiator).instantiateType(BarCustomizer.class)).willReturn(new BarCustomizer());
117117
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Collections.emptySet(),
118-
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(),
118+
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), null,
119119
Set.of(FooCustomizer.class, BarCustomizer.class));
120120
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
121121
this.instantiator, properties);
122122
assertThat(writeSampleJson(customizer)).contains("\"foo\":\"foo\"").contains("\"bar\":\"bar\"");
123123
}
124124

125+
@Test
126+
@SuppressWarnings({ "rawtypes", "unchecked" })
127+
void customizeWhenHasCustomizerAndCustomizersCustomizesMember() {
128+
given(((Instantiator) this.instantiator).instantiateType(FooCustomizer.class)).willReturn(new FooCustomizer());
129+
given(((Instantiator) this.instantiator).instantiateType(BarCustomizer.class)).willReturn(new BarCustomizer());
130+
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Collections.emptySet(),
131+
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), FooCustomizer.class,
132+
Set.of(BarCustomizer.class));
133+
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
134+
this.instantiator, properties);
135+
assertThat(writeSampleJson(customizer)).contains("\"foo\":\"foo\"").contains("\"bar\":\"bar\"");
136+
}
137+
125138
@SuppressWarnings({ "rawtypes", "unchecked" })
126139
private String writeSampleJson(StructuredLoggingJsonMembersCustomizer customizer) {
127140
return JsonWriter.of((members) -> {

Diff for: spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/structured/StructuredLoggingJsonPropertiesTests.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ void getBindsFromEnvironment() {
4848
environment.setProperty("logging.structured.json.customizer", TestCustomizer.class.getName());
4949
StructuredLoggingJsonProperties properties = StructuredLoggingJsonProperties.get(environment);
5050
assertThat(properties).isEqualTo(new StructuredLoggingJsonProperties(Set.of("a", "b"), Set.of("c", "d"),
51-
Map.of("e", "f"), Map.of("g", "h"), Set.of(TestCustomizer.class)));
51+
Map.of("e", "f"), Map.of("g", "h"), TestCustomizer.class, null));
5252
}
5353

5454
@Test
@@ -64,7 +64,7 @@ void shouldRegisterRuntimeHints() throws Exception {
6464
assertThat(RuntimeHintsPredicates.reflection().onType(StructuredLoggingJsonProperties.class)).accepts(hints);
6565
assertThat(RuntimeHintsPredicates.reflection()
6666
.onConstructor(StructuredLoggingJsonProperties.class.getDeclaredConstructor(Set.class, Set.class, Map.class,
67-
Map.class, Set.class))
67+
Map.class, Class.class, Set.class))
6868
.invoke()).accepts(hints);
6969
}
7070

0 commit comments

Comments
 (0)