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.ArrayList; 019import java.util.List; 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.ContextAwareBase; 029import ch.qos.logback.core.status.StatusUtil; 030 031public class ReconfigureOnChangeTask extends ContextAwareBase implements Runnable { 032 033 public static final String DETECTED_CHANGE_IN_CONFIGURATION_FILES = "Detected change in configuration files."; 034 static final String RE_REGISTERING_PREVIOUS_SAFE_CONFIGURATION = "Re-registering previous fallback configuration once more as a fallback configuration point"; 035 static final String FALLING_BACK_TO_SAFE_CONFIGURATION = "Given previous errors, falling back to previously registered safe configuration."; 036 037 long birthdate = System.currentTimeMillis(); 038 List<ReconfigureOnChangeTaskListener> listeners = null; 039 040 void addListener(ReconfigureOnChangeTaskListener listener) { 041 if (listeners == null) 042 listeners = new ArrayList<ReconfigureOnChangeTaskListener>(1); 043 listeners.add(listener); 044 } 045 046 @Override 047 public void run() { 048 fireEnteredRunMethod(); 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 fireChangeDetected(); 066 URL mainConfigurationURL = configurationWatchList.getMainURL(); 067 068 addInfo(DETECTED_CHANGE_IN_CONFIGURATION_FILES); 069 addInfo(CoreConstants.RESET_MSG_PREFIX + "named [" + context.getName() + "]"); 070 071 LoggerContext lc = (LoggerContext) context; 072 if (mainConfigurationURL.toString().endsWith("xml")) { 073 performXMLConfiguration(lc, mainConfigurationURL); 074 } else if (mainConfigurationURL.toString().endsWith("groovy")) { 075 addError("Groovy configuration disabled due to Java 9 compilation issues."); 076 } 077 fireDoneReconfiguring(); 078 } 079 080 private void fireEnteredRunMethod() { 081 if (listeners == null) 082 return; 083 084 for (ReconfigureOnChangeTaskListener listener : listeners) 085 listener.enteredRunMethod(); 086 } 087 088 private void fireChangeDetected() { 089 if (listeners == null) 090 return; 091 092 for (ReconfigureOnChangeTaskListener listener : listeners) 093 listener.changeDetected(); 094 } 095 096 private void fireDoneReconfiguring() { 097 if (listeners == null) 098 return; 099 100 for (ReconfigureOnChangeTaskListener listener : listeners) 101 listener.doneReconfiguring(); 102 } 103 104 private void performXMLConfiguration(LoggerContext lc, URL mainConfigurationURL) { 105 JoranConfigurator jc = new JoranConfigurator(); 106 jc.setContext(context); 107 StatusUtil statusUtil = new StatusUtil(context); 108 Model failsafeTop = jc.recallSafeConfiguration(); 109 URL mainURL = ConfigurationWatchListUtil.getMainWatchURL(context); 110 lc.reset(); 111 long threshold = System.currentTimeMillis(); 112 try { 113 jc.doConfigure(mainConfigurationURL); 114 if (statusUtil.hasXMLParsingErrors(threshold)) { 115 fallbackConfiguration(lc, failsafeTop, mainURL); 116 } 117 } catch (JoranException e) { 118 fallbackConfiguration(lc, failsafeTop, mainURL); 119 } 120 } 121 122// private List<SaxEvent> removeIncludeEvents(List<SaxEvent> unsanitizedEventList) { 123// List<SaxEvent> sanitizedEvents = new ArrayList<SaxEvent>(); 124// if (unsanitizedEventList == null) 125// return sanitizedEvents; 126// 127// for (SaxEvent e : unsanitizedEventList) { 128// if (!"include".equalsIgnoreCase(e.getLocalName())) 129// sanitizedEvents.add(e); 130// 131// } 132// return sanitizedEvents; 133// } 134 135 private void fallbackConfiguration(LoggerContext lc, Model failsafeTop, URL mainURL) { 136 // failsafe events are used only in case of errors. Therefore, we must *not* 137 // invoke file inclusion since the included files may be the cause of the error. 138 139 // List<SaxEvent> failsafeEvents = removeIncludeEvents(eventList); 140 JoranConfigurator joranConfigurator = new JoranConfigurator(); 141 joranConfigurator.setContext(context); 142 ConfigurationWatchList oldCWL = ConfigurationWatchListUtil.getConfigurationWatchList(context); 143 ConfigurationWatchList newCWL = oldCWL.buildClone(); 144 145 if (failsafeTop == null) { 146 addWarn("No previous configuration to fall back on."); 147 return; 148 } else { 149 addWarn(FALLING_BACK_TO_SAFE_CONFIGURATION); 150 addInfo("Safe model "+failsafeTop); 151 try { 152 lc.reset(); 153 ConfigurationWatchListUtil.registerConfigurationWatchList(context, newCWL); 154 ModelUtil.resetForReuse(failsafeTop); 155 joranConfigurator.processModel(failsafeTop); 156 addInfo(RE_REGISTERING_PREVIOUS_SAFE_CONFIGURATION); 157 joranConfigurator.registerSafeConfiguration(failsafeTop); 158 159 addInfo("after registerSafeConfiguration"); 160 } catch (Exception e) { 161 addError("Unexpected exception thrown by a configuration considered safe.", e); 162 } 163 } 164 } 165 166 @Override 167 public String toString() { 168 return "ReconfigureOnChangeTask(born:" + birthdate + ")"; 169 } 170}