Currently, the order of auto configuration classes that being sorted and applied is not part of the evaluation report.
While debugging @ConditionalOn... on auto configurations, the actual order is very helpful information.
Can this be added as part of the auto config report?
The sorting of auto configuration is happening at AutoConfigurationImportSelector.AutoConfigurationGroup#selectImports.
So, here ConditionEvaluationReport can be retrieved from beanFactory and records the sorted auto config order information. Then, it can be used when writing the evaluation report.
Comment From: wilkinsona
See https://github.com/spring-projects/spring-boot/issues/1067 for some background. The classes were originally ordered by their fully-qualified class name. This then changed to ordering by their short name, matching how they are displayed.
I'm not sure how we can satisfy both requirements here. If you don't know the exact name of the auto-configuration class, alphabetical ordering is useful. If you want to know exactly what order the classes ran in, using the sorted ordering would be useful.
Comment From: ttddyy
I think current shortname alphabetical ordering is good to keep for conditions.
What I initially thought was rather the ordering of autoconfiguration classes. (ordered entries from spring.factories).
For example, having separate section in condition report:
Auto Configuration Processing Order
-----------------------------------
1. org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration
2. org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
3. org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration
4. org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration
5. org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration
...
Or, may be using ConditionEvaluationReport#recordConditionEvaluation to record actual conditional order, but it may be too much detail if it shows all nested conditions?
Since each auto configuration classes may import other configurations, having actual import processed order may not be deterministic. But, showing order of auto configuration loading entries would give big picture of ordering and they are deterministic, so that developer can rely on.
Strictly speaking, this ordering info might not belong to "conditional evaluation report" because it is one step before getting into the conditions.
If team thinks this should not be part of conditional evaluation report, at least I want them to be logged.
Thanks,
Comment From: wilkinsona
We discussed this today and, while we didn't settle on an answer, we covered a few things that we wanted to note here so that they're not forgotten.
The current report is split into positive and negative matches. This is useful for quickly identifying conditions that did and didn't not match. It also fits well with DevTools' condition evaluation delta. If the order in the report reflected the order in which conditions are evaluated the positive and negative match split would no longer make sense and it would split the ordered entries into two, losing information along the way.
The current report is useful both for developers writing an auto-configuration and for users who want to understand what has and has not been auto-configured. Something that helps with debugging an auto-configuration is really only of interest to the developer of the auto-configuration. As such, we're not sure that it belongs in the condition evaluation report.
To make some progress on this one, we think we'll need to do some prototyping work to try out a few things to get a feel for the best way to add the new information without losing any of the existing functionality.
Comment From: ttddyy
For those who want to have auto configuration order logged:
I wrote this class as a temporal workaround to write out "Auto configuration order report" to the log.
This class reflectively accesses auto configuration sorter and write the report upon receiving AutoConfigurationImportEvent.
code:
/**
* Write the processing order of auto configuration classes to the log.
* <p>
* This implementation reflectively invokes {@code AutoConfigurationSorter} to sort auto
* configuration classes since Spring Boot does not expose the sorting infrastructure for
* auto configuration classes.
* <p>
* Note: This class doesn't work with {@code ApplicationContextRunner}. This is because
* {@code ApplicationContextRunner} does not trigger
* {@link AutoConfigurationImportListener} event and relies on the directly
* populating(recording) {@link ConditionEvaluationReport} via
* {@link SpringBootCondition}. The configuration order is sorted by
* {@code AutoConfigurations} when new configurations are added to the runner. One
* possible solution is to use {@code ApplicationContextRunner#with} method to return a
* proxy. At the time of running the runner, it can introspect all configurations, which
* has ordered, and report the order of auto configuration classes.
* <p>
* https://github.com/spring-projects/spring-boot/issues/20732
*
* @author Tadaya Tsuyukubo
*/
// Inherit the logging category from Spring Boot. For example, with "--debug" option,
// "LoggingApplicationListener" sets the log level to "debug" for Spring Boot categories.
// Then, this logger will automatically inherit the same level.
@Slf4j(topic = "org.springframework.boot.mylib")
public class AutoConfigurationOrderLoggingListener
implements BeanFactoryAware, ResourceLoaderAware, AutoConfigurationImportListener {
private static final String SORTER_CLASS = "org.springframework.boot.autoconfigure.AutoConfigurationSorter";
private ConfigurableListableBeanFactory beanFactory;
private ResourceLoader resourceLoader;
@Override
public void onAutoConfigurationImportEvent(AutoConfigurationImportEvent event) {
// if (!log.isDebugEnabled()) {
// return; // only run when debug is on
// }
List<String> configs = event.getCandidateConfigurations();
List<String> sorted;
try {
sorted = sort(configs);
}
catch (Exception ex) {
log.error("Failed to sort auto configuration classes", ex);
return; // do not proceed
}
// If we want to follow how "ConditionEvaluationReport" is logged, log them at
// "ContextRefreshedEvent" and "ApplicationFailedEvent" with implementing
// "GenericApplicationListener" (see "ConditionEvaluationReportLoggingListener").
// Then, register to "spring.factories" as "ApplicationListener".
logAutoConfigurationOrder(sorted);
}
private void logAutoConfigurationOrder(List<String> configs) {
StringBuilder sb = getLogMessage(configs, "AUTO CONFIGURATION ORDER REPORT");
log.debug(sb.toString());
}
private StringBuilder getLogMessage(List<String> configs, String title) {
StringBuilder message = new StringBuilder();
message.append(String.format("%n%n%n"));
StringBuilder separator = new StringBuilder();
for (int i = 0; i < title.length(); i++) {
separator.append("=");
}
message.append(String.format("%s%n", separator));
message.append(String.format("%s%n", title));
message.append(String.format("%s%n%n%n", separator));
for (int i = 0; i < configs.size(); i++) {
message.append(String.format("%3d - %s%n", i + 1, configs.get(i)));
}
message.append(String.format("%n%n"));
return message;
}
// Equivalent to
// "org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup.sortAutoConfigurations"
@SuppressWarnings("unchecked")
private List<String> sort(List<String> configs) throws Exception {
Class<?> clazz = ClassUtils.resolveClassName(SORTER_CLASS,
AutoConfigurationOrderLoggingListener.class.getClassLoader());
Method method = ReflectionUtils.findMethod(clazz, "getInPriorityOrder", Collection.class);
ReflectionUtils.makeAccessible(method);
Constructor<?> constructor = ReflectionUtils.accessibleConstructor(clazz, MetadataReaderFactory.class,
AutoConfigurationMetadata.class);
Object[] args = new Object[] { getMetadataReaderFactory(), getAutoConfigurationMetadata() };
Object sorter = BeanUtils.instantiateClass(constructor, args);
List<String> sorted = (List<String>) ReflectionUtils.invokeMethod(method, sorter, configs);
return sorted;
}
// Equivalent to
// "org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup.getMetadataReaderFactory"
private MetadataReaderFactory getMetadataReaderFactory() {
String beanName = "org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory";
try {
return this.beanFactory.getBean(beanName, MetadataReaderFactory.class);
}
catch (NoSuchBeanDefinitionException ex) {
return new CachingMetadataReaderFactory(this.resourceLoader);
}
}
// Equivalent to
// "org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup.getAutoConfigurationMetadata"
private AutoConfigurationMetadata getAutoConfigurationMetadata() {
String loaderClass = "org.springframework.boot.autoconfigure.AutoConfigurationMetadataLoader";
Class<?> clazz = ClassUtils.resolveClassName(loaderClass,
AutoConfigurationOrderLoggingListener.class.getClassLoader());
Method method = ReflectionUtils.findMethod(clazz, "loadMetadata", ClassLoader.class);
ReflectionUtils.makeAccessible(method);
AutoConfigurationMetadata metadata = (AutoConfigurationMetadata) ReflectionUtils.invokeMethod(method, null,
AutoConfigurationOrderLoggingListener.class.getClassLoader());
return metadata;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory);
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}
spring.factories:
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
com.example.AutoConfigurationOrderLoggingListener
Comment From: philwebb
We're cleaning out the issue tracker and closing issues that we've not seen much demand to fix. Feel free to comment with additional justifications if you feel that this one should not have been closed.