Commit 03dad33b authored by Phillip Webb's avatar Phillip Webb

Polish diagnostics support

Rework a few parts of the diagnostics support:

- Move code from SpringApplication to FailureAnalyzers
- Allow AbstractFailureAnalyzer to take generic cause type
- Move own analyzers into a new package and make package private

See gh-4907
parent c6c2959a
...@@ -41,9 +41,7 @@ import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader; ...@@ -41,9 +41,7 @@ import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.boot.diagnostics.FailureAnalysis; import org.springframework.boot.diagnostics.FailureAnalyzers;
import org.springframework.boot.diagnostics.FailureAnalysisReporter;
import org.springframework.boot.diagnostics.FailureAnalyzer;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
...@@ -835,8 +833,7 @@ public class SpringApplication { ...@@ -835,8 +833,7 @@ public class SpringApplication {
private void reportFailure(Throwable failure) { private void reportFailure(Throwable failure) {
try { try {
FailureAnalysis failureAnalysis = analyzeFailure(failure); if (FailureAnalyzers.analyzeAndReport(failure, getClass().getClassLoader())) {
if (failureAnalysis != null && reportFailureAnalysis(failureAnalysis)) {
registerLoggedException(failure); registerLoggedException(failure);
return; return;
} }
...@@ -850,30 +847,6 @@ public class SpringApplication { ...@@ -850,30 +847,6 @@ public class SpringApplication {
} }
} }
private FailureAnalysis analyzeFailure(Throwable failure) {
List<FailureAnalyzer> analyzers = SpringFactoriesLoader
.loadFactories(FailureAnalyzer.class, getClass().getClassLoader());
for (FailureAnalyzer analyzer : analyzers) {
FailureAnalysis analysis = analyzer.analyze(failure);
if (analysis != null) {
return analysis;
}
}
return null;
}
private boolean reportFailureAnalysis(FailureAnalysis failureAnalysis) {
List<FailureAnalysisReporter> reporters = SpringFactoriesLoader.loadFactories(
FailureAnalysisReporter.class, getClass().getClassLoader());
if (!reporters.isEmpty()) {
for (FailureAnalysisReporter reporter : reporters) {
reporter.report(failureAnalysis);
}
return true;
}
return false;
}
/** /**
* Register that the given exception has been logged. By default, if the running in * Register that the given exception has been logged. By default, if the running in
* the main thread, this method will suppress additional printing of the stacktrace. * the main thread, this method will suppress additional printing of the stacktrace.
......
...@@ -29,7 +29,6 @@ public class PortInUseException extends EmbeddedServletContainerException { ...@@ -29,7 +29,6 @@ public class PortInUseException extends EmbeddedServletContainerException {
/** /**
* Creates a new port in use exception for the given {@code port}. * Creates a new port in use exception for the given {@code port}.
*
* @param port the port that was in use * @param port the port that was in use
*/ */
public PortInUseException(int port) { public PortInUseException(int port) {
...@@ -39,7 +38,6 @@ public class PortInUseException extends EmbeddedServletContainerException { ...@@ -39,7 +38,6 @@ public class PortInUseException extends EmbeddedServletContainerException {
/** /**
* Returns the port that was in use. * Returns the port that was in use.
*
* @return the port * @return the port
*/ */
public int getPort() { public int getPort() {
......
...@@ -167,9 +167,7 @@ public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer ...@@ -167,9 +167,7 @@ public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer
if (connector != null && this.autoStart) { if (connector != null && this.autoStart) {
startConnector(connector); startConnector(connector);
} }
checkThatConnectorsHaveStarted(); checkThatConnectorsHaveStarted();
TomcatEmbeddedServletContainer.logger TomcatEmbeddedServletContainer.logger
.info("Tomcat started on port(s): " + getPortsDescription(true)); .info("Tomcat started on port(s): " + getPortsDescription(true));
} }
......
...@@ -16,25 +16,57 @@ ...@@ -16,25 +16,57 @@
package org.springframework.boot.diagnostics; package org.springframework.boot.diagnostics;
import org.springframework.core.ResolvableType;
/** /**
* Abstract base class for most {@code FailureAnalyzer} implementations. * Abstract base class for most {@code FailureAnalyzer} implementations.
* *
* @param <T> The type of exception to analyze
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Phillip Webb
* @since 1.4.0 * @since 1.4.0
*/ */
public abstract class AbstractFailureAnalyzer implements FailureAnalyzer { public abstract class AbstractFailureAnalyzer<T extends Throwable>
implements FailureAnalyzer {
@Override
public FailureAnalysis analyze(Throwable failure) {
T cause = findCause(failure, getCauseType());
if (cause != null) {
return analyze(failure, cause);
}
return null;
}
/**
* Returns an analysis of the given {@code failure}, or {@code null} if no analysis
* was possible.
* @param rootFailure the root failure passed to the analyzer
* @param cause the actual found cause
* @return the analysis or {@code null}
*/
protected abstract FailureAnalysis analyze(Throwable rootFailure, T cause);
/**
* Return the cause type being handled by the analyzer. By default the class generic
* is used.
* @return the cause type
*/
@SuppressWarnings("unchecked")
protected Class<? extends T> getCauseType() {
return (Class<? extends T>) ResolvableType
.forClass(AbstractFailureAnalyzer.class, getClass()).resolveGeneric();
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected <T extends Throwable> T findFailure(Throwable failure, Class<T> type) { protected final <E extends Throwable> T findCause(Throwable failure, Class<E> type) {
Throwable candidate = failure; while (failure != null) {
T mostSpecificMatch = null; if (type.isInstance(failure)) {
while (candidate != null) { return (T) failure;
if (type.isInstance(candidate)) {
mostSpecificMatch = (T) candidate;
} }
candidate = candidate.getCause(); failure = failure.getCause();
} }
return mostSpecificMatch; return null;
} }
} }
...@@ -34,7 +34,6 @@ public class FailureAnalysis { ...@@ -34,7 +34,6 @@ public class FailureAnalysis {
* Creates a new {@code FailureAnalysis} with the given {@code description} and * Creates a new {@code FailureAnalysis} with the given {@code description} and
* {@code action}, if any, that the user should take to address the problem. The * {@code action}, if any, that the user should take to address the problem. The
* failure had the given underlying {@code cause}. * failure had the given underlying {@code cause}.
*
* @param description the description * @param description the description
* @param action the action * @param action the action
* @param cause the cause * @param cause the cause
...@@ -47,7 +46,6 @@ public class FailureAnalysis { ...@@ -47,7 +46,6 @@ public class FailureAnalysis {
/** /**
* Returns a description of the failure. * Returns a description of the failure.
*
* @return the description * @return the description
*/ */
public String getDescription() { public String getDescription() {
...@@ -56,7 +54,6 @@ public class FailureAnalysis { ...@@ -56,7 +54,6 @@ public class FailureAnalysis {
/** /**
* Returns the action, if any, to be taken to address the failure. * Returns the action, if any, to be taken to address the failure.
*
* @return the action or {@code null} * @return the action or {@code null}
*/ */
public String getAction() { public String getAction() {
...@@ -65,7 +62,6 @@ public class FailureAnalysis { ...@@ -65,7 +62,6 @@ public class FailureAnalysis {
/** /**
* Returns the cause of the failure. * Returns the cause of the failure.
*
* @return the cause * @return the cause
*/ */
public Throwable getCause() { public Throwable getCause() {
......
...@@ -26,9 +26,8 @@ public interface FailureAnalysisReporter { ...@@ -26,9 +26,8 @@ public interface FailureAnalysisReporter {
/** /**
* Reports the given {@code failureAnalysis} to the user. * Reports the given {@code failureAnalysis} to the user.
* * @param analysis the analysis
* @param failureAnalysis the analysis
*/ */
void report(FailureAnalysis failureAnalysis); void report(FailureAnalysis analysis);
} }
...@@ -28,7 +28,6 @@ public interface FailureAnalyzer { ...@@ -28,7 +28,6 @@ public interface FailureAnalyzer {
/** /**
* Returns an analysis of the given {@code failure}, or {@code null} if no analysis * Returns an analysis of the given {@code failure}, or {@code null} if no analysis
* was possible. * was possible.
*
* @param failure the failure * @param failure the failure
* @return the analysis or {@code null} * @return the analysis or {@code null}
*/ */
......
/*
* Copyright 2012-2016 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.diagnostics;
import java.util.List;
import org.springframework.core.io.support.SpringFactoriesLoader;
/**
* Utility to trigger {@link FailureAnalyzer} and {@link FailureAnalysisReporter}
* instances loaded from {@code spring.factories}.
*
* @author Andy Wilkinson
* @author Phillip Webb
* @since 1.4.0
*/
public final class FailureAnalyzers {
private FailureAnalyzers() {
}
public static boolean analyzeAndReport(Throwable failure, ClassLoader classLoader) {
List<FailureAnalyzer> analyzers = SpringFactoriesLoader
.loadFactories(FailureAnalyzer.class, classLoader);
List<FailureAnalysisReporter> reporters = SpringFactoriesLoader
.loadFactories(FailureAnalysisReporter.class, classLoader);
FailureAnalysis analysis = analyze(failure, analyzers);
return report(analysis, reporters);
}
private static FailureAnalysis analyze(Throwable failure,
List<FailureAnalyzer> analyzers) {
for (FailureAnalyzer analyzer : analyzers) {
FailureAnalysis analysis = analyzer.analyze(failure);
if (analysis != null) {
return analysis;
}
}
return null;
}
private static boolean report(FailureAnalysis analysis,
List<FailureAnalysisReporter> reporters) {
if (analysis == null || reporters.isEmpty()) {
return false;
}
for (FailureAnalysisReporter reporter : reporters) {
reporter.report(analysis);
}
return true;
}
}
...@@ -14,62 +14,59 @@ ...@@ -14,62 +14,59 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.diagnostics; package org.springframework.boot.diagnostics.analyzer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanCurrentlyInCreationException; import org.springframework.beans.factory.BeanCurrentlyInCreationException;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
* A {@link FailureAnalyzer} the performs analysis of failures caused by a * A {@link AbstractFailureAnalyzer} the performs analysis of failures caused by a
* {@link BeanCurrentlyInCreationException}. * {@link BeanCurrentlyInCreationException}.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @since 1.4.0
*/ */
public class BeanCurrentlyInCreationFailureAnalyzer extends AbstractFailureAnalyzer { class BeanCurrentlyInCreationFailureAnalyzer
extends AbstractFailureAnalyzer<BeanCurrentlyInCreationException> {
private static final String FIELD_AUTOWIRING_FAILURE_MESSAGE_PREFIX = "Could not autowire field: "; private static final String FIELD_AUTOWIRING_FAILURE_MESSAGE_PREFIX = "Could not autowire field: ";
@Override @Override
public FailureAnalysis analyze(Throwable failure) { protected FailureAnalysis analyze(Throwable rootFailure,
BeanCurrentlyInCreationException inCreationEx = findFailure(failure, BeanCurrentlyInCreationException cause) {
BeanCurrentlyInCreationException.class); List<String> beansInCycle = new ArrayList<String>();
if (inCreationEx != null) { Throwable candidate = rootFailure;
List<String> beansInCycle = new ArrayList<String>(); while (candidate != null) {
Throwable candidate = failure; if (candidate instanceof BeanCreationException) {
while (candidate != null) { BeanCreationException creationEx = (BeanCreationException) candidate;
if (candidate instanceof BeanCreationException) { if (StringUtils.hasText(creationEx.getBeanName())) {
BeanCreationException creationEx = (BeanCreationException) candidate; beansInCycle
if (StringUtils.hasText(creationEx.getBeanName())) { .add(creationEx.getBeanName() + getDescription(creationEx));
beansInCycle.add(
creationEx.getBeanName() + getDescription(creationEx));
}
} }
candidate = candidate.getCause();
} }
StringBuilder message = new StringBuilder(); candidate = candidate.getCause();
int uniqueBeans = beansInCycle.size() - 1; }
message.append(String StringBuilder message = new StringBuilder();
.format("There is a circular dependency between %s beans in the " int uniqueBeans = beansInCycle.size() - 1;
+ "application context:%n", uniqueBeans)); message.append(
for (String bean : beansInCycle) { String.format("There is a circular dependency between %s beans in the "
message.append(String.format("\t- %s%n", bean)); + "application context:%n", uniqueBeans));
} for (String bean : beansInCycle) {
message.append(String.format("\t- %s%n", bean));
return new FailureAnalysis(message.toString(), null, failure);
} }
return null; return new FailureAnalysis(message.toString(), null, cause);
} }
private String getDescription(BeanCreationException ex) { private String getDescription(BeanCreationException ex) {
if (StringUtils.hasText(ex.getResourceDescription())) { if (StringUtils.hasText(ex.getResourceDescription())) {
return String.format(" defined in %s", ex.getResourceDescription()); return String.format(" defined in %s", ex.getResourceDescription());
} }
else if (causedByFieldAutowiringFailure(ex)) { if (causedByFieldAutowiringFailure(ex)) {
return String.format(" (field %s)", return String.format(" (field %s)",
ex.getCause().getMessage().substring( ex.getCause().getMessage().substring(
FIELD_AUTOWIRING_FAILURE_MESSAGE_PREFIX.length(), FIELD_AUTOWIRING_FAILURE_MESSAGE_PREFIX.length(),
......
...@@ -14,8 +14,9 @@ ...@@ -14,8 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.context.embedded; package org.springframework.boot.diagnostics.analyzer;
import org.springframework.boot.context.embedded.PortInUseException;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis; import org.springframework.boot.diagnostics.FailureAnalysis;
...@@ -24,24 +25,18 @@ import org.springframework.boot.diagnostics.FailureAnalysis; ...@@ -24,24 +25,18 @@ import org.springframework.boot.diagnostics.FailureAnalysis;
* {@code PortInUseException}. * {@code PortInUseException}.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @since 1.4.0
*/ */
public class PortInUseFailureAnalyzer extends AbstractFailureAnalyzer { class PortInUseFailureAnalyzer extends AbstractFailureAnalyzer<PortInUseException> {
@Override @Override
public FailureAnalysis analyze(Throwable failure) { protected FailureAnalysis analyze(Throwable rootFailure, PortInUseException cause) {
PortInUseException portInUseException = findFailure(failure, return new FailureAnalysis(
PortInUseException.class); "Embedded servlet container failed to start. Port " + cause.getPort()
if (portInUseException != null) { + " was already in use.",
return new FailureAnalysis( "Identify and stop the process that's listening on port "
"Embedded servlet container failed to start. Port " + cause.getPort() + " or configure this "
+ portInUseException.getPort() + " was already in use.", + "application to listen on another port.",
"Identify and stop the process that's listening on port " cause);
+ portInUseException.getPort() + " or configure this "
+ "application to listen on another port.",
portInUseException);
}
return null;
} }
} }
/*
* Copyright 2012-2014 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.
*/
/**
* Internal {@link org.springframework.boot.diagnostics.FailureAnalyzer} implementations.
*/
package org.springframework.boot.diagnostics.analyzer;
/*
* Copyright 2012-2014 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.
*/
/**
* Support for failure analysis and reporting.
*
* @see org.springframework.boot.diagnostics.FailureAnalyzer
* @see org.springframework.boot.diagnostics.FailureAnalysisReporter
*/
package org.springframework.boot.diagnostics;
...@@ -33,8 +33,8 @@ org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor ...@@ -33,8 +33,8 @@ org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor
# Failure Analyzers # Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\ org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.context.embedded.PortInUseFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.BeanCurrentlyInCreationFailureAnalyzer org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer
# FailureAnalysisReporters # FailureAnalysisReporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\ org.springframework.boot.diagnostics.FailureAnalysisReporter=\
......
...@@ -14,11 +14,13 @@ ...@@ -14,11 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.diagnostics; package org.springframework.boot.diagnostics.analyzer;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.boot.diagnostics.FailureAnalyzer;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment