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 public class ReconfigureOnChangeFilter extends TurboFilter {
43
44
45
46
47
48 public final static long DEFAULT_REFRESH_PERIOD = 60 * MILLIS_IN_ONE_SECOND;
49
50 long refreshPeriod = DEFAULT_REFRESH_PERIOD;
51 URL mainConfigurationURL;
52 protected volatile long nextCheck;
53
54 ConfigurationWatchList configurationWatchList;
55
56 @Override
57 public void start() {
58 configurationWatchList = ConfigurationWatchListUtil.getConfigurationWatchList(context);
59 if (configurationWatchList != null) {
60 mainConfigurationURL = configurationWatchList.getMainURL();
61 if (mainConfigurationURL == null) {
62 addWarn("Due to missing top level configuration file, automatic reconfiguration is impossible.");
63 return;
64 }
65 List<File> watchList = configurationWatchList.getCopyOfFileWatchList();
66 long inSeconds = refreshPeriod / 1000;
67 addInfo("Will scan for changes in [" + watchList + "] every " + inSeconds + " seconds. ");
68 synchronized (configurationWatchList) {
69 updateNextCheck(System.currentTimeMillis());
70 }
71 super.start();
72 } else {
73 addWarn("Empty ConfigurationWatchList in context");
74 }
75 }
76
77 @Override
78 public String toString() {
79 return "ReconfigureOnChangeFilter{" + "invocationCounter=" + invocationCounter + '}';
80 }
81
82
83
84
85
86
87
88 private long invocationCounter = 0;
89
90 private volatile long mask = 0xF;
91 private volatile long lastMaskCheck = System.currentTimeMillis();
92
93 @Override
94 public FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t) {
95 if (!isStarted()) {
96 return FilterReply.NEUTRAL;
97 }
98
99
100
101
102 if (((invocationCounter++) & mask) != mask) {
103 return FilterReply.NEUTRAL;
104 }
105
106 long now = System.currentTimeMillis();
107
108 synchronized (configurationWatchList) {
109 updateMaskIfNecessary(now);
110 if (changeDetected(now)) {
111
112
113
114
115 disableSubsequentReconfiguration();
116 detachReconfigurationToNewThread();
117 }
118 }
119
120 return FilterReply.NEUTRAL;
121 }
122
123
124
125
126 private static final int MAX_MASK = 0xFFFF;
127
128
129
130
131 private static final long MASK_INCREASE_THRESHOLD = 100;
132
133
134
135
136 private static final long MASK_DECREASE_THRESHOLD = MASK_INCREASE_THRESHOLD * 8;
137
138
139
140 private void updateMaskIfNecessary(long now) {
141 final long timeElapsedSinceLastMaskUpdateCheck = now - lastMaskCheck;
142 lastMaskCheck = now;
143 if (timeElapsedSinceLastMaskUpdateCheck < MASK_INCREASE_THRESHOLD && (mask < MAX_MASK)) {
144 mask = (mask << 1) | 1;
145 } else if (timeElapsedSinceLastMaskUpdateCheck > MASK_DECREASE_THRESHOLD) {
146 mask = mask >>> 2;
147 }
148 }
149
150
151
152
153 void detachReconfigurationToNewThread() {
154 addInfo("Detected change in [" + configurationWatchList.getCopyOfFileWatchList() + "]");
155 context.getExecutorService().submit(new ReconfiguringThread());
156 }
157
158 void updateNextCheck(long now) {
159 nextCheck = now + refreshPeriod;
160 }
161
162 protected boolean changeDetected(long now) {
163 if (now >= nextCheck) {
164 updateNextCheck(now);
165 return configurationWatchList.changeDetected();
166 }
167 return false;
168 }
169
170 void disableSubsequentReconfiguration() {
171 nextCheck = Long.MAX_VALUE;
172 }
173
174 public long getRefreshPeriod() {
175 return refreshPeriod;
176 }
177
178 public void setRefreshPeriod(long refreshPeriod) {
179 this.refreshPeriod = refreshPeriod;
180 }
181
182 class ReconfiguringThread implements Runnable {
183 public void run() {
184 if (mainConfigurationURL == null) {
185 addInfo("Due to missing top level configuration file, skipping reconfiguration");
186 return;
187 }
188 LoggerContext lc = (LoggerContext) context;
189 addInfo(CoreConstants.RESET_MSG_PREFIX + "named [" + context.getName() + "]");
190 if (mainConfigurationURL.toString().endsWith("xml")) {
191 performXMLConfiguration(lc);
192 } else if (mainConfigurationURL.toString().endsWith("groovy")) {
193 addError("Groovy configuration disabled due to Java 9 compilation issues.");
194 }
195 }
196
197 private void performXMLConfiguration(LoggerContext lc) {
198 JoranConfigurator jc = new JoranConfigurator();
199 jc.setContext(context);
200 StatusUtil statusUtil = new StatusUtil(context);
201 Model failSafeTop = jc.recallSafeConfiguration();
202 URL mainURL = ConfigurationWatchListUtil.getMainWatchURL(context);
203 lc.reset();
204 long threshold = System.currentTimeMillis();
205 try {
206 jc.doConfigure(mainConfigurationURL);
207 if (statusUtil.hasXMLParsingErrors(threshold)) {
208 fallbackConfiguration(lc, failSafeTop, mainURL);
209 }
210 } catch (JoranException e) {
211 fallbackConfiguration(lc, failSafeTop, mainURL);
212 }
213 }
214
215 private void fallbackConfiguration(LoggerContext lc, Model failSafeTop, URL mainURL) {
216 JoranConfigurator joranConfigurator = new JoranConfigurator();
217 joranConfigurator.setContext(context);
218 if (failSafeTop != null) {
219 addWarn("Falling back to previously registered safe configuration.");
220 try {
221 lc.reset();
222 JoranConfigurator.informContextOfURLUsedForConfiguration(context, mainURL);
223 ModelUtil.resetForReuse(failSafeTop);
224 joranConfigurator.processModel(failSafeTop);
225 addInfo("Re-registering previous fallback configuration once more as a fallback configuration point");
226 joranConfigurator.registerSafeConfiguration(failSafeTop);
227 } catch (Exception e) {
228 addError("Unexpected exception thrown by a configuration considered safe.", e);
229 }
230 } else {
231 addWarn("No previous configuration to fall back on.");
232 }
233 }
234 }
235 }