SpringBoot Print Detailed Startup Exception Information

  • 2021-10-13 07:25:27
  • OfStack

SpringBoot can't print out the specific stack error message amicably if it encounters an exception when the project starts. We can only see simple error messages, so that we can't solve the problems in time. For this problem, SpringBoot provides the concept of fault analyzer (failure-analyzer), and provides some implementations according to different types of exceptions internally. What should we do if we want to customize it?

FailureAnalyzer

SpringBoot provides the initiation exception analysis interface FailureAnalyzer, which is located within org. springframework. boot. diagnosticspackage. Internal only provides 1 analysis method, the source code is as follows:


@FunctionalInterface
public interface FailureAnalyzer {

    /**
     * Returns an analysis of the given {@code failure}, or {@code null} if no analysis
     * was possible.
     * @param failure the failure
     * @return the analysis or {@code null}
     */
    FailureAnalysis analyze(Throwable failure);

}

The interface delivers the encountered exception object instances Throwable failure to the implementation class, which performs custom processing.

AbstractFailureAnalyzer

AbstractFailureAnalyzer is the basic implementation abstract class of FailureAnalyzer, which implements the analyze (Throwable failure) method defined by FailureAnalyzer, and provides an abstract method analyze (Throwable rootFailure, T cause) with a specified exception type. The source code is as follows:


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 rootFailure}, 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")
    protected final <E extends Throwable> E findCause(Throwable failure, Class<E> type) {
        while (failure != null) {
            if (type.isInstance(failure)) {
                return (E) failure;
            }
            failure = failure.getCause();
        }
        return null;
    }

}

Through the source code of AbstractFailureAnalyzer, we can see that it is specially handled in the interface method implemented in FailureAnalyzer, and the first generic type defined by the current class is obtained according to getCauseType () method, which is the specified exception type that we need to analyze.

After obtaining the generic exception type, it is judged whether Throwable matches the generic exception type according to the method findCause, and if so, it is directly returned to SpringBoot for registration processing.

Analysis Implementation Provided by SpringBoot

SpringBoot defines a series of startup analysis for specific exception types by implementing AbstractFailureAnalyzer abstract classes, as shown in the following figure:

Specify exception analysis

The startup exception analysis provided in SpringBoot is implemented by specifying specific exception types, The most common error is that the port number is occupied (PortInUseException). Although SpringBoot provides a startup analysis of this exception, we can also replace this exception analysis. We only need to create AbstractFailureAnalyzer of PortInUseException exception and register the implementation class to SpringBoot. The implementation customization is as follows:


/**
 *  Port number occupied {@link PortInUseException} Abnormal startup analysis 
 *
 * @author  Heng Yu Junior 
 */
public class PortInUseFailureAnalyzer extends AbstractFailureAnalyzer<PortInUseException> {
    /**
     * logger instance
     */
    static Logger logger = LoggerFactory.getLogger(PortInUseFailureAnalyzer.class);

    @Override
    protected FailureAnalysis analyze(Throwable rootFailure, PortInUseException cause) {
        logger.error(" Port is occupied. ", cause);
        return new FailureAnalysis(" Port number: "   cause.getPort()   " Be occupied ", "PortInUseException", rootFailure);
    }
}

Registration startup exception analysis

Above, we just wrote the specified exception startup analysis, and we need to make it take effect next. This effective method is quite special, similar to the custom SpringBoot Starter AutoConfiguration. We need to define it in the META-INF/spring. factories file, as follows:


org.springframework.boot.diagnostics.FailureAnalyzer=\
  org.minbox.chapter.springboot.failure.analyzer.PortInUseFailureAnalyzer

Then why do we need to define it in this way?

The order of exceptions encountered in project startup cannot be determined. It is very likely that exceptions occurred before Spring IOC was initialized. We can't make them effective in the form of @ Component annotations, so SpringBoot provides definitions through spring. factories configuration files.

Start exception analysis inheritance relationship

Custom running exceptions 1 are generally inherited from RuntimeException. What would be the effect if we defined an exception startup analysis instance of RuntimeException?


/**
 *  Project startup runtime exception {@link RuntimeException} Unified 1 Start analysis 
 *
 * @author  Heng Yu Junior 
 */
public class ProjectBootUnifiedFailureAnalyzer extends AbstractFailureAnalyzer<RuntimeException> {
    /**
     * logger instance
     */
    static Logger logger = LoggerFactory.getLogger(ProjectBootUnifiedFailureAnalyzer.class);

    @Override
    protected FailureAnalysis analyze(Throwable rootFailure, RuntimeException cause) {
        logger.error(" Encountered a runtime exception ", cause);
        return new FailureAnalysis(cause.getMessage(), "error", rootFailure);
    }
}

Register the class also in the spring. factories file as follows:


org.springframework.boot.diagnostics.FailureAnalyzer=\
  org.minbox.chapter.springboot.failure.analyzer.PortInUseFailureAnalyzer,\
  org.minbox.chapter.springboot.failure.analyzer.ProjectBootUnifiedFailureAnalyzer

Running the project and testing the port number occupied exception, we found that instead of executing the analyze method in ProjectBootUnifiedFailureAnalyzer, we continued to execute the method in the PortInUseFailureAnalyzer class.

Then we temporarily delete the startup analysis of PortInUseFailureAnalyzer from the spring. factories file, and when we run the project, we will find that the in-class analysis method of ProjectBootUnifiedFailureAnalyzer will be executed at this time.

Summarize

According to this chapter, we understand the operation principle of the startup exception analysis interface provided by SpringBoot and the basic abstract implementation class. Moreover, starting exception analysis has the inheritance relationship between the upper and lower levels of analyzing generic exception classes, and the starting analysis of exception subclasses will overwrite the starting analysis of exception parent classes. If you want to include all exception starting analysis, you can try to use Exception as the generic parameter of AbstractFailureAnalyzer.


Related articles: