Polish 'Add support for multiple StructuredLoggingJsonMembersCustomizers'
See gh-43368
This commit is contained in:
@@ -641,7 +641,8 @@ logging:
|
||||
corpname: mycorp
|
||||
----
|
||||
|
||||
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.
|
||||
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 also declare implementations by listing them in a `META-INF/spring.factories` file.
|
||||
|
||||
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
|
||||
package org.springframework.boot.logging.structured;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.ReflectionHints;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
|
||||
@@ -34,6 +34,7 @@ import org.springframework.core.env.Environment;
|
||||
*
|
||||
* @author Dmytro Nosan
|
||||
* @author Yanming Zhou
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcessor
|
||||
implements BeanFactoryInitializationAotProcessor {
|
||||
@@ -43,27 +44,28 @@ class StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcesso
|
||||
@Override
|
||||
public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
|
||||
Environment environment = beanFactory.getBean(ENVIRONMENT_BEAN_NAME, Environment.class);
|
||||
return Optional.ofNullable(StructuredLoggingJsonProperties.get(environment))
|
||||
.map(StructuredLoggingJsonProperties::customizer)
|
||||
.map(AotContribution::new)
|
||||
.orElse(null);
|
||||
StructuredLoggingJsonProperties properties = StructuredLoggingJsonProperties.get(environment);
|
||||
return (properties != null) ? AotContribution.get(properties.allCustomizers()) : null;
|
||||
}
|
||||
|
||||
private static final class AotContribution implements BeanFactoryInitializationAotContribution {
|
||||
|
||||
private final Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizer;
|
||||
private final Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizers;
|
||||
|
||||
private AotContribution(Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizer) {
|
||||
this.customizer = customizer;
|
||||
private AotContribution(Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizers) {
|
||||
this.customizers = customizers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyTo(GenerationContext generationContext,
|
||||
BeanFactoryInitializationCode beanFactoryInitializationCode) {
|
||||
this.customizer.forEach((it) -> generationContext.getRuntimeHints()
|
||||
.reflection()
|
||||
.registerType(it, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
|
||||
MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));
|
||||
ReflectionHints reflection = generationContext.getRuntimeHints().reflection();
|
||||
this.customizers.forEach((customizer) -> reflection.registerType(customizer,
|
||||
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));
|
||||
}
|
||||
|
||||
static AotContribution get(Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizers) {
|
||||
return (!customizers.isEmpty()) ? new AotContribution(customizers) : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,12 +16,17 @@
|
||||
|
||||
package org.springframework.boot.logging.structured;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.context.properties.bind.BindableRuntimeHintsRegistrar;
|
||||
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.
|
||||
@@ -31,12 +36,42 @@ import org.springframework.core.env.Environment;
|
||||
* @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}
|
||||
* {@link StructuredLoggingJsonMembersCustomizer} implementation
|
||||
* @param customizers 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, Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizer) {
|
||||
Map<String, String> add, Class<? extends StructuredLoggingJsonMembersCustomizer<?>> customizer,
|
||||
Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizers) {
|
||||
|
||||
Collection<StructuredLoggingJsonMembersCustomizer<Object>> allCustomizers(Instantiator<?> instantiator) {
|
||||
return allCustomizers().stream().map((customizer) -> instantiateCustomizer(instantiator, customizer)).toList();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static StructuredLoggingJsonProperties get(Environment environment) {
|
||||
return Binder.get(environment)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2024 the original author or authors.
|
||||
* Copyright 2012-2025 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.
|
||||
@@ -17,7 +17,6 @@
|
||||
package org.springframework.boot.logging.structured;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.json.JsonWriter.MemberPath;
|
||||
import org.springframework.boot.json.JsonWriter.Members;
|
||||
@@ -51,10 +50,7 @@ class StructuredLoggingJsonPropertiesJsonMembersCustomizer implements Structured
|
||||
if (!CollectionUtils.isEmpty(add)) {
|
||||
add.forEach(members::add);
|
||||
}
|
||||
Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizer = this.properties.customizer();
|
||||
if (customizer != null) {
|
||||
customizer.forEach((c) -> createAndApplyCustomizer(members, c));
|
||||
}
|
||||
this.properties.allCustomizers(this.instantiator).forEach((customizer) -> customizer.customize(members));
|
||||
}
|
||||
|
||||
String renameJsonMembers(MemberPath path, String existingName) {
|
||||
@@ -71,11 +67,4 @@ class StructuredLoggingJsonPropertiesJsonMembersCustomizer implements Structured
|
||||
return (!included || excluded);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private void createAndApplyCustomizer(Members<Object> members,
|
||||
Class<? extends StructuredLoggingJsonMembersCustomizer<?>> customizerClass) {
|
||||
((StructuredLoggingJsonMembersCustomizer) this.instantiator.instantiateType(customizerClass))
|
||||
.customize(members);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -268,6 +268,11 @@
|
||||
},
|
||||
{
|
||||
"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"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2024 the original author or authors.
|
||||
* Copyright 2012-2025 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.
|
||||
@@ -48,7 +48,7 @@ class StructuredLoggingJsonPropertiesJsonMembersCustomizerTests {
|
||||
@Test
|
||||
void customizeWhenHasExcludeFiltersMember() {
|
||||
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Collections.emptySet(),
|
||||
Set.of("a"), Collections.emptyMap(), Collections.emptyMap(), null);
|
||||
Set.of("a"), Collections.emptyMap(), Collections.emptyMap(), null, 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);
|
||||
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), null, 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);
|
||||
Collections.emptyMap(), Collections.emptyMap(), null, 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);
|
||||
Collections.emptySet(), Map.of("a", "z"), Collections.emptyMap(), null, 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);
|
||||
Collections.emptySet(), Collections.emptyMap(), Map.of("z", "z"), null, 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(), Set.of(TestCustomizer.class));
|
||||
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), TestCustomizer.class, null);
|
||||
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
|
||||
this.instantiator, properties);
|
||||
assertThat(writeSampleJson(customizer)).contains("\"A\":\"a\"");
|
||||
@@ -111,17 +111,30 @@ class StructuredLoggingJsonPropertiesJsonMembersCustomizerTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
void multipleCustomizers() {
|
||||
void customizeWhenHasCustomizersCustomizesMember() {
|
||||
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(),
|
||||
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), null,
|
||||
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) -> {
|
||||
|
||||
@@ -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"), Set.of(TestCustomizer.class)));
|
||||
Map.of("e", "f"), Map.of("g", "h"), TestCustomizer.class, null));
|
||||
}
|
||||
|
||||
@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, Set.class))
|
||||
Map.class, Class.class, Set.class))
|
||||
.invoke()).accepts(hints);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user