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.ArrayList;
19  import java.util.List;
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.ContextAwareBase;
29  import ch.qos.logback.core.status.StatusUtil;
30  
31  public class ReconfigureOnChangeTask extends ContextAwareBase implements Runnable {
32  
33      public static final String DETECTED_CHANGE_IN_CONFIGURATION_FILES = "Detected change in configuration files.";
34      static final String RE_REGISTERING_PREVIOUS_SAFE_CONFIGURATION = "Re-registering previous fallback configuration once more as a fallback configuration point";
35      static final String FALLING_BACK_TO_SAFE_CONFIGURATION = "Given previous errors, falling back to previously registered safe configuration.";
36  
37      long birthdate = System.currentTimeMillis();
38      List<ReconfigureOnChangeTaskListener> listeners = null;
39  
40      void addListener(ReconfigureOnChangeTaskListener listener) {
41          if (listeners == null)
42              listeners = new ArrayList<ReconfigureOnChangeTaskListener>(1);
43          listeners.add(listener);
44      }
45  
46      @Override
47      public void run() {
48          fireEnteredRunMethod();
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          fireChangeDetected();
66          URL mainConfigurationURL = configurationWatchList.getMainURL();
67  
68          addInfo(DETECTED_CHANGE_IN_CONFIGURATION_FILES);
69          addInfo(CoreConstants.RESET_MSG_PREFIX + "named [" + context.getName() + "]");
70  
71          LoggerContext lc = (LoggerContext) context;
72          if (mainConfigurationURL.toString().endsWith("xml")) {
73              performXMLConfiguration(lc, mainConfigurationURL);
74          } else if (mainConfigurationURL.toString().endsWith("groovy")) {
75              addError("Groovy configuration disabled due to Java 9 compilation issues.");
76          }
77          fireDoneReconfiguring();
78      }
79  
80      private void fireEnteredRunMethod() {
81          if (listeners == null)
82              return;
83  
84          for (ReconfigureOnChangeTaskListener listener : listeners)
85              listener.enteredRunMethod();
86      }
87  
88      private void fireChangeDetected() {
89          if (listeners == null)
90              return;
91  
92          for (ReconfigureOnChangeTaskListener listener : listeners)
93              listener.changeDetected();
94      }
95  
96      private void fireDoneReconfiguring() {
97          if (listeners == null)
98              return;
99  
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 }