Tolerate multiple identical configurations in Logback AOT contribution
Fixes gh-36997
This commit is contained in:
parent
b605b86b32
commit
acdaa6dc35
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package org.springframework.boot.logging.logback;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
|
@ -25,6 +24,7 @@ import java.io.ObjectOutputStream;
|
|||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
|
@ -51,6 +51,8 @@ import ch.qos.logback.core.spi.ContextAware;
|
|||
import ch.qos.logback.core.spi.ContextAwareBase;
|
||||
import ch.qos.logback.core.util.AggregationType;
|
||||
|
||||
import org.springframework.aot.generate.GeneratedFiles.FileHandler;
|
||||
import org.springframework.aot.generate.GeneratedFiles.Kind;
|
||||
import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.SerializationHints;
|
||||
|
|
@ -62,11 +64,11 @@ import org.springframework.core.CollectionFactory;
|
|||
import org.springframework.core.NativeDetector;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PropertiesLoaderUtils;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.function.SingletonSupplier;
|
||||
import org.springframework.util.function.ThrowingConsumer;
|
||||
|
||||
/**
|
||||
* Extended version of the Logback {@link JoranConfigurator} that adds additional Spring
|
||||
|
|
@ -176,15 +178,10 @@ class SpringBootJoranConfigurator extends JoranConfigurator {
|
|||
}
|
||||
|
||||
private void writeTo(GenerationContext generationContext) {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
try (ObjectOutputStream output = new ObjectOutputStream(bytes)) {
|
||||
output.writeObject(this.model);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
Resource modelResource = new ByteArrayResource(bytes.toByteArray());
|
||||
generationContext.getGeneratedFiles().addResourceFile(MODEL_RESOURCE_LOCATION, modelResource);
|
||||
byte[] serializedModel = serializeModel();
|
||||
generationContext.getGeneratedFiles()
|
||||
.handleFile(Kind.RESOURCE, MODEL_RESOURCE_LOCATION,
|
||||
new RequireNewOrMatchingContentFileHandler(serializedModel));
|
||||
generationContext.getRuntimeHints().resources().registerPattern(MODEL_RESOURCE_LOCATION);
|
||||
SerializationHints serializationHints = generationContext.getRuntimeHints().serialization();
|
||||
serializationTypes(this.model).forEach(serializationHints::registerType);
|
||||
|
|
@ -194,6 +191,17 @@ class SpringBootJoranConfigurator extends JoranConfigurator {
|
|||
MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));
|
||||
}
|
||||
|
||||
private byte[] serializeModel() {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
try (ObjectOutputStream output = new ObjectOutputStream(bytes)) {
|
||||
output.writeObject(this.model);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
return bytes.toByteArray();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Set<Class<? extends Serializable>> serializationTypes(Model model) {
|
||||
Set<Class<? extends Serializable>> modelClasses = new HashSet<>();
|
||||
|
|
@ -389,7 +397,9 @@ class SpringBootJoranConfigurator extends JoranConfigurator {
|
|||
|
||||
private void save(GenerationContext generationContext) {
|
||||
Map<String, String> registryMap = getRegistryMap();
|
||||
generationContext.getGeneratedFiles().addResourceFile(RESOURCE_LOCATION, () -> asInputStream(registryMap));
|
||||
byte[] rules = asBytes(registryMap);
|
||||
generationContext.getGeneratedFiles()
|
||||
.handleFile(Kind.RESOURCE, RESOURCE_LOCATION, new RequireNewOrMatchingContentFileHandler(rules));
|
||||
generationContext.getRuntimeHints().resources().registerPattern(RESOURCE_LOCATION);
|
||||
for (String ruleClassName : registryMap.values()) {
|
||||
generationContext.getRuntimeHints()
|
||||
|
|
@ -398,7 +408,7 @@ class SpringBootJoranConfigurator extends JoranConfigurator {
|
|||
}
|
||||
}
|
||||
|
||||
private InputStream asInputStream(Map<String, String> patternRuleRegistry) {
|
||||
private byte[] asBytes(Map<String, String> patternRuleRegistry) {
|
||||
Properties properties = CollectionFactory.createSortedProperties(true);
|
||||
patternRuleRegistry.forEach(properties::setProperty);
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
|
@ -408,7 +418,32 @@ class SpringBootJoranConfigurator extends JoranConfigurator {
|
|||
catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
return new ByteArrayInputStream(bytes.toByteArray());
|
||||
return bytes.toByteArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class RequireNewOrMatchingContentFileHandler implements ThrowingConsumer<FileHandler> {
|
||||
|
||||
private final byte[] newContent;
|
||||
|
||||
private RequireNewOrMatchingContentFileHandler(byte[] newContent) {
|
||||
this.newContent = newContent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void acceptWithException(FileHandler file) throws Exception {
|
||||
if (file.exists()) {
|
||||
byte[] existingContent = file.getContent().getInputStream().readAllBytes();
|
||||
if (!Arrays.equals(this.newContent, existingContent)) {
|
||||
throw new IllegalStateException(
|
||||
"Logging configuration differs from the configuration that has already been written. "
|
||||
+ "Update your logging configuration so that it is the same for each context");
|
||||
}
|
||||
}
|
||||
else {
|
||||
file.create(new ByteArrayResource(this.newContent));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
* Copyright 2012-2024 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.
|
||||
|
|
@ -30,6 +30,7 @@ import java.util.stream.Stream;
|
|||
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
|
||||
import ch.qos.logback.classic.model.RootLoggerModel;
|
||||
import ch.qos.logback.core.CoreConstants;
|
||||
import ch.qos.logback.core.FileAppender;
|
||||
import ch.qos.logback.core.Layout;
|
||||
|
|
@ -61,6 +62,7 @@ import org.springframework.boot.logging.logback.SpringBootJoranConfigurator.Logb
|
|||
import org.springframework.core.io.InputStreamSource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link LogbackConfigurationAotContribution}.
|
||||
|
|
@ -93,6 +95,37 @@ class LogbackConfigurationAotContributionTests {
|
|||
assertThat(patternRules).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void contributionOfBasicModelThatMatchesExistingModel() {
|
||||
TestGenerationContext generationContext = new TestGenerationContext();
|
||||
Model model = new Model();
|
||||
applyContribution(model, generationContext);
|
||||
applyContribution(model, generationContext);
|
||||
InMemoryGeneratedFiles generatedFiles = generationContext.getGeneratedFiles();
|
||||
assertThat(generatedFiles).has(resource("META-INF/spring/logback-model"));
|
||||
assertThat(generatedFiles).has(resource("META-INF/spring/logback-pattern-rules"));
|
||||
SerializationHints serializationHints = generationContext.getRuntimeHints().serialization();
|
||||
assertThat(serializationHints.javaSerializationHints()
|
||||
.map(JavaSerializationHint::getType)
|
||||
.map(TypeReference::getName))
|
||||
.containsExactlyInAnyOrder(namesOf(Model.class, ArrayList.class, Boolean.class, Integer.class));
|
||||
assertThat(generationContext.getRuntimeHints().reflection().typeHints()).isEmpty();
|
||||
Properties patternRules = load(
|
||||
generatedFiles.getGeneratedFile(Kind.RESOURCE, "META-INF/spring/logback-pattern-rules"));
|
||||
assertThat(patternRules).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void contributionOfBasicModelThatDiffersFromExistingModelThrows() {
|
||||
TestGenerationContext generationContext = new TestGenerationContext();
|
||||
applyContribution(new Model(), generationContext);
|
||||
Model model = new Model();
|
||||
model.addSubModel(new RootLoggerModel());
|
||||
assertThatIllegalStateException().isThrownBy(() -> applyContribution(model, generationContext))
|
||||
.withMessage("Logging configuration differs from the configuration that has already been written. "
|
||||
+ "Update your logging configuration so that it is the same for each context");
|
||||
}
|
||||
|
||||
@Test
|
||||
void patternRulesAreStoredAndRegisteredForReflection() {
|
||||
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||
|
|
@ -238,6 +271,18 @@ class LogbackConfigurationAotContributionTests {
|
|||
}
|
||||
|
||||
private TestGenerationContext applyContribution(Model model, Consumer<LoggerContext> contextCustomizer) {
|
||||
TestGenerationContext generationContext = new TestGenerationContext();
|
||||
applyContribution(model, contextCustomizer, generationContext);
|
||||
return generationContext;
|
||||
}
|
||||
|
||||
private void applyContribution(Model model, TestGenerationContext generationContext) {
|
||||
applyContribution(model, (context) -> {
|
||||
}, generationContext);
|
||||
}
|
||||
|
||||
private void applyContribution(Model model, Consumer<LoggerContext> contextCustomizer,
|
||||
TestGenerationContext generationContext) {
|
||||
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||
contextCustomizer.accept(context);
|
||||
SpringBootJoranConfigurator configurator = new SpringBootJoranConfigurator(null);
|
||||
|
|
@ -245,9 +290,7 @@ class LogbackConfigurationAotContributionTests {
|
|||
withSystemProperty("spring.aot.processing", "true", () -> configurator.processModel(model));
|
||||
LogbackConfigurationAotContribution contribution = (LogbackConfigurationAotContribution) context
|
||||
.getObject(BeanFactoryInitializationAotContribution.class.getName());
|
||||
TestGenerationContext generationContext = new TestGenerationContext();
|
||||
contribution.applyTo(generationContext, null);
|
||||
return generationContext;
|
||||
}
|
||||
|
||||
private String[] namesOf(Class<?>... types) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue