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.spi.ConfigurationEvent.newConfigurationChangeDetectorRunningEvent; 033import static ch.qos.logback.core.spi.ConfigurationEvent.newConfigurationEndedEvent; 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 static final String RE_REGISTERING_PREVIOUS_SAFE_CONFIGURATION = "Re-registering previous fallback configuration once more as a fallback configuration point"; 039 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 List<File> filesToWatch = configurationWatchList.getCopyOfFileWatchList(); 057 if (filesToWatch == null || filesToWatch.isEmpty()) { 058 addInfo("Empty watch file list. Disabling "); 059 return; 060 } 061 062 if (!configurationWatchList.changeDetected()) { 063 return; 064 } 065 context.fireConfigurationEvent(ConfigurationEvent.newConfigurationChangeDetectedEvent(this)); 066 cancelFutureInvocationsOfThisTaskInstance(); 067 068 URL mainConfigurationURL = configurationWatchList.getMainURL(); 069 070 addInfo(DETECTED_CHANGE_IN_CONFIGURATION_FILES); 071 addInfo(CoreConstants.RESET_MSG_PREFIX + "named [" + context.getName() + "]"); 072 073 LoggerContext lc = (LoggerContext) context; 074 if (mainConfigurationURL.toString().endsWith("xml")) { 075 performXMLConfiguration(lc, mainConfigurationURL); 076 } else if (mainConfigurationURL.toString().endsWith("groovy")) { 077 addError("Groovy configuration disabled due to Java 9 compilation issues."); 078 } 079 //fireDoneReconfiguring(); 080 } 081 082 private void cancelFutureInvocationsOfThisTaskInstance() { 083 boolean result = scheduledFuture.cancel(false); 084 if(!result) { 085 addWarn("could not cancel "+ this.toString()); 086 } 087 } 088 089 private void performXMLConfiguration(LoggerContext lc, URL mainConfigurationURL) { 090 JoranConfigurator jc = new JoranConfigurator(); 091 jc.setContext(context); 092 StatusUtil statusUtil = new StatusUtil(context); 093 Model failsafeTop = jc.recallSafeConfiguration(); 094 URL mainURL = ConfigurationWatchListUtil.getMainWatchURL(context); 095 lc.reset(); 096 long threshold = System.currentTimeMillis(); 097 try { 098 jc.doConfigure(mainConfigurationURL); 099 // e.g. IncludeAction will add a status regarding XML parsing errors but no exception will reach here 100 if (statusUtil.hasXMLParsingErrors(threshold)) { 101 fallbackConfiguration(lc, failsafeTop, mainURL); 102 } 103 } catch (JoranException e) { 104 addWarn("Exception occurred during reconfiguration", e); 105 fallbackConfiguration(lc, failsafeTop, mainURL); 106 } 107 } 108 109 private void fallbackConfiguration(LoggerContext lc, Model failsafeTop, URL mainURL) { 110 // failsafe events are used only in case of errors. Therefore, we must *not* 111 // invoke file inclusion since the included files may be the cause of the error. 112 113 // List<SaxEvent> failsafeEvents = removeIncludeEvents(eventList); 114 JoranConfigurator joranConfigurator = new JoranConfigurator(); 115 joranConfigurator.setContext(context); 116 ConfigurationWatchList oldCWL = ConfigurationWatchListUtil.getConfigurationWatchList(context); 117 ConfigurationWatchList newCWL = oldCWL.buildClone(); 118 119 if (failsafeTop == null) { 120 addWarn("No previous configuration to fall back on."); 121 return; 122 } else { 123 addWarn(FALLING_BACK_TO_SAFE_CONFIGURATION); 124 addInfo("Safe model "+failsafeTop); 125 try { 126 lc.reset(); 127 ConfigurationWatchListUtil.registerConfigurationWatchList(context, newCWL); 128 ModelUtil.resetForReuse(failsafeTop); 129 joranConfigurator.processModel(failsafeTop); 130 addInfo(RE_REGISTERING_PREVIOUS_SAFE_CONFIGURATION); 131 joranConfigurator.registerSafeConfiguration(failsafeTop); 132 context.fireConfigurationEvent(newConfigurationEndedEvent(this)); 133 addInfo("after registerSafeConfiguration"); 134 } catch (Exception e) { 135 addError("Unexpected exception thrown by a configuration considered safe.", e); 136 } 137 } 138 } 139 140 @Override 141 public String toString() { 142 return "ReconfigureOnChangeTask(born:" + birthdate + ")"; 143 } 144 145 146 public void setScheduredFuture(ScheduledFuture<?> aScheduledFuture) { 147 this.scheduledFuture = aScheduledFuture; 148 } 149}