001/*
002 * Copyright 2012-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.autoconfigure.logging;
018
019import org.apache.commons.logging.Log;
020import org.apache.commons.logging.LogFactory;
021
022import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
023import org.springframework.boot.context.event.ApplicationFailedEvent;
024import org.springframework.boot.logging.LogLevel;
025import org.springframework.context.ApplicationContextInitializer;
026import org.springframework.context.ApplicationEvent;
027import org.springframework.context.ConfigurableApplicationContext;
028import org.springframework.context.event.ApplicationContextEvent;
029import org.springframework.context.event.ContextRefreshedEvent;
030import org.springframework.context.event.GenericApplicationListener;
031import org.springframework.context.support.GenericApplicationContext;
032import org.springframework.core.Ordered;
033import org.springframework.core.ResolvableType;
034import org.springframework.util.Assert;
035
036/**
037 * {@link ApplicationContextInitializer} that writes the {@link ConditionEvaluationReport}
038 * to the log. Reports are logged at the {@link LogLevel#DEBUG DEBUG} level. A crash
039 * report triggers an info output suggesting the user runs again with debug enabled to
040 * display the report.
041 * <p>
042 * This initializer is not intended to be shared across multiple application context
043 * instances.
044 *
045 * @author Greg Turnquist
046 * @author Dave Syer
047 * @author Phillip Webb
048 * @author Andy Wilkinson
049 * @author Madhura Bhave
050 */
051public class ConditionEvaluationReportLoggingListener
052                implements ApplicationContextInitializer<ConfigurableApplicationContext> {
053
054        private final Log logger = LogFactory.getLog(getClass());
055
056        private ConfigurableApplicationContext applicationContext;
057
058        private ConditionEvaluationReport report;
059
060        private final LogLevel logLevelForReport;
061
062        public ConditionEvaluationReportLoggingListener() {
063                this(LogLevel.DEBUG);
064        }
065
066        public ConditionEvaluationReportLoggingListener(LogLevel logLevelForReport) {
067                Assert.isTrue(isInfoOrDebug(logLevelForReport), "LogLevel must be INFO or DEBUG");
068                this.logLevelForReport = logLevelForReport;
069        }
070
071        private boolean isInfoOrDebug(LogLevel logLevelForReport) {
072                return LogLevel.INFO.equals(logLevelForReport)
073                                || LogLevel.DEBUG.equals(logLevelForReport);
074        }
075
076        public LogLevel getLogLevelForReport() {
077                return this.logLevelForReport;
078        }
079
080        @Override
081        public void initialize(ConfigurableApplicationContext applicationContext) {
082                this.applicationContext = applicationContext;
083                applicationContext
084                                .addApplicationListener(new ConditionEvaluationReportListener());
085                if (applicationContext instanceof GenericApplicationContext) {
086                        // Get the report early in case the context fails to load
087                        this.report = ConditionEvaluationReport
088                                        .get(this.applicationContext.getBeanFactory());
089                }
090        }
091
092        protected void onApplicationEvent(ApplicationEvent event) {
093                ConfigurableApplicationContext initializerApplicationContext = this.applicationContext;
094                if (event instanceof ContextRefreshedEvent) {
095                        if (((ApplicationContextEvent) event)
096                                        .getApplicationContext() == initializerApplicationContext) {
097                                logAutoConfigurationReport();
098                        }
099                }
100                else if (event instanceof ApplicationFailedEvent
101                                && ((ApplicationFailedEvent) event)
102                                                .getApplicationContext() == initializerApplicationContext) {
103                        logAutoConfigurationReport(true);
104                }
105        }
106
107        private void logAutoConfigurationReport() {
108                logAutoConfigurationReport(!this.applicationContext.isActive());
109        }
110
111        public void logAutoConfigurationReport(boolean isCrashReport) {
112                if (this.report == null) {
113                        if (this.applicationContext == null) {
114                                this.logger.info("Unable to provide the conditions report "
115                                                + "due to missing ApplicationContext");
116                                return;
117                        }
118                        this.report = ConditionEvaluationReport
119                                        .get(this.applicationContext.getBeanFactory());
120                }
121                if (!this.report.getConditionAndOutcomesBySource().isEmpty()) {
122                        if (this.getLogLevelForReport().equals(LogLevel.INFO)) {
123                                if (this.logger.isInfoEnabled()) {
124                                        this.logger.info(new ConditionEvaluationReportMessage(this.report));
125                                }
126                                else if (isCrashReport) {
127                                        logMessage("info");
128                                }
129                        }
130                        else {
131                                if (this.logger.isDebugEnabled()) {
132                                        this.logger.debug(new ConditionEvaluationReportMessage(this.report));
133                                }
134                                else if (isCrashReport) {
135                                        logMessage("debug");
136                                }
137                        }
138                }
139        }
140
141        private void logMessage(String logLevel) {
142                this.logger.info(
143                                String.format("%n%nError starting ApplicationContext. To display the "
144                                                + "conditions report re-run your application with '" + logLevel
145                                                + "' enabled."));
146        }
147
148        private class ConditionEvaluationReportListener
149                        implements GenericApplicationListener {
150
151                @Override
152                public int getOrder() {
153                        return Ordered.LOWEST_PRECEDENCE;
154                }
155
156                @Override
157                public boolean supportsEventType(ResolvableType resolvableType) {
158                        Class<?> type = resolvableType.getRawClass();
159                        if (type == null) {
160                                return false;
161                        }
162                        return ContextRefreshedEvent.class.isAssignableFrom(type)
163                                        || ApplicationFailedEvent.class.isAssignableFrom(type);
164                }
165
166                @Override
167                public boolean supportsSourceType(Class<?> sourceType) {
168                        return true;
169                }
170
171                @Override
172                public void onApplicationEvent(ApplicationEvent event) {
173                        ConditionEvaluationReportLoggingListener.this.onApplicationEvent(event);
174                }
175
176        }
177
178}