001/* 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2026, 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 v2.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 if(changedFile != null) { 071 changeInFile(changedFile, configurationWatchList); 072 } 073 074 if(changedURL != null) { 075 changeInURL(changedURL); 076 } 077 } 078 079 private void changeInURL(URL url) { 080 String path = url.getPath(); 081 if(path.endsWith(PROPERTIES_FILE_EXTENSION)) { 082 runPropertiesConfigurator(url); 083 } 084 } 085 private void changeInFile(File changedFile, ConfigurationWatchList configurationWatchList) { 086 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.getTopURL(); 096 097 LoggerContext lc = (LoggerContext) context; 098 if (mainConfigurationURL.toString().endsWith("xml")) { 099 performXMLConfiguration(lc, mainConfigurationURL); 100 } 101 } 102 103 private void runPropertiesConfigurator(Object changedObject) { 104 addInfo("Will run PropertyConfigurator on "+changedObject); 105 PropertiesConfigurator propertiesConfigurator = new PropertiesConfigurator(); 106 propertiesConfigurator.setContext(context); 107 try { 108 if(changedObject instanceof File) { 109 File changedFile = (File) changedObject; 110 propertiesConfigurator.doConfigure(changedFile); 111 } else if(changedObject instanceof URL) { 112 URL changedURL = (URL) changedObject; 113 propertiesConfigurator.doConfigure(changedURL); 114 } 115 context.fireConfigurationEvent(newPartialConfigurationEndedSuccessfullyEvent(this)); 116 } catch (JoranException e) { 117 addError("Failed to reload "+ changedObject); 118 } 119 } 120 121 private void cancelFutureInvocationsOfThisTaskInstance() { 122 boolean result = scheduledFuture.cancel(false); 123 if(!result) { 124 addWarn("could not cancel "+ this.toString()); 125 } 126 } 127 128 private void performXMLConfiguration(LoggerContext loggerContext, URL mainConfigurationURL) { 129 130 JoranConfigurator jc = new JoranConfigurator(); 131 jc.setContext(loggerContext); 132 StatusUtil statusUtil = new StatusUtil(loggerContext); 133 Model failsafeTop = jc.recallSafeConfiguration(); 134 URL topURL = ConfigurationWatchListUtil.getMainWatchURL(context); 135 addInfo("Resetting loggerContext ["+loggerContext.getName()+"]"); 136 loggerContext.reset(); 137 long threshold = System.currentTimeMillis(); 138 try { 139 jc.doConfigure(mainConfigurationURL); 140 // e.g. IncludeAction will add a status regarding XML parsing errors but no exception will reach here 141 if (statusUtil.hasXMLParsingErrors(threshold)) { 142 fallbackConfiguration(loggerContext, failsafeTop, topURL); 143 } 144 } catch (JoranException e) { 145 addWarn("Exception occurred during reconfiguration", e); 146 fallbackConfiguration(loggerContext, failsafeTop, topURL); 147 } 148 } 149 150 private void fallbackConfiguration(LoggerContext loggerContext, Model failsafeTopModel, URL topURL) { 151 // failsafe events are used only in case of errors. Therefore, we must *not* 152 // invoke file inclusion since the included files may be the cause of the error. 153 154 // List<SaxEvent> failsafeEvents = removeIncludeEvents(eventList); 155 JoranConfigurator joranConfigurator = new JoranConfigurator(); 156 joranConfigurator.setContext(loggerContext); 157 joranConfigurator.setTopURL(topURL); 158 159// ConfigurationWatchList oldCWL = ConfigurationWatchListUtil.getConfigurationWatchList(loggerContext); 160// System.out.println("--------oldCWL:"+oldCWL); 161// ConfigurationWatchList newCWL = oldCWL.buildClone(); 162 163 if (failsafeTopModel == null) { 164 addWarn("No previous configuration to fall back on."); 165 return; 166 } else { 167 addWarn(FALLING_BACK_TO_SAFE_CONFIGURATION); 168 addInfo("Safe model "+failsafeTopModel); 169 try { 170 loggerContext.reset(); 171// ConfigurationWatchListUtil.registerConfigurationWatchList(context, newCWL); 172 ModelUtil.resetForReuse(failsafeTopModel); 173 joranConfigurator.processModel(failsafeTopModel); 174 addInfo(RE_REGISTERING_PREVIOUS_SAFE_CONFIGURATION); 175 joranConfigurator.registerSafeConfiguration(failsafeTopModel); 176 context.fireConfigurationEvent(newConfigurationEndedSuccessfullyEvent(this)); 177 } catch (Exception e) { 178 addError("Unexpected exception thrown by a configuration considered safe.", e); 179 } 180 } 181 } 182 183 @Override 184 public String toString() { 185 return "ReconfigureOnChangeTask(born:" + birthdate + ")"; 186 } 187 188 /** 189 * Contains typo. Replaced by {@link #setScheduledFuture(ScheduledFuture)}. 190 * @param aScheduledFuture 191 * @deprecated 192 */ 193 @Deprecated 194 public void setScheduredFuture(ScheduledFuture<?> aScheduledFuture) { 195 setScheduledFuture(aScheduledFuture); 196 } 197 198 /** 199 * Replaces {@link #setScheduredFuture(ScheduledFuture)} 200 * @param aScheduledFuture 201 * @since 1.5.19 202 */ 203 public void setScheduledFuture(ScheduledFuture<?> aScheduledFuture) { 204 this.scheduledFuture = aScheduledFuture; 205 } 206}