From 7674841946b61b4a42d81850c85278a6f04c24d7 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 24 Nov 2017 14:03:34 +0000 Subject: [PATCH] Log condition evaluation delta upon DevTools restart --- .../condition/ConditionEvaluationReport.java | 21 ++++++ .../ConditionEvaluationReportMessage.java | 75 ++++++++++++++----- ...nditionEvaluationDeltaLoggingListener.java | 60 +++++++++++++++ .../autoconfigure/DevToolsProperties.java | 13 ++++ .../LocalDevToolsAutoConfiguration.java | 6 ++ .../src/main/asciidoc/using-spring-boot.adoc | 12 +++ 6 files changed, 170 insertions(+), 17 deletions(-) create mode 100644 spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/ConditionEvaluationDeltaLoggingListener.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java index 9790f9fd3a..3ab2d21463 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionEvaluationReport.java @@ -188,6 +188,27 @@ public final class ConditionEvaluationReport { } } + public ConditionEvaluationReport getDelta(ConditionEvaluationReport previousReport) { + ConditionEvaluationReport delta = new ConditionEvaluationReport(); + for (Entry entry : this.outcomes.entrySet()) { + ConditionAndOutcomes previous = previousReport.outcomes.get(entry.getKey()); + if (previous == null + || previous.isFullMatch() != entry.getValue().isFullMatch()) { + entry.getValue() + .forEach((conditionAndOutcome) -> delta.recordConditionEvaluation( + entry.getKey(), conditionAndOutcome.getCondition(), + conditionAndOutcome.getOutcome())); + } + } + List newExclusions = new ArrayList<>(this.exclusions); + newExclusions.removeAll(previousReport.getExclusions()); + delta.recordExclusions(newExclusions); + List newUnconditionalClasses = new ArrayList<>(this.unconditionalClasses); + newUnconditionalClasses.removeAll(previousReport.unconditionalClasses); + delta.unconditionalClasses.addAll(newUnconditionalClasses); + return delta; + } + /** * Provides access to a number of {@link ConditionAndOutcome} items. */ diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/ConditionEvaluationReportMessage.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/ConditionEvaluationReportMessage.java index 2176876839..547929e1e1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/ConditionEvaluationReportMessage.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/ConditionEvaluationReportMessage.java @@ -22,6 +22,8 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcome; @@ -33,6 +35,7 @@ import org.springframework.util.StringUtils; * A condition evaluation report message that can logged or printed. * * @author Phillip Webb + * @author Andy Wilkinson * @since 1.4.0 */ public class ConditionEvaluationReportMessage { @@ -40,33 +43,69 @@ public class ConditionEvaluationReportMessage { private StringBuilder message; public ConditionEvaluationReportMessage(ConditionEvaluationReport report) { - this.message = getLogMessage(report); + this(report, "CONDITIONS EVALUATION REPORT"); } - private StringBuilder getLogMessage(ConditionEvaluationReport report) { + public ConditionEvaluationReportMessage(ConditionEvaluationReport report, + String title) { + this.message = getLogMessage(report, title); + } + + private StringBuilder getLogMessage(ConditionEvaluationReport report, String title) { StringBuilder message = new StringBuilder(); message.append(String.format("%n%n%n")); - message.append(String.format("=========================%n")); - message.append(String.format("CONDITIONS REPORT%n")); - message.append(String.format("=========================%n%n%n")); - message.append(String.format("Positive matches:%n")); - message.append(String.format("-----------------%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)); Map shortOutcomes = orderByName( report.getConditionAndOutcomesBySource()); - for (Map.Entry entry : shortOutcomes.entrySet()) { - if (entry.getValue().isFullMatch()) { - addMatchLogMessage(message, entry.getKey(), entry.getValue()); - } + logPositiveMatches(message, shortOutcomes); + logNegativeMatches(message, shortOutcomes); + logExclusions(report, message); + logUnconditionalClasses(report, message); + message.append(String.format("%n%n")); + return message; + } + + private void logPositiveMatches(StringBuilder message, + Map shortOutcomes) { + message.append(String.format("Positive matches:%n")); + message.append(String.format("-----------------%n")); + List> matched = shortOutcomes.entrySet() + .stream().filter((entry) -> entry.getValue().isFullMatch()) + .collect(Collectors.toList()); + if (matched.isEmpty()) { + message.append(String.format("%n None%n")); + } + else { + matched.forEach((entry) -> addMatchLogMessage(message, entry.getKey(), + entry.getValue())); } message.append(String.format("%n%n")); + } + + private void logNegativeMatches(StringBuilder message, + Map shortOutcomes) { message.append(String.format("Negative matches:%n")); message.append(String.format("-----------------%n")); - for (Map.Entry entry : shortOutcomes.entrySet()) { - if (!entry.getValue().isFullMatch()) { - addNonMatchLogMessage(message, entry.getKey(), entry.getValue()); - } + List> nonMatched = shortOutcomes.entrySet() + .stream().filter((entry) -> !entry.getValue().isFullMatch()) + .collect(Collectors.toList()); + if (nonMatched.isEmpty()) { + message.append(String.format("%n None%n")); + } + else { + nonMatched.forEach((entry) -> addNonMatchLogMessage(message, entry.getKey(), + entry.getValue())); } message.append(String.format("%n%n")); + } + + private void logExclusions(ConditionEvaluationReport report, StringBuilder message) { message.append(String.format("Exclusions:%n")); message.append(String.format("-----------%n")); if (report.getExclusions().isEmpty()) { @@ -78,6 +117,10 @@ public class ConditionEvaluationReportMessage { } } message.append(String.format("%n%n")); + } + + private void logUnconditionalClasses(ConditionEvaluationReport report, + StringBuilder message) { message.append(String.format("Unconditional classes:%n")); message.append(String.format("----------------------%n")); if (report.getUnconditionalClasses().isEmpty()) { @@ -88,8 +131,6 @@ public class ConditionEvaluationReportMessage { message.append(String.format("%n %s%n", unconditionalClass)); } } - message.append(String.format("%n%n")); - return message; } private Map orderByName( diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/ConditionEvaluationDeltaLoggingListener.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/ConditionEvaluationDeltaLoggingListener.java new file mode 100644 index 0000000000..dee68c75f4 --- /dev/null +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/ConditionEvaluationDeltaLoggingListener.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2017 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.devtools.autoconfigure; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; +import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportMessage; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationListener; + +/** + * An {@link ApplicationListener} that logs the delta of condition evaluation across + * restarts. + * + * @author Andy Wilkinson + */ +class ConditionEvaluationDeltaLoggingListener + implements ApplicationListener { + + private final Log logger = LogFactory.getLog(getClass()); + + private static ConditionEvaluationReport previousReport; + + @Override + public void onApplicationEvent(ApplicationReadyEvent event) { + ConditionEvaluationReport report = event.getApplicationContext() + .getBean(ConditionEvaluationReport.class); + if (previousReport != null) { + ConditionEvaluationReport delta = report.getDelta(previousReport); + if (!delta.getConditionAndOutcomesBySource().isEmpty() + || !delta.getExclusions().isEmpty() + || !delta.getUnconditionalClasses().isEmpty()) { + this.logger.info("Condition evaluation delta:" + + new ConditionEvaluationReportMessage(delta, + "CONDITION EVALUATION DELTA")); + } + else { + this.logger.info("Condition evaluation unchanged"); + } + } + previousReport = report; + } + +} diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsProperties.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsProperties.java index 70243cf73c..5ecf9bc122 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsProperties.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsProperties.java @@ -100,6 +100,11 @@ public class DevToolsProperties { */ private List additionalPaths = new ArrayList<>(); + /** + * Whether to log the condition evaluation delta upon restart. + */ + private boolean logConditionEvaluationDelta = true; + public boolean isEnabled() { return this.enabled; } @@ -168,6 +173,14 @@ public class DevToolsProperties { this.additionalPaths = additionalPaths; } + public boolean isLogConditionEvaluationDelta() { + return this.logConditionEvaluationDelta; + } + + public void setLogConditionEvaluationDelta(boolean logConditionEvaluationDelta) { + this.logConditionEvaluationDelta = logConditionEvaluationDelta; + } + } /** diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfiguration.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfiguration.java index 99025ca4b3..2e0fbe6184 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfiguration.java @@ -140,6 +140,12 @@ public class LocalDevToolsAutoConfiguration { return this::newFileSystemWatcher; } + @Bean + @ConditionalOnProperty(prefix = "spring.devtools.restart", name = "log-condition-evaluation-delta", matchIfMissing = true) + public ConditionEvaluationDeltaLoggingListener conditionEvaluationDeltaLoggingListener() { + return new ConditionEvaluationDeltaLoggingListener(); + } + private FileSystemWatcher newFileSystemWatcher() { Restart restartProperties = this.properties.getRestart(); FileSystemWatcher watcher = new FileSystemWatcher(true, diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc index ac29245982..c86a751fe4 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc @@ -813,6 +813,18 @@ http://zeroturnaround.com/software/jrebel/[JRebel] from ZeroTurnaround. These wo rewriting classes as they are loaded to make them more amenable to reloading. **** +[[using-boot-devtools-restart-logging-condition-delta]] +==== Logging changes in condition evaluation +By default, each time your application restarts, a report showing the condition evaluation +delta is logged. The report shows the changes to your application's auto-configuration as +you make changes such as adding or removing beans and setting configuration properties. + +To disable the logging of the report, set the following property: + +[indent=0] +---- + spring.devtools.restart.log-condition-evaluation-delta=false +---- [[using-boot-devtools-restart-exclude]]