Add RuntimeHints for StackTracePrinter

See gh-44242

Signed-off-by: Dmytro Nosan <dimanosan@gmail.com>
This commit is contained in:
Dmytro Nosan 2025-02-12 13:45:38 +02:00 committed by Stéphane Nicoll
parent 9f920be87c
commit 5d781ffcbb
5 changed files with 117 additions and 17 deletions

View File

@ -87,8 +87,7 @@ record StructuredLoggingJsonProperties(Set<String> include, Set<String> exclude,
Boolean includeCommonFrames, Boolean includeHashes) {
StackTracePrinter createPrinter() {
String name = (printer() != null) ? printer() : "";
name = name.toLowerCase(Locale.getDefault()).replace("-", "");
String name = getPrinter();
if ("loggingsystem".equals(name) || (name.isEmpty() && !hasAnyOtherProperty())) {
return null;
}
@ -101,6 +100,18 @@ record StructuredLoggingJsonProperties(Set<String> include, Set<String> exclude,
.instantiate(printer());
}
boolean hasCustomPrinter() {
String name = getPrinter();
if (name.isEmpty()) {
return false;
}
return !("loggingsystem".equals(name) || "standard".equals(name));
}
private String getPrinter() {
return Objects.toString(printer(), "").toLowerCase(Locale.ROOT).replace("-", "");
}
private boolean hasAnyOtherProperty() {
return Stream.of(root(), maxLength(), maxThrowableDepth(), includeCommonFrames(), includeHashes())
.anyMatch(Objects::nonNull);

View File

@ -16,6 +16,7 @@
package org.springframework.boot.logging.structured;
import java.util.Optional;
import java.util.Set;
import org.springframework.aot.generate.GenerationContext;
@ -26,6 +27,7 @@ import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContrib
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.logging.structured.StructuredLoggingJsonProperties.StackTrace;
import org.springframework.core.env.Environment;
/**
@ -36,7 +38,7 @@ import org.springframework.core.env.Environment;
* @author Yanming Zhou
* @author Phillip Webb
*/
class StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcessor
class StructuredLoggingJsonPropertiesBeanFactoryInitializationAotProcessor
implements BeanFactoryInitializationAotProcessor {
private static final String ENVIRONMENT_BEAN_NAME = "environment";
@ -45,15 +47,19 @@ 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.customizer()) : null;
return (properties != null) ? AotContribution.get(properties) : null;
}
private static final class AotContribution implements BeanFactoryInitializationAotContribution {
private final Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizers;
private AotContribution(Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizers) {
private final String stackTracePrinter;
private AotContribution(Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizers,
String stackTracePrinter) {
this.customizers = customizers;
this.stackTracePrinter = stackTracePrinter;
}
@Override
@ -62,10 +68,26 @@ class StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcesso
ReflectionHints reflection = generationContext.getRuntimeHints().reflection();
this.customizers.forEach((customizer) -> reflection.registerType(customizer,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));
if (this.stackTracePrinter != null) {
reflection.registerTypeIfPresent(getClass().getClassLoader(), this.stackTracePrinter,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
}
}
static AotContribution get(Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizers) {
return (!customizers.isEmpty()) ? new AotContribution(customizers) : null;
static AotContribution get(StructuredLoggingJsonProperties properties) {
Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizers = properties.customizer();
String stackTracePrinter = getStackTracePrinter(properties);
if (stackTracePrinter == null && customizers.isEmpty()) {
return null;
}
return new AotContribution(customizers, stackTracePrinter);
}
private static String getStackTracePrinter(StructuredLoggingJsonProperties properties) {
return Optional.ofNullable(properties.stackTrace())
.filter(StackTrace::hasCustomPrinter)
.map(StackTrace::printer)
.orElse(null);
}
}

View File

@ -20,7 +20,7 @@ org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\
org.springframework.boot.context.properties.ConfigurationPropertiesBeanFactoryInitializationAotProcessor,\
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener.EnvironmentBeanFactoryInitializationAotProcessor,\
org.springframework.boot.jackson.JsonComponentModule.JsonComponentBeanFactoryInitializationAotProcessor,\
org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcessor
org.springframework.boot.logging.structured.StructuredLoggingJsonPropertiesBeanFactoryInitializationAotProcessor
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
org.springframework.boot.context.properties.ConfigurationPropertiesBeanRegistrationAotProcessor,\

View File

@ -16,7 +16,11 @@
package org.springframework.boot.logging.structured;
import java.io.IOException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
@ -26,6 +30,7 @@ import org.springframework.beans.factory.aot.AotServices;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
import org.springframework.boot.json.JsonWriter.Members;
import org.springframework.boot.logging.StackTracePrinter;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.mock.env.MockEnvironment;
@ -33,21 +38,20 @@ import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for
* {@link StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcessor}.
* Tests for {@link StructuredLoggingJsonPropertiesBeanFactoryInitializationAotProcessor}.
*
* @author Dmytro Nosan
*/
class StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcessorTests {
class StructuredLoggingJsonPropertiesBeanFactoryInitializationAotProcessorTests {
@Test
void structuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcessorIsRegistered() {
void structuredLoggingJsonPropertiesBeanFactoryInitializationAotProcessorIsRegistered() {
assertThat(AotServices.factories().load(BeanFactoryInitializationAotProcessor.class))
.anyMatch(StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcessor.class::isInstance);
.anyMatch(StructuredLoggingJsonPropertiesBeanFactoryInitializationAotProcessor.class::isInstance);
}
@Test
void shouldRegisterStructuredLoggingJsonMembersCustomizerRuntimeHints() {
void shouldRegisterRuntimeHintsWhenCustomizerIsPresent() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("logging.structured.json.customizer", TestCustomizer.class.getName());
@ -63,14 +67,40 @@ class StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcesso
}
@Test
void shouldNotRegisterStructuredLoggingJsonMembersCustomizerRuntimeHintsWhenPropertiesAreNotSet() {
void shouldRegisterRuntimeHintsWhenStackTracePrinterIsPresent() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("logging.structured.json.stacktrace.printer", TestStackTracePrinter.class.getName());
BeanFactoryInitializationAotContribution contribution = getContribution(environment);
assertThat(contribution).isNotNull();
RuntimeHints hints = getRuntimeHints(contribution);
assertThat(RuntimeHintsPredicates.reflection()
.onType(TestStackTracePrinter.class)
.withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS))
.accepts(hints);
}
@ParameterizedTest
@ValueSource(strings = { "logging-system", "standard" })
void shouldNotRegisterRuntimeHintsWhenStackTracePrinterIsNotCustomImplementation(String printer) {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("logging.structured.json.stacktrace.printer", printer);
BeanFactoryInitializationAotContribution contribution = getContribution(environment);
assertThat(contribution).isNull();
}
@Test
void shouldNotRegisterRuntimeHintsWhenPropertiesAreNotSet() {
MockEnvironment environment = new MockEnvironment();
BeanFactoryInitializationAotContribution contribution = getContribution(environment);
assertThat(contribution).isNull();
}
@Test
void shouldNotRegisterStructuredLoggingJsonMembersCustomizerRuntimeHintsWhenCustomizerIsNotSet() {
void shouldNotRegisterRuntimeHintsWhenCustomizerAndPrinterAreNotSet() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("logging.structured.json.exclude", "something");
BeanFactoryInitializationAotContribution contribution = getContribution(environment);
@ -81,7 +111,7 @@ class StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcesso
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.setEnvironment(environment);
context.refresh();
return new StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcessor()
return new StructuredLoggingJsonPropertiesBeanFactoryInitializationAotProcessor()
.processAheadOfTime(context.getBeanFactory());
}
}
@ -100,4 +130,13 @@ class StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcesso
}
static class TestStackTracePrinter implements StackTracePrinter {
@Override
public void printStackTrace(Throwable throwable, Appendable out) throws IOException {
}
}
}

View File

@ -93,6 +93,10 @@ class StructuredLoggingJsonPropertiesTests {
.onConstructor(StructuredLoggingJsonProperties.class.getDeclaredConstructor(Set.class, Set.class, Map.class,
Map.class, StackTrace.class, Set.class))
.invoke()).accepts(hints);
assertThat(RuntimeHintsPredicates.reflection()
.onConstructor(StackTrace.class.getDeclaredConstructor(String.class, Root.class, Integer.class,
Integer.class, Boolean.class, Boolean.class))
.invoke()).accepts(hints);
}
@Test
@ -184,6 +188,30 @@ class StructuredLoggingJsonPropertiesTests {
}
@Test
void shouldReturnFalseWhenPrinterIsEmpty() {
StackTrace stackTrace = new StackTrace("", null, null, null, null, null);
assertThat(stackTrace.hasCustomPrinter()).isFalse();
}
@Test
void hasCustomPrinterShouldReturnFalseWhenPrinterHasLoggingSystem() {
StackTrace stackTrace = new StackTrace("loggingsystem", null, null, null, null, null);
assertThat(stackTrace.hasCustomPrinter()).isFalse();
}
@Test
void hasCustomPrinterShouldReturnFalseWhenPrinterHasStandard() {
StackTrace stackTrace = new StackTrace("standard", null, null, null, null, null);
assertThat(stackTrace.hasCustomPrinter()).isFalse();
}
@Test
void hasCustomPrinterShouldReturnTrueWhenPrinterHasCustom() {
StackTrace stackTrace = new StackTrace("custom-printer", null, null, null, null, null);
assertThat(stackTrace.hasCustomPrinter()).isTrue();
}
}
static class TestCustomizer implements StructuredLoggingJsonMembersCustomizer<String> {