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}