001/** 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2015, 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.turbo; 015 016import static ch.qos.logback.core.CoreConstants.MILLIS_IN_ONE_SECOND; 017 018import java.io.File; 019import java.net.URL; 020import java.util.List; 021 022import org.slf4j.Marker; 023 024import ch.qos.logback.classic.Level; 025import ch.qos.logback.classic.Logger; 026import ch.qos.logback.classic.LoggerContext; 027import ch.qos.logback.classic.joran.JoranConfigurator; 028import ch.qos.logback.core.CoreConstants; 029import ch.qos.logback.core.joran.spi.ConfigurationWatchList; 030import ch.qos.logback.core.joran.spi.JoranException; 031import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil; 032import ch.qos.logback.core.model.Model; 033import ch.qos.logback.core.model.ModelUtil; 034import ch.qos.logback.core.spi.FilterReply; 035import ch.qos.logback.core.status.StatusUtil; 036 037/** 038 * Reconfigure a LoggerContext when the configuration file changes. 039 * 040 * @author Ceki Gulcu 041 * @deprecated replaced by {@link ch.qos.logback.classic.joran.ReconfigureOnChangeTask} 042 */ 043@Deprecated 044public class ReconfigureOnChangeFilter extends TurboFilter { 045 046 /** 047 * Scan for changes in configuration file once every minute. 048 */ 049 // 1 minute - value mentioned in documentation 050 public final static long DEFAULT_REFRESH_PERIOD = 60 * MILLIS_IN_ONE_SECOND; 051 052 long refreshPeriod = DEFAULT_REFRESH_PERIOD; 053 URL mainConfigurationURL; 054 protected volatile long nextCheck; 055 056 ConfigurationWatchList configurationWatchList; 057 058 @Override 059 public void start() { 060 configurationWatchList = ConfigurationWatchListUtil.getConfigurationWatchList(context); 061 if (configurationWatchList != null) { 062 mainConfigurationURL = configurationWatchList.getMainURL(); 063 if (mainConfigurationURL == null) { 064 addWarn("Due to missing top level configuration file, automatic reconfiguration is impossible."); 065 return; 066 } 067 List<File> watchList = configurationWatchList.getCopyOfFileWatchList(); 068 long inSeconds = refreshPeriod / 1000; 069 addInfo("Will scan for changes in [" + watchList + "] every " + inSeconds + " seconds. "); 070 synchronized (configurationWatchList) { 071 updateNextCheck(System.currentTimeMillis()); 072 } 073 super.start(); 074 } else { 075 addWarn("Empty ConfigurationWatchList in context"); 076 } 077 } 078 079 @Override 080 public String toString() { 081 return "ReconfigureOnChangeFilter{" + "invocationCounter=" + invocationCounter + '}'; 082 } 083 084 // The next fields counts the number of time the decide method is called 085 // 086 // IMPORTANT: This field can be updated by multiple threads. It follows that 087 // its values may *not* be incremented sequentially. However, we don't care 088 // about the actual value of the field except that from time to time the 089 // expression (invocationCounter++ & mask) == mask) should be true. 090 private long invocationCounter = 0; 091 092 private volatile long mask = 0xF; 093 private volatile long lastMaskCheck = System.currentTimeMillis(); 094 095 @Override 096 public FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t) { 097 if (!isStarted()) { 098 return FilterReply.NEUTRAL; 099 } 100 101 // for performance reasons, skip change detection (MASK-1) times out of MASK. 102 // Only once every MASK calls is change detection code executed 103 // Note that MASK is a variable itself. 104 if (((invocationCounter++) & mask) != mask) { 105 return FilterReply.NEUTRAL; 106 } 107 108 long now = System.currentTimeMillis(); 109 110 synchronized (configurationWatchList) { 111 updateMaskIfNecessary(now); 112 if (changeDetected(now)) { 113 // Even though reconfiguration involves resetting the loggerContext, 114 // which clears the list of turbo filters including this instance, it is 115 // still possible for this instance to be subsequently invoked by another 116 // thread if it was already executing when the context was reset. 117 disableSubsequentReconfiguration(); 118 detachReconfigurationToNewThread(); 119 } 120 } 121 122 return FilterReply.NEUTRAL; 123 } 124 125 // experiments indicate that even for CPU intensive applications with 200 or 126 // more threads MASK 127 // values in the order of 0xFFFF is appropriate 128 private static final int MAX_MASK = 0xFFFF; 129 130 // if less than MASK_INCREASE_THRESHOLD milliseconds elapse between invocations 131 // of updateMaskIfNecessary() method, 132 // then the mask should be increased 133 private static final long MASK_INCREASE_THRESHOLD = 100; 134 135 // if more than MASK_DECREASE_THRESHOLD milliseconds elapse between invocations 136 // of updateMaskIfNecessary() method, 137 // then the mask should be decreased 138 private static final long MASK_DECREASE_THRESHOLD = MASK_INCREASE_THRESHOLD * 8; 139 140 // update the mask so as to execute change detection code about once every 100 141 // to 8000 milliseconds. 142 private void updateMaskIfNecessary(long now) { 143 final long timeElapsedSinceLastMaskUpdateCheck = now - lastMaskCheck; 144 lastMaskCheck = now; 145 if (timeElapsedSinceLastMaskUpdateCheck < MASK_INCREASE_THRESHOLD && (mask < MAX_MASK)) { 146 mask = (mask << 1) | 1; 147 } else if (timeElapsedSinceLastMaskUpdateCheck > MASK_DECREASE_THRESHOLD) { 148 mask = mask >>> 2; 149 } 150 } 151 152 // by detaching reconfiguration to a new thread, we release the various 153 // locks held by the current thread, in particular, the AppenderAttachable 154 // reader lock. 155 void detachReconfigurationToNewThread() { 156 addInfo("Detected change in [" + configurationWatchList.getCopyOfFileWatchList() + "]"); 157 context.getExecutorService().submit(new ReconfiguringThread()); 158 } 159 160 void updateNextCheck(long now) { 161 nextCheck = now + refreshPeriod; 162 } 163 164 protected boolean changeDetected(long now) { 165 if (now >= nextCheck) { 166 updateNextCheck(now); 167 File file = configurationWatchList.changeDetected(); 168 return file != null; 169 } 170 return false; 171 } 172 173 void disableSubsequentReconfiguration() { 174 nextCheck = Long.MAX_VALUE; 175 } 176 177 public long getRefreshPeriod() { 178 return refreshPeriod; 179 } 180 181 public void setRefreshPeriod(long refreshPeriod) { 182 this.refreshPeriod = refreshPeriod; 183 } 184 185 class ReconfiguringThread implements Runnable { 186 public void run() { 187 if (mainConfigurationURL == null) { 188 addInfo("Due to missing top level configuration file, skipping reconfiguration"); 189 return; 190 } 191 LoggerContext lc = (LoggerContext) context; 192 addInfo(CoreConstants.RESET_MSG_PREFIX + "named [" + context.getName() + "]"); 193 if (mainConfigurationURL.toString().endsWith("xml")) { 194 performXMLConfiguration(lc); 195 } else if (mainConfigurationURL.toString().endsWith("groovy")) { 196 addError("Groovy configuration disabled due to Java 9 compilation issues."); 197 } 198 } 199 200 private void performXMLConfiguration(LoggerContext lc) { 201 JoranConfigurator jc = new JoranConfigurator(); 202 jc.setContext(context); 203 StatusUtil statusUtil = new StatusUtil(context); 204 Model failSafeTop = jc.recallSafeConfiguration(); 205 URL mainURL = ConfigurationWatchListUtil.getMainWatchURL(context); 206 lc.reset(); 207 long threshold = System.currentTimeMillis(); 208 try { 209 jc.doConfigure(mainConfigurationURL); 210 if (statusUtil.hasXMLParsingErrors(threshold)) { 211 fallbackConfiguration(lc, failSafeTop, mainURL); 212 } 213 } catch (JoranException e) { 214 fallbackConfiguration(lc, failSafeTop, mainURL); 215 } 216 } 217 218 private void fallbackConfiguration(LoggerContext lc, Model failSafeTop, URL mainURL) { 219 JoranConfigurator joranConfigurator = new JoranConfigurator(); 220 joranConfigurator.setContext(context); 221 if (failSafeTop != null) { 222 addWarn("Falling back to previously registered safe configuration."); 223 try { 224 lc.reset(); 225 JoranConfigurator.informContextOfURLUsedForConfiguration(context, mainURL); 226 ModelUtil.resetForReuse(failSafeTop); 227 joranConfigurator.processModel(failSafeTop); 228 addInfo("Re-registering previous fallback configuration once more as a fallback configuration point"); 229 joranConfigurator.registerSafeConfiguration(failSafeTop); 230 } catch (Exception e) { 231 addError("Unexpected exception thrown by a configuration considered safe.", e); 232 } 233 } else { 234 addWarn("No previous configuration to fall back on."); 235 } 236 } 237 } 238}