Skip to content

Commit 311e440

Browse files
committed
Merge pull request #43368 from quaff
* pr/43368: Polish 'Add support for multiple StructuredLoggingJsonMembersCustomizers' Add support for multiple StructuredLoggingJsonMembersCustomizers Closes gh-43368
2 parents 7fd4801 + 55633a1 commit 311e440

File tree

7 files changed

+118
-36
lines changed

7 files changed

+118
-36
lines changed

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

Lines changed: 2 additions & 1 deletion
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

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

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@
1616

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

19-
import java.util.Optional;
19+
import java.util.Set;
2020

2121
import org.springframework.aot.generate.GenerationContext;
2222
import org.springframework.aot.hint.MemberCategory;
23+
import org.springframework.aot.hint.ReflectionHints;
2324
import org.springframework.aot.hint.RuntimeHints;
2425
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
2526
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
@@ -32,6 +33,8 @@
3233
* {@link StructuredLoggingJsonPropertiesJsonMembersCustomizer}.
3334
*
3435
* @author Dmytro Nosan
36+
* @author Yanming Zhou
37+
* @author Phillip Webb
3538
*/
3639
class StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcessor
3740
implements BeanFactoryInitializationAotProcessor {
@@ -41,27 +44,28 @@ class StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcesso
4144
@Override
4245
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
4346
Environment environment = beanFactory.getBean(ENVIRONMENT_BEAN_NAME, Environment.class);
44-
return Optional.ofNullable(StructuredLoggingJsonProperties.get(environment))
45-
.map(StructuredLoggingJsonProperties::customizer)
46-
.map(AotContribution::new)
47-
.orElse(null);
47+
StructuredLoggingJsonProperties properties = StructuredLoggingJsonProperties.get(environment);
48+
return (properties != null) ? AotContribution.get(properties.allCustomizers()) : null;
4849
}
4950

5051
private static final class AotContribution implements BeanFactoryInitializationAotContribution {
5152

52-
private final Class<? extends StructuredLoggingJsonMembersCustomizer<?>> customizer;
53+
private final Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizers;
5354

54-
private AotContribution(Class<? extends StructuredLoggingJsonMembersCustomizer<?>> customizer) {
55-
this.customizer = customizer;
55+
private AotContribution(Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizers) {
56+
this.customizers = customizers;
5657
}
5758

5859
@Override
5960
public void applyTo(GenerationContext generationContext,
6061
BeanFactoryInitializationCode beanFactoryInitializationCode) {
61-
generationContext.getRuntimeHints()
62-
.reflection()
63-
.registerType(this.customizer, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
64-
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;
6569
}
6670

6771
}

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

Lines changed: 38 additions & 2 deletions
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,11 +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
43+
* @author Yanming Zhou
3644
*/
3745
record StructuredLoggingJsonProperties(Set<String> include, Set<String> exclude, Map<String, String> rename,
38-
Map<String, String> add, 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+
}
3975

4076
static StructuredLoggingJsonProperties get(Environment environment) {
4177
return Binder.get(environment)

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

Lines changed: 3 additions & 12 deletions
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.
@@ -28,6 +28,7 @@
2828
* {@link StructuredLoggingJsonProperties}.
2929
*
3030
* @author Phillip Webb
31+
* @author Yanming Zhou
3132
*/
3233
class StructuredLoggingJsonPropertiesJsonMembersCustomizer implements StructuredLoggingJsonMembersCustomizer<Object> {
3334

@@ -49,10 +50,7 @@ public void customize(Members<Object> members) {
4950
if (!CollectionUtils.isEmpty(add)) {
5051
add.forEach(members::add);
5152
}
52-
Class<? extends StructuredLoggingJsonMembersCustomizer<?>> customizer = this.properties.customizer();
53-
if (customizer != null) {
54-
createAndApplyCustomizer(members, customizer);
55-
}
53+
this.properties.allCustomizers(this.instantiator).forEach((customizer) -> customizer.customize(members));
5654
}
5755

5856
String renameJsonMembers(MemberPath path, String existingName) {
@@ -69,11 +67,4 @@ boolean filterPath(MemberPath path) {
6967
return (!included || excluded);
7068
}
7169

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

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,11 @@
271271
"type": "java.lang.Class<? extends org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer<?>>",
272272
"description": "The fully qualified class name of a StructuredLoggingJsonMembersCustomizer"
273273
},
274+
{
275+
"name": "logging.structured.json.customizers",
276+
"type": "java.util.Set<java.lang.Class<? extends org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer<?>>>",
277+
"description": "The fully qualified class names of a StructuredLoggingJsonMembersCustomizer"
278+
},
274279
{
275280
"name": "logging.structured.json.exclude",
276281
"type": "java.util.Set<java.lang.String>",

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

Lines changed: 52 additions & 7 deletions
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.
@@ -37,6 +37,7 @@
3737
* Tests for {@link StructuredLoggingJsonPropertiesJsonMembersCustomizer}.
3838
*
3939
* @author Phillip Webb
40+
* @author Yanming Zhou
4041
*/
4142
@ExtendWith(MockitoExtension.class)
4243
class StructuredLoggingJsonPropertiesJsonMembersCustomizerTests {
@@ -47,7 +48,7 @@ class StructuredLoggingJsonPropertiesJsonMembersCustomizerTests {
4748
@Test
4849
void customizeWhenHasExcludeFiltersMember() {
4950
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Collections.emptySet(),
50-
Set.of("a"), Collections.emptyMap(), Collections.emptyMap(), null);
51+
Set.of("a"), Collections.emptyMap(), Collections.emptyMap(), null, null);
5152
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
5253
this.instantiator, properties);
5354
assertThat(writeSampleJson(customizer)).doesNotContain("a").contains("b");
@@ -56,7 +57,7 @@ void customizeWhenHasExcludeFiltersMember() {
5657
@Test
5758
void customizeWhenHasIncludeFiltersOtherMembers() {
5859
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Set.of("a"),
59-
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), null);
60+
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), null, null);
6061
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
6162
this.instantiator, properties);
6263
assertThat(writeSampleJson(customizer)).contains("a")
@@ -68,7 +69,7 @@ void customizeWhenHasIncludeFiltersOtherMembers() {
6869
@Test
6970
void customizeWhenHasIncludeAndExcludeFiltersMembers() {
7071
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Set.of("a", "b"), Set.of("b"),
71-
Collections.emptyMap(), Collections.emptyMap(), null);
72+
Collections.emptyMap(), Collections.emptyMap(), null, null);
7273
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
7374
this.instantiator, properties);
7475
assertThat(writeSampleJson(customizer)).contains("a")
@@ -80,7 +81,7 @@ void customizeWhenHasIncludeAndExcludeFiltersMembers() {
8081
@Test
8182
void customizeWhenHasRenameRenamesMember() {
8283
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Collections.emptySet(),
83-
Collections.emptySet(), Map.of("a", "z"), Collections.emptyMap(), null);
84+
Collections.emptySet(), Map.of("a", "z"), Collections.emptyMap(), null, null);
8485
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
8586
this.instantiator, properties);
8687
assertThat(writeSampleJson(customizer)).contains("\"z\":\"a\"");
@@ -89,7 +90,7 @@ void customizeWhenHasRenameRenamesMember() {
8990
@Test
9091
void customizeWhenHasAddAddsMemeber() {
9192
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Collections.emptySet(),
92-
Collections.emptySet(), Collections.emptyMap(), Map.of("z", "z"), null);
93+
Collections.emptySet(), Collections.emptyMap(), Map.of("z", "z"), null, null);
9394
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
9495
this.instantiator, properties);
9596
assertThat(writeSampleJson(customizer)).contains("\"z\":\"z\"");
@@ -102,12 +103,38 @@ void customizeWhenHasCustomizerCustomizesMember() {
102103
.applyingNameProcessor(NameProcessor.of(String::toUpperCase));
103104
given(((Instantiator) this.instantiator).instantiateType(TestCustomizer.class)).willReturn(uppercaseCustomizer);
104105
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Collections.emptySet(),
105-
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), TestCustomizer.class);
106+
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), TestCustomizer.class, null);
106107
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
107108
this.instantiator, properties);
108109
assertThat(writeSampleJson(customizer)).contains("\"A\":\"a\"");
109110
}
110111

112+
@Test
113+
@SuppressWarnings({ "rawtypes", "unchecked" })
114+
void customizeWhenHasCustomizersCustomizesMember() {
115+
given(((Instantiator) this.instantiator).instantiateType(FooCustomizer.class)).willReturn(new FooCustomizer());
116+
given(((Instantiator) this.instantiator).instantiateType(BarCustomizer.class)).willReturn(new BarCustomizer());
117+
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Collections.emptySet(),
118+
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), null,
119+
Set.of(FooCustomizer.class, BarCustomizer.class));
120+
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
121+
this.instantiator, properties);
122+
assertThat(writeSampleJson(customizer)).contains("\"foo\":\"foo\"").contains("\"bar\":\"bar\"");
123+
}
124+
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+
111138
@SuppressWarnings({ "rawtypes", "unchecked" })
112139
private String writeSampleJson(StructuredLoggingJsonMembersCustomizer customizer) {
113140
return JsonWriter.of((members) -> {
@@ -126,4 +153,22 @@ public void customize(Members<String> members) {
126153

127154
}
128155

156+
static class FooCustomizer implements StructuredLoggingJsonMembersCustomizer<String> {
157+
158+
@Override
159+
public void customize(Members<String> members) {
160+
members.add("foo", "foo");
161+
}
162+
163+
}
164+
165+
static class BarCustomizer implements StructuredLoggingJsonMembersCustomizer<String> {
166+
167+
@Override
168+
public void customize(Members<String> members) {
169+
members.add("bar", "bar");
170+
}
171+
172+
}
173+
129174
}

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

Lines changed: 2 additions & 2 deletions
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"), 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, Class.class))
67+
Map.class, Class.class, Set.class))
6868
.invoke()).accepts(hints);
6969
}
7070

0 commit comments

Comments
 (0)