View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2022, 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.joran;
15  
16  import java.io.File;
17  import java.net.URL;
18  import java.util.List;
19  import java.util.concurrent.ScheduledFuture;
20  
21  import ch.qos.logback.classic.LoggerContext;
22  import ch.qos.logback.core.CoreConstants;
23  import ch.qos.logback.core.joran.spi.ConfigurationWatchList;
24  import ch.qos.logback.core.joran.spi.JoranException;
25  import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil;
26  import ch.qos.logback.core.model.Model;
27  import ch.qos.logback.core.model.ModelUtil;
28  import ch.qos.logback.core.spi.ConfigurationEvent;
29  import ch.qos.logback.core.spi.ContextAwareBase;
30  import ch.qos.logback.core.status.StatusUtil;
31  
32  import static ch.qos.logback.core.spi.ConfigurationEvent.newConfigurationChangeDetectorRunningEvent;
33  import static ch.qos.logback.core.spi.ConfigurationEvent.newConfigurationEndedEvent;
34  
35  public class ReconfigureOnChangeTask extends ContextAwareBase implements Runnable {
36  
37      public static final String DETECTED_CHANGE_IN_CONFIGURATION_FILES = "Detected change in configuration files.";
38      static final String RE_REGISTERING_PREVIOUS_SAFE_CONFIGURATION = "Re-registering previous fallback configuration once more as a fallback configuration point";
39      static final String FALLING_BACK_TO_SAFE_CONFIGURATION = "Given previous errors, falling back to previously registered safe configuration.";
40  
41      long birthdate = System.currentTimeMillis();
42      List<ReconfigureOnChangeTaskListener> listeners = null;
43  
44      ScheduledFuture<?> scheduledFuture;
45  
46      @Override
47      public void run() {
48          context.fireConfigurationEvent(newConfigurationChangeDetectorRunningEvent(this));
49  
50          ConfigurationWatchList configurationWatchList = ConfigurationWatchListUtil.getConfigurationWatchList(context);
51          if (configurationWatchList == null) {
52              addWarn("Empty ConfigurationWatchList in context");
53              return;
54          }
55  
56          List<File> filesToWatch = configurationWatchList.getCopyOfFileWatchList();
57          if (filesToWatch == null || filesToWatch.isEmpty()) {
58              addInfo("Empty watch file list. Disabling ");
59              return;
60          }
61  
62          if (!configurationWatchList.changeDetected()) {
63              return;
64          }
65          context.fireConfigurationEvent(ConfigurationEvent.newConfigurationChangeDetectedEvent(this));
66          cancelFutureInvocationsOfThisTaskInstance();
67  
68          URL mainConfigurationURL = configurationWatchList.getMainURL();
69  
70          addInfo(DETECTED_CHANGE_IN_CONFIGURATION_FILES);
71          addInfo(CoreConstants.RESET_MSG_PREFIX + "named [" + context.getName() + "]");
72  
73          LoggerContext lc = (LoggerContext) context;
74          if (mainConfigurationURL.toString().endsWith("xml")) {
75              performXMLConfiguration(lc, mainConfigurationURL);
76          } else if (mainConfigurationURL.toString().endsWith("groovy")) {
77              addError("Groovy configuration disabled due to Java 9 compilation issues.");
78          }
79          //fireDoneReconfiguring();
80      }
81  
82      private void cancelFutureInvocationsOfThisTaskInstance() {
83          boolean result = scheduledFuture.cancel(false);
84          if(!result) {
85              addWarn("could not cancel "+ this.toString());
86          }
87      }
88  
89      private void performXMLConfiguration(LoggerContext lc, URL mainConfigurationURL) {
90          JoranConfigurator jc = new JoranConfigurator();
91          jc.setContext(context);
92          StatusUtil statusUtil = new StatusUtil(context);
93          Model failsafeTop = jc.recallSafeConfiguration();
94          URL mainURL = ConfigurationWatchListUtil.getMainWatchURL(context);
95          lc.reset();
96          long threshold = System.currentTimeMillis();
97          try {
98              jc.doConfigure(mainConfigurationURL);
99              // 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 }