1
2
3
4
5
6
7
8
9
10
11
12
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
38
39
40
41 public class ReconfigureOnChangeFilter extends TurboFilter {
42
43
44
45
46
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
81
82
83
84
85
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
96 if (((invocationCounter++) & 0xF) != 0xF) {
97 return FilterReply.NEUTRAL;
98 }
99
100 synchronized (configurationWatchList) {
101 if (changeDetected()) {
102
103
104
105
106 disableSubsequentReconfiguration();
107 detachReconfigurationToNewThread();
108 }
109 }
110
111 return FilterReply.NEUTRAL;
112 }
113
114
115
116
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
161
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 }