View Javadoc

1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2011, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.classic.turbo;
15  
16  import java.io.File;
17  import java.net.URL;
18  import java.util.List;
19  
20  import ch.qos.logback.classic.gaffer.GafferUtil;
21  import ch.qos.logback.classic.util.EnvUtil;
22  import ch.qos.logback.core.CoreConstants;
23  import ch.qos.logback.core.joran.event.SaxEvent;
24  import ch.qos.logback.core.joran.spi.ConfigurationWatchList;
25  import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil;
26  import ch.qos.logback.core.status.StatusChecker;
27  import org.slf4j.Marker;
28  
29  import ch.qos.logback.classic.Level;
30  import ch.qos.logback.classic.Logger;
31  import ch.qos.logback.classic.LoggerContext;
32  import ch.qos.logback.classic.joran.JoranConfigurator;
33  import ch.qos.logback.core.joran.spi.JoranException;
34  import ch.qos.logback.core.spi.FilterReply;
35  
36  /**
37   * Reconfigure a LoggerContext when the configuration file changes.
38   *
39   * @author Ceki Gulcu
40   */
41  public class ReconfigureOnChangeFilter extends TurboFilter {
42  
43    /**
44     * Scan for changes in configuration file once every minute.
45     */
46    // 1 minute - value mentioned in documentation
47    public final static long DEFAULT_REFRESH_PERIOD = 60 * 1000;
48  
49    long refreshPeriod = DEFAULT_REFRESH_PERIOD;
50    URL mainConfigurationURL;
51    protected volatile long nextCheck;
52  
53    ConfigurationWatchList configurationWatchList;
54  
55    @Override
56    public void start() {
57      configurationWatchList = ConfigurationWatchListUtil.getConfigurationWatchList(context);
58      if (configurationWatchList != null) {
59        mainConfigurationURL = configurationWatchList.getMainURL();
60        List<File> watchList = configurationWatchList.getCopyOfFileWatchList();
61        long inSeconds = refreshPeriod / 1000;
62        addInfo("Will scan for changes in [" + watchList + "] every "
63                + inSeconds + " seconds. ");
64        synchronized (configurationWatchList) {
65          updateNextCheck(System.currentTimeMillis());
66        }
67        super.start();
68      } else {
69        addWarn("Empty ConfigurationWatchList in context");
70      }
71    }
72  
73    @Override
74    public String toString() {
75      return "ReconfigureOnChangeFilter{" +
76              "invocationCounter=" + invocationCounter +
77              '}';
78    }
79  
80    // The next fields counts the number of time the decide method is called
81    //
82    // IMPORTANT: This field can be updated by multiple threads. It follows that
83    // its values may *not* be incremented sequentially. However, we don't care
84    // about the actual value of the field except that from time to time the
85    // expression (invocationCounter++ & 0xF) == 0xF) should be true.
86    private long invocationCounter = 0;
87  
88    @Override
89    public FilterReply decide(Marker marker, Logger logger, Level level,
90                              String format, Object[] params, Throwable t) {
91      if (!isStarted()) {
92        return FilterReply.NEUTRAL;
93      }
94  
95      // for performance reasons, check for changes every 16 invocations
96      if (((invocationCounter++) & 0xF) != 0xF) {
97        return FilterReply.NEUTRAL;
98      }
99  
100     synchronized (configurationWatchList) {
101       if (changeDetected()) {
102         // Even though reconfiguration involves resetting the loggerContext,
103         // which clears the list of turbo filters including this instance, it is
104         // still possible for this instance to be subsequently invoked by another
105         // thread if it was already executing when the context was reset.
106         disableSubsequentReconfiguration();
107         detachReconfigurationToNewThread();
108       }
109     }
110 
111     return FilterReply.NEUTRAL;
112   }
113 
114   // by detaching reconfiguration to a new thread, we release the various
115   // locks held by the current thread, in particular, the AppenderAttachable
116   // reader lock.
117   private void detachReconfigurationToNewThread() {
118     addInfo("Detected change in [" + configurationWatchList.getCopyOfFileWatchList() + "]");
119     context.getExecutorService().submit(new ReconfiguringThread());
120   }
121 
122   void updateNextCheck(long now) {
123     nextCheck = now + refreshPeriod;
124   }
125 
126   protected boolean changeDetected() {
127     long now = System.currentTimeMillis();
128     if (now >= nextCheck) {
129       updateNextCheck(now);
130       return configurationWatchList.changeDetected();
131     }
132     return false;
133   }
134 
135   void disableSubsequentReconfiguration() {
136     nextCheck = Long.MAX_VALUE;
137   }
138 
139   public long getRefreshPeriod() {
140     return refreshPeriod;
141   }
142 
143   public void setRefreshPeriod(long refreshPeriod) {
144     this.refreshPeriod = refreshPeriod;
145   }
146 
147   class ReconfiguringThread implements Runnable {
148     public void run() {
149       if (mainConfigurationURL == null) {
150         addInfo("Due to missing top level configuration file, skipping reconfiguration");
151         return;
152       }
153       LoggerContext lc = (LoggerContext) context;
154       addInfo(CoreConstants.RESET_MSG_PREFIX + "named ["+context.getName() + "]");
155       if (mainConfigurationURL.toString().endsWith("xml")) {
156         performXMLConfiguration(lc);
157       } else if (mainConfigurationURL.toString().endsWith("groovy")) {
158         if (EnvUtil.isGroovyAvailable()) {
159           lc.reset();
160           // avoid directly referring to GafferConfigurator so as to avoid
161           // loading  groovy.lang.GroovyObject . See also http://jira.qos.ch/browse/LBCLASSIC-214
162           GafferUtil.runGafferConfiguratorOn(lc, this, mainConfigurationURL);
163         } else {
164           addError("Groovy classes are not available on the class path. ABORTING INITIALIZATION.");
165         }
166       }
167     }
168 
169     private void performXMLConfiguration(LoggerContext lc) {
170       JoranConfigurator jc = new JoranConfigurator();
171       jc.setContext(context);
172       StatusChecker statusChecker = new StatusChecker(context);
173       List<SaxEvent> eventList = jc.recallSafeConfiguration();
174       URL mainURL = ConfigurationWatchListUtil.getMainWatchURL(context);
175       lc.reset();
176       long threshold = System.currentTimeMillis();
177       try {
178         jc.doConfigure(mainConfigurationURL);
179         if (statusChecker.hasXMLParsingErrors(threshold)) {
180           fallbackConfiguration(lc, eventList, mainURL);
181         }
182       } catch (JoranException e) {
183         fallbackConfiguration(lc, eventList, mainURL);
184       }
185     }
186 
187     private void fallbackConfiguration(LoggerContext lc, List<SaxEvent> eventList, URL mainURL) {
188       JoranConfigurator joranConfigurator = new JoranConfigurator();
189       joranConfigurator.setContext(context);
190       if (eventList != null) {
191         addWarn("Falling back to previously registered safe configuration.");
192         try {
193           lc.reset();
194           joranConfigurator.informContextOfURLUsedForConfiguration(context, mainURL);
195           joranConfigurator.doConfigure(eventList);
196           addInfo("Re-registering previous fallback configuration once more as a fallback configuration point");
197           joranConfigurator.registerSafeConfiguration();
198         } catch (JoranException e) {
199           addError("Unexpected exception thrown by a configuration considered safe.", e);
200         }
201       } else {
202         addWarn("No previous configuration to fall back to.");
203       }
204     }
205   }
206 }