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.io.FileInputStream; 018import java.net.URL; 019import java.util.List; 020import java.util.concurrent.ScheduledFuture; 021 022import ch.qos.logback.classic.LoggerContext; 023import ch.qos.logback.core.CoreConstants; 024import ch.qos.logback.core.joran.spi.ConfigurationWatchList; 025import ch.qos.logback.core.joran.spi.JoranException; 026import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil; 027import ch.qos.logback.core.model.Model; 028import ch.qos.logback.core.model.ModelUtil; 029import ch.qos.logback.core.spi.ConfigurationEvent; 030import ch.qos.logback.core.spi.ContextAwareBase; 031import ch.qos.logback.core.status.StatusUtil; 032 033import static ch.qos.logback.core.CoreConstants.PROPERTIES_FILE_EXTENSION; 034import static ch.qos.logback.core.spi.ConfigurationEvent.*; 035 036public class ReconfigureOnChangeTask extends ContextAwareBase implements Runnable { 037 038 public static final String DETECTED_CHANGE_IN_CONFIGURATION_FILES = "Detected change in configuration files."; 039 static final String RE_REGISTERING_PREVIOUS_SAFE_CONFIGURATION = "Re-registering previous fallback configuration once more as a fallback configuration point"; 040 static final String FALLING_BACK_TO_SAFE_CONFIGURATION = "Given previous errors, falling back to previously registered safe configuration."; 041 042 long birthdate = System.currentTimeMillis(); 043 List<ReconfigureOnChangeTaskListener> listeners = null; 044 045 ScheduledFuture<?> scheduledFuture; 046 047 @Override 048 public void run() { 049 context.fireConfigurationEvent(newConfigurationChangeDetectorRunningEvent(this)); 050 051 ConfigurationWatchList configurationWatchList = ConfigurationWatchListUtil.getConfigurationWatchList(context); 052 if (configurationWatchList == null) { 053 addWarn("Empty ConfigurationWatchList in context"); 054 return; 055 } 056 057 List<File> filesToWatch = configurationWatchList.getCopyOfFileWatchList(); 058 if (filesToWatch == null || filesToWatch.isEmpty()) { 059 addInfo("Empty watch file list. Disabling "); 060 return; 061 } 062 File changedFile = configurationWatchList.changeDetected(); 063 if (changedFile == null) { 064 return; 065 } 066 context.fireConfigurationEvent(ConfigurationEvent.newConfigurationChangeDetectedEvent(this)); 067 068 addInfo(DETECTED_CHANGE_IN_CONFIGURATION_FILES); 069 addInfo(CoreConstants.RESET_MSG_PREFIX + "named [" + context.getName() + "]"); 070 071 if(changedFile.getName().endsWith(PROPERTIES_FILE_EXTENSION)) { 072 runPropertiesConfigurator(changedFile); 073 // no further processing 074 return; 075 } 076 077 // ======== fuller processing below 078 079 cancelFutureInvocationsOfThisTaskInstance(); 080 URL mainConfigurationURL = configurationWatchList.getMainURL(); 081 082 LoggerContext lc = (LoggerContext) context; 083 if (mainConfigurationURL.toString().endsWith("xml")) { 084 performXMLConfiguration(lc, mainConfigurationURL); 085 } else if (mainConfigurationURL.toString().endsWith("groovy")) { 086 addError("Groovy configuration disabled due to Java 9 compilation issues."); 087 } 088 //fireDoneReconfiguring(); 089 } 090 091 private void runPropertiesConfigurator(File changedFile) { 092 addInfo("Will run PropertyConfigurator on "+changedFile.getAbsolutePath()); 093 PropertyConfigurator propertyConfigurator = new PropertyConfigurator(); 094 propertyConfigurator.setContext(context); 095 try { 096 propertyConfigurator.doConfigure(changedFile); 097 context.fireConfigurationEvent(newPartialConfigurationEndedSuccessfullyEvent(this)); 098 } catch (JoranException e) { 099 addError("Failed to reload "+ changedFile); 100 } 101 } 102 103 private void cancelFutureInvocationsOfThisTaskInstance() { 104 boolean result = scheduledFuture.cancel(false); 105 if(!result) { 106 addWarn("could not cancel "+ this.toString()); 107 } 108 } 109 110 private void performXMLConfiguration(LoggerContext lc, URL mainConfigurationURL) { 111 JoranConfigurator jc = new JoranConfigurator(); 112 jc.setContext(context); 113 StatusUtil statusUtil = new StatusUtil(context); 114 Model failsafeTop = jc.recallSafeConfiguration(); 115 URL mainURL = ConfigurationWatchListUtil.getMainWatchURL(context); 116 addInfo("Resetting loggerContext ["+lc.getName()+"]"); 117 lc.reset(); 118 long threshold = System.currentTimeMillis(); 119 try { 120 jc.doConfigure(mainConfigurationURL); 121 // e.g. IncludeAction will add a status regarding XML parsing errors but no exception will reach here 122 if (statusUtil.hasXMLParsingErrors(threshold)) { 123 fallbackConfiguration(lc, failsafeTop, mainURL); 124 } 125 } catch (JoranException e) { 126 addWarn("Exception occurred during reconfiguration", e); 127 fallbackConfiguration(lc, failsafeTop, mainURL); 128 } 129 } 130 131 private void fallbackConfiguration(LoggerContext lc, Model failsafeTop, URL mainURL) { 132 // failsafe events are used only in case of errors. Therefore, we must *not* 133 // invoke file inclusion since the included files may be the cause of the error. 134 135 // List<SaxEvent> failsafeEvents = removeIncludeEvents(eventList); 136 JoranConfigurator joranConfigurator = new JoranConfigurator(); 137 joranConfigurator.setContext(context); 138 ConfigurationWatchList oldCWL = ConfigurationWatchListUtil.getConfigurationWatchList(context); 139 ConfigurationWatchList newCWL = oldCWL.buildClone(); 140 141 if (failsafeTop == null) { 142 addWarn("No previous configuration to fall back on."); 143 return; 144 } else { 145 addWarn(FALLING_BACK_TO_SAFE_CONFIGURATION); 146 addInfo("Safe model "+failsafeTop); 147 try { 148 lc.reset(); 149 ConfigurationWatchListUtil.registerConfigurationWatchList(context, newCWL); 150 ModelUtil.resetForReuse(failsafeTop); 151 joranConfigurator.processModel(failsafeTop); 152 addInfo(RE_REGISTERING_PREVIOUS_SAFE_CONFIGURATION); 153 joranConfigurator.registerSafeConfiguration(failsafeTop); 154 context.fireConfigurationEvent(newConfigurationEndedSuccessfullyEvent(this)); 155 } catch (Exception e) { 156 addError("Unexpected exception thrown by a configuration considered safe.", e); 157 } 158 } 159 } 160 161 @Override 162 public String toString() { 163 return "ReconfigureOnChangeTask(born:" + birthdate + ")"; 164 } 165 166 167 public void setScheduredFuture(ScheduledFuture<?> aScheduledFuture) { 168 this.scheduledFuture = aScheduledFuture; 169 } 170}