Call spring.factories customizers in the same way as one from props

Previously, customizers loaded from spring.factories were called
using LambdaSafe. This resulted in customizers with a generic type
more specific than Object being ignored. A customizer loaded from
the logging.structured.json.customizer property was not affected as
it was called directly rather than through LambdaSafe.

This commit aligns the way in which customizers loaded from
spring.factories are called with the way in which any customizer
specified using the logging.structured.json.customizer property is
called.

Closes gh-43312
This commit is contained in:
Andy Wilkinson 2024-11-28 11:04:50 +00:00
parent f9aedf5a43
commit a1c1e32947
2 changed files with 43 additions and 11 deletions

View File

@ -27,7 +27,6 @@ import org.springframework.boot.json.JsonWriter.Members;
import org.springframework.boot.util.Instantiator;
import org.springframework.boot.util.Instantiator.AvailableParameters;
import org.springframework.boot.util.Instantiator.FailureHandler;
import org.springframework.boot.util.LambdaSafe;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.SpringFactoriesLoader;
@ -108,11 +107,12 @@ public class StructuredLogFormatterFactory<E> {
ArgumentResolver.from(this.instantiator::getArg));
}
@SuppressWarnings("unchecked")
@SuppressWarnings({ "unchecked", "rawtypes" })
private void invokeCustomizers(List<StructuredLoggingJsonMembersCustomizer<?>> customizers,
Members<Object> members) {
LambdaSafe.callbacks(StructuredLoggingJsonMembersCustomizer.class, customizers, members)
.invoke((customizer) -> customizer.customize(members));
for (StructuredLoggingJsonMembersCustomizer<?> customizer : customizers) {
((StructuredLoggingJsonMembersCustomizer) customizer).customize(members);
}
}
/**

View File

@ -20,6 +20,7 @@ import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.boot.json.JsonWriter.Members;
import org.springframework.boot.json.JsonWriter.ValueProcessor;
import org.springframework.boot.logging.structured.StructuredLogFormatterFactory.CommonFormatters;
import org.springframework.boot.util.Instantiator.AvailableParameters;
@ -104,18 +105,49 @@ class StructuredLogFormatterFactoryTests {
}
@Test
void getInjectCustomizers() {
void getInjectStringMembersCustomizer() {
this.environment.setProperty("logging.structured.json.rename.spring", "test");
SpringFactoriesLoader factoriesLoader = mock(SpringFactoriesLoader.class);
StructuredLoggingJsonMembersCustomizer<?> customizer = (members) -> members
.applyingValueProcessor(ValueProcessor.of(String.class, String::toUpperCase));
given(factoriesLoader.load(any(), any(ArgumentResolver.class))).willReturn(List.of(customizer));
given(factoriesLoader.load(any(), any(ArgumentResolver.class)))
.willReturn(List.of(new StringMembersStructuredLoggingJsonMembersCustomizer()));
StructuredLogFormatterFactory<LogEvent> factory = new StructuredLogFormatterFactory<>(factoriesLoader,
LogEvent.class, this.environment, this::addAvailableParameters, this::addCommonFormatters);
CutomizedFormatter formatter = (CutomizedFormatter) factory.get(CutomizedFormatter.class.getName());
CustomizedFormatter formatter = (CustomizedFormatter) factory.get(CustomizedFormatter.class.getName());
assertThat(formatter.format(new LogEvent())).contains("\"test\":\"BOOT\"");
}
@Test
void getInjectObjectMembersCustomizer() {
this.environment.setProperty("logging.structured.json.rename.spring", "test");
SpringFactoriesLoader factoriesLoader = mock(SpringFactoriesLoader.class);
given(factoriesLoader.load(any(), any(ArgumentResolver.class)))
.willReturn(List.of(new ObjectMembersStructuredLoggingJsonMembersCustomizer()));
StructuredLogFormatterFactory<LogEvent> factory = new StructuredLogFormatterFactory<>(factoriesLoader,
LogEvent.class, this.environment, this::addAvailableParameters, this::addCommonFormatters);
CustomizedFormatter formatter = (CustomizedFormatter) factory.get(CustomizedFormatter.class.getName());
assertThat(formatter.format(new LogEvent())).contains("\"test\":\"BOOT\"");
}
static class StringMembersStructuredLoggingJsonMembersCustomizer
implements StructuredLoggingJsonMembersCustomizer<String> {
@Override
public void customize(Members<String> members) {
members.applyingValueProcessor(ValueProcessor.of(String.class, String::toUpperCase));
}
}
static class ObjectMembersStructuredLoggingJsonMembersCustomizer
implements StructuredLoggingJsonMembersCustomizer<Object> {
@Override
public void customize(Members<Object> members) {
members.applyingValueProcessor(ValueProcessor.of(String.class, String::toUpperCase));
}
}
static class LogEvent {
}
@ -167,9 +199,9 @@ class StructuredLogFormatterFactoryTests {
}
static class CutomizedFormatter extends JsonWriterStructuredLogFormatter<LogEvent> {
static class CustomizedFormatter extends JsonWriterStructuredLogFormatter<LogEvent> {
CutomizedFormatter(StructuredLoggingJsonMembersCustomizer<?> customizer) {
CustomizedFormatter(StructuredLoggingJsonMembersCustomizer<?> customizer) {
super((members) -> members.add("spring", "boot"), customizer);
}