1
2
3
4
5
6
7
8
9
10
11
12
13
14 package ch.qos.logback.classic.turbo;
15
16 import static ch.qos.logback.core.CoreConstants.MILLIS_IN_ONE_SECOND;
17
18 import java.io.File;
19 import java.net.URL;
20 import java.util.List;
21
22 import org.slf4j.Marker;
23
24 import ch.qos.logback.classic.Level;
25 import ch.qos.logback.classic.Logger;
26 import ch.qos.logback.classic.LoggerContext;
27 import ch.qos.logback.classic.joran.JoranConfigurator;
28 import ch.qos.logback.core.CoreConstants;
29 import ch.qos.logback.core.joran.spi.ConfigurationWatchList;
30 import ch.qos.logback.core.joran.spi.JoranException;
31 import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil;
32 import ch.qos.logback.core.model.Model;
33 import ch.qos.logback.core.model.ModelUtil;
34 import ch.qos.logback.core.spi.FilterReply;
35 import ch.qos.logback.core.status.StatusUtil;
36
37
38
39
40
41
42
43 @Deprecated
44 public class ReconfigureOnChangeFilter extends TurboFilter {
45
46
47
48
49
50 public final static long DEFAULT_REFRESH_PERIOD = 60 * MILLIS_IN_ONE_SECOND;
51
52 long refreshPeriod = DEFAULT_REFRESH_PERIOD;
53 URL mainConfigurationURL;
54 protected volatile long nextCheck;
55
56 ConfigurationWatchList configurationWatchList;
57
58 @Override
59 public void start() {
60 configurationWatchList = ConfigurationWatchListUtil.getConfigurationWatchList(context);
61 if (configurationWatchList != null) {
62 mainConfigurationURL = configurationWatchList.getMainURL();
63 if (mainConfigurationURL == null) {
64 addWarn("Due to missing top level configuration file, automatic reconfiguration is impossible.");
65 return;
66 }
67 List<File> watchList = configurationWatchList.getCopyOfFileWatchList();
68 long inSeconds = refreshPeriod / 1000;
69 addInfo("Will scan for changes in [" + watchList + "] every " + inSeconds + " seconds. ");
70 synchronized (configurationWatchList) {
71 updateNextCheck(System.currentTimeMillis());
72 }
73 super.start();
74 } else {
75 addWarn("Empty ConfigurationWatchList in context");
76 }
77 }
78
79 @Override
80 public String toString() {
81 return "ReconfigureOnChangeFilter{" + "invocationCounter=" + invocationCounter + '}';
82 }
83
84
85
86
87
88
89
90 private long invocationCounter = 0;
91
92 private volatile long mask = 0xF;
93 private volatile long lastMaskCheck = System.currentTimeMillis();
94
95 @Override
96 public FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t) {
97 if (!isStarted()) {
98 return FilterReply.NEUTRAL;
99 }
100
101
102
103
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
114
115
116
117 disableSubsequentReconfiguration();
118 detachReconfigurationToNewThread();
119 }
120 }
121
122 return FilterReply.NEUTRAL;
123 }
124
125
126
127
128 private static final int MAX_MASK = 0xFFFF;
129
130
131
132
133 private static final long MASK_INCREASE_THRESHOLD = 100;
134
135
136
137
138 private static final long MASK_DECREASE_THRESHOLD = MASK_INCREASE_THRESHOLD * 8;
139
140
141
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
153
154
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 }