From 5d781ffcbb3ad2e2b30dca9d77e15f524e6eb0ed Mon Sep 17 00:00:00 2001 From: Dmytro Nosan Date: Wed, 12 Feb 2025 13:45:38 +0200 Subject: [PATCH] Add RuntimeHints for StackTracePrinter See gh-44242 Signed-off-by: Dmytro Nosan --- .../StructuredLoggingJsonProperties.java | 15 ++++- ...eanFactoryInitializationAotProcessor.java} | 32 +++++++++-- .../resources/META-INF/spring/aot.factories | 2 +- ...ctoryInitializationAotProcessorTests.java} | 57 ++++++++++++++++--- .../StructuredLoggingJsonPropertiesTests.java | 28 +++++++++ 5 files changed, 117 insertions(+), 17 deletions(-) rename spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/{StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcessor.java => StructuredLoggingJsonPropertiesBeanFactoryInitializationAotProcessor.java} (67%) rename spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/structured/{StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcessorTests.java => StructuredLoggingJsonPropertiesBeanFactoryInitializationAotProcessorTests.java} (61%) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLoggingJsonProperties.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLoggingJsonProperties.java index 25a38393ae2..023ffb6c4ad 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLoggingJsonProperties.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLoggingJsonProperties.java @@ -87,8 +87,7 @@ record StructuredLoggingJsonProperties(Set include, Set 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 include, Set 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); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcessor.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLoggingJsonPropertiesBeanFactoryInitializationAotProcessor.java similarity index 67% rename from spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcessor.java rename to spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLoggingJsonPropertiesBeanFactoryInitializationAotProcessor.java index 327bd427f9f..fc48e89b7ee 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcessor.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/structured/StructuredLoggingJsonPropertiesBeanFactoryInitializationAotProcessor.java @@ -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>> customizers; - private AotContribution(Set>> customizers) { + private final String stackTracePrinter; + + private AotContribution(Set>> 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>> customizers) { - return (!customizers.isEmpty()) ? new AotContribution(customizers) : null; + static AotContribution get(StructuredLoggingJsonProperties properties) { + Set>> 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); } } diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/spring/aot.factories b/spring-boot-project/spring-boot/src/main/resources/META-INF/spring/aot.factories index f78f261e659..4e5f3ad071e 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/spring/aot.factories +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/spring/aot.factories @@ -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,\ diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/structured/StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcessorTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/structured/StructuredLoggingJsonPropertiesBeanFactoryInitializationAotProcessorTests.java similarity index 61% rename from spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/structured/StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcessorTests.java rename to spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/structured/StructuredLoggingJsonPropertiesBeanFactoryInitializationAotProcessorTests.java index baf95b4d905..3cf88741d5c 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/structured/StructuredLoggingJsonMembersCustomizerBeanFactoryInitializationAotProcessorTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/structured/StructuredLoggingJsonPropertiesBeanFactoryInitializationAotProcessorTests.java @@ -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 { + + } + + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/structured/StructuredLoggingJsonPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/structured/StructuredLoggingJsonPropertiesTests.java index 7bd10b42f11..8d19c71569a 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/structured/StructuredLoggingJsonPropertiesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/structured/StructuredLoggingJsonPropertiesTests.java @@ -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 {