001/** 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2022, QOS.ch. All rights reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under 006 * either the terms of the Eclipse Public License v1.0 as published by 007 * the Eclipse Foundation 008 * 009 * or (per the licensee's choosing) 010 * 011 * under the terms of the GNU Lesser General Public License version 2.1 012 * as published by the Free Software Foundation. 013 */ 014package ch.qos.logback.classic.joran; 015 016import java.io.File; 017import java.net.URL; 018import java.util.List; 019import java.util.concurrent.ScheduledFuture; 020 021import ch.qos.logback.classic.LoggerContext; 022import ch.qos.logback.core.CoreConstants; 023import ch.qos.logback.core.joran.spi.ConfigurationWatchList; 024import ch.qos.logback.core.joran.spi.JoranException; 025import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil; 026import ch.qos.logback.core.model.Model; 027import ch.qos.logback.core.model.ModelUtil; 028import ch.qos.logback.core.spi.ConfigurationEvent; 029import ch.qos.logback.core.spi.ContextAwareBase; 030import ch.qos.logback.core.status.StatusUtil; 031 032import static ch.qos.logback.core.CoreConstants.PROPERTIES_FILE_EXTENSION; 033import static ch.qos.logback.core.spi.ConfigurationEvent.*; 034 035public class ReconfigureOnChangeTask extends ContextAwareBase implements Runnable { 036 037 public static final String DETECTED_CHANGE_IN_CONFIGURATION_FILES = "Detected change in configuration files."; 038 public static final String RE_REGISTERING_PREVIOUS_SAFE_CONFIGURATION = "Re-registering previous fallback configuration once more as a fallback configuration point"; 039 public static final String FALLING_BACK_TO_SAFE_CONFIGURATION = "Given previous errors, falling back to previously registered safe configuration."; 040 041 long birthdate = System.currentTimeMillis(); 042 List<ReconfigureOnChangeTaskListener> listeners = null; 043 044 ScheduledFuture<?> scheduledFuture; 045 046 @Override 047 public void run() { 048 context.fireConfigurationEvent(newConfigurationChangeDetectorRunningEvent(this)); 049 050 ConfigurationWatchList configurationWatchList = ConfigurationWatchListUtil.getConfigurationWatchList(context); 051 if (configurationWatchList == null) { 052 addWarn("Empty ConfigurationWatchList in context"); 053 return; 054 } 055 056 if (configurationWatchList.emptyWatchLists()) { 057 addInfo("Both watch lists are empty. Disabling "); 058 return; 059 } 060 061 File changedFile = configurationWatchList.changeDetectedInFile(); 062 URL changedURL = configurationWatchList.changeDetectedInURL(); 063 064 if (changedFile == null && changedURL == null) { 065 return; 066 } 067 068 context.fireConfigurationEvent(ConfigurationEvent.newConfigurationChangeDetectedEvent(this)); 069 addInfo(DETECTED_CHANGE_IN_CONFIGURATION_FILES); 070 071 if(changedFile != null) { 072 changeInFile(changedFile, configurationWatchList); 073 } 074 075 if(changedURL != null) { 076 changeInURL(changedURL); 077 } 078 } 079 080 private void changeInURL(URL url) { 081 String path = url.getPath(); 082 if(path.endsWith(PROPERTIES_FILE_EXTENSION)) { 083 runPropertiesConfigurator(url); 084 } 085 } 086 private void changeInFile(File changedFile, ConfigurationWatchList configurationWatchList) { 087 if(changedFile.getName().endsWith(PROPERTIES_FILE_EXTENSION)) { 088 runPropertiesConfigurator(changedFile); 089 return; 090 } 091 092 // ======== fuller processing below 093 addInfo(CoreConstants.RESET_MSG_PREFIX + "named [" + context.getName() + "]"); 094 cancelFutureInvocationsOfThisTaskInstance(); 095 URL mainConfigurationURL = configurationWatchList.getMainURL(); 096 097 LoggerContext lc = (LoggerContext) context; 098 if (mainConfigurationURL.toString().endsWith("xml")) { 099 performXMLConfiguration(lc, mainConfigurationURL); 100 } else if (mainConfigurationURL.toString().endsWith("groovy")) { 101 addError("Groovy configuration disabled due to Java 9 compilation issues."); 102 } 103 } 104 105 private void runPropertiesConfigurator(Object changedObject) { 106 addInfo("Will run PropertyConfigurator on "+changedObject); 107 PropertiesConfigurator propertiesConfigurator = new PropertiesConfigurator(); 108 propertiesConfigurator.setContext(context); 109 try { 110 if(changedObject instanceof File) { 111 File changedFile = (File) changedObject; 112 propertiesConfigurator.doConfigure(changedFile); 113 } else if(changedObject instanceof URL) { 114 URL changedURL = (URL) changedObject; 115 propertiesConfigurator.doConfigure(changedURL); 116 } 117 context.fireConfigurationEvent(newPartialConfigurationEndedSuccessfullyEvent(this)); 118 } catch (JoranException e) { 119 addError("Failed to reload "+ changedObject); 120 } 121 } 122 123 private void cancelFutureInvocationsOfThisTaskInstance() { 124 boolean result = scheduledFuture.cancel(false); 125 if(!result) { 126 addWarn("could not cancel "+ this.toString()); 127 } 128 } 129 130 private void performXMLConfiguration(LoggerContext lc, URL mainConfigurationURL) { 131 JoranConfigurator jc = new JoranConfigurator(); 132 jc.setContext(context); 133 StatusUtil statusUtil = new StatusUtil(context); 134 Model failsafeTop = jc.recallSafeConfiguration(); 135 URL mainURL = ConfigurationWatchListUtil.getMainWatchURL(context); 136 addInfo("Resetting loggerContext ["+lc.getName()+"]"); 137 lc.reset(); 138 long threshold = System.currentTimeMillis(); 139 try { 140 jc.doConfigure(mainConfigurationURL); 141 // e.g. IncludeAction will add a status regarding XML parsing errors but no exception will reach here 142 if (statusUtil.hasXMLParsingErrors(threshold)) { 143 fallbackConfiguration(lc, failsafeTop, mainURL); 144 } 145 } catch (JoranException e) { 146 addWarn("Exception occurred during reconfiguration", e); 147 fallbackConfiguration(lc, failsafeTop, mainURL); 148 } 149 } 150 151 private void fallbackConfiguration(LoggerContext lc, Model failsafeTop, URL mainURL) { 152 // failsafe events are used only in case of errors. Therefore, we must *not* 153 // invoke file inclusion since the included files may be the cause of the error. 154 155 // List<SaxEvent> failsafeEvents = removeIncludeEvents(eventList); 156 JoranConfigurator joranConfigurator = new JoranConfigurator(); 157 joranConfigurator.setContext(context); 158 ConfigurationWatchList oldCWL = ConfigurationWatchListUtil.getConfigurationWatchList(context); 159 ConfigurationWatchList newCWL = oldCWL.buildClone(); 160 161 if (failsafeTop == null) { 162 addWarn("No previous configuration to fall back on."); 163 return; 164 } else { 165 addWarn(FALLING_BACK_TO_SAFE_CONFIGURATION); 166 addInfo("Safe model "+failsafeTop); 167 try { 168 lc.reset(); 169 ConfigurationWatchListUtil.registerConfigurationWatchList(context, newCWL); 170 ModelUtil.resetForReuse(failsafeTop); 171 joranConfigurator.processModel(failsafeTop); 172 addInfo(RE_REGISTERING_PREVIOUS_SAFE_CONFIGURATION); 173 joranConfigurator.registerSafeConfiguration(failsafeTop); 174 context.fireConfigurationEvent(newConfigurationEndedSuccessfullyEvent(this)); 175 } catch (Exception e) { 176 addError("Unexpected exception thrown by a configuration considered safe.", e); 177 } 178 } 179 } 180 181 @Override 182 public String toString() { 183 return "ReconfigureOnChangeTask(born:" + birthdate + ")"; 184 } 185 186 187 public void setScheduredFuture(ScheduledFuture<?> aScheduledFuture) { 188 this.scheduledFuture = aScheduledFuture; 189 } 190}