1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.log4j.chainsaw;
18
19 import java.beans.PropertyChangeListener;
20 import java.beans.PropertyChangeSupport;
21 import java.util.ArrayList;
22 import java.util.HashMap;
23 import java.util.Iterator;
24 import java.util.List;
25 import java.util.Map;
26 import javax.swing.event.EventListenerList;
27 import org.apache.log4j.AppenderSkeleton;
28 import org.apache.log4j.LogManager;
29 import org.apache.log4j.MDC;
30 import org.apache.log4j.helpers.Constants;
31 import org.apache.log4j.net.SocketReceiver;
32 import org.apache.log4j.rule.ExpressionRule;
33 import org.apache.log4j.rule.Rule;
34 import org.apache.log4j.spi.LoggingEvent;
35 import org.apache.log4j.spi.LoggerRepositoryEx;
36 import org.apache.log4j.spi.LoggingEventFieldResolver;
37
38 /***
39 * A handler class that either extends a particular appender hierarchy or can be
40 * bound into the Log4j appender framework, and queues events, to be later
41 * dispatched to registered/interested parties.
42 *
43 * @author Scott Deboy <sdeboy@apache.org>
44 * @author Paul Smith <psmith@apache.org>
45 *
46 */
47 public class ChainsawAppenderHandler extends AppenderSkeleton {
48 private static final String DEFAULT_IDENTIFIER = "Unknown";
49 private final Object mutex = new Object();
50 private int sleepInterval = 1000;
51 private EventListenerList listenerList = new EventListenerList();
52 private double dataRate = 0.0;
53 private String identifierExpression;
54 private final LoggingEventFieldResolver resolver = LoggingEventFieldResolver
55 .getInstance();
56 private PropertyChangeSupport propertySupport = new PropertyChangeSupport(
57 this);
58 private Map customExpressionRules = new HashMap();
59
60 /***
61 * NOTE: This variable needs to be physically located LAST, because
62 * of the initialization sequence, the WorkQueue constructor starts a thread
63 * which ends up needing some reference to fields created in ChainsawAppenderHandler (outer instance)
64 * which may not have been created yet. Becomes a race condition, and therefore
65 * this field initialization should be kept last.
66 */
67 private WorkQueue worker = new WorkQueue();
68
69 public ChainsawAppenderHandler(final ChainsawAppender appender) {
70 super(true);
71 appender.setAppender(this);
72 }
73
74 public ChainsawAppenderHandler() {
75 super(true);
76 }
77
78 public void setIdentifierExpression(String identifierExpression) {
79 synchronized (mutex) {
80 this.identifierExpression = identifierExpression;
81 mutex.notify();
82 }
83 }
84
85 public String getIdentifierExpression() {
86 return identifierExpression;
87 }
88
89 public void addCustomEventBatchListener(String identifier,
90 EventBatchListener l) throws IllegalArgumentException {
91 customExpressionRules.put(identifier, ExpressionRule.getRule(identifier));
92 listenerList.add(EventBatchListener.class, l);
93 }
94
95 public void addEventBatchListener(EventBatchListener l) {
96 listenerList.add(EventBatchListener.class, l);
97 }
98
99 public void removeEventBatchListener(EventBatchListener l) {
100 listenerList.remove(EventBatchListener.class, l);
101 }
102
103 public void append(LoggingEvent event) {
104 worker.enqueue(event);
105 }
106
107 public void close() {}
108
109 public boolean requiresLayout() {
110 return false;
111 }
112
113 public int getQueueInterval() {
114 return sleepInterval;
115 }
116
117 public void setQueueInterval(int interval) {
118 sleepInterval = interval;
119 }
120
121 /***
122 * Determines an appropriate title for the Tab for the Tab Pane by locating a
123 * the hostname property
124 *
125 * @param event
126 * @return identifier
127 */
128 String getTabIdentifier(LoggingEvent e) {
129 String ident = resolver.applyFields(identifierExpression, e);
130 return ((ident != null) ? ident : DEFAULT_IDENTIFIER);
131 }
132
133 /***
134 * A little test bed
135 *
136 * @param args
137 */
138 public static void main(String[] args) throws InterruptedException {
139 ChainsawAppenderHandler handler = new ChainsawAppenderHandler();
140 handler.addEventBatchListener(new EventBatchListener() {
141 public String getInterestedIdentifier() {
142 return null;
143 }
144
145 public void receiveEventBatch(String identifier, List events) {
146 System.out.println("received " + events.size());
147 }
148 });
149 LogManager.getRootLogger().addAppender(handler);
150 SocketReceiver receiver = new SocketReceiver(4445);
151 ((LoggerRepositoryEx) LogManager.getLoggerRepository()).getPluginRegistry().addPlugin(receiver);
152 receiver.activateOptions();
153 Thread.sleep(60000);
154 }
155
156 /***
157 * Exposes the current Data rate calculated. This is periodically updated by
158 * an internal Thread as is the number of events that have been processed, and
159 * dispatched to all listeners since the last sample period divided by the
160 * number of seconds since the last sample period.
161 *
162 * This method fires a PropertyChange event so listeners can monitor the rate
163 *
164 * @return double # of events processed per second
165 */
166 public double getDataRate() {
167 return dataRate;
168 }
169
170 /***
171 * @param dataRate
172 */
173 void setDataRate(double dataRate) {
174 double oldValue = this.dataRate;
175 this.dataRate = dataRate;
176 propertySupport.firePropertyChange("dataRate", new Double(oldValue),
177 new Double(this.dataRate));
178 }
179
180 /***
181 * @param listener
182 */
183 public synchronized void addPropertyChangeListener(
184 PropertyChangeListener listener) {
185 propertySupport.addPropertyChangeListener(listener);
186 }
187
188 /***
189 * @param propertyName
190 * @param listener
191 */
192 public synchronized void addPropertyChangeListener(String propertyName,
193 PropertyChangeListener listener) {
194 propertySupport.addPropertyChangeListener(propertyName, listener);
195 }
196
197 /***
198 * @param listener
199 */
200 public synchronized void removePropertyChangeListener(
201 PropertyChangeListener listener) {
202 propertySupport.removePropertyChangeListener(listener);
203 }
204
205 /***
206 * @param propertyName
207 * @param listener
208 */
209 public synchronized void removePropertyChangeListener(String propertyName,
210 PropertyChangeListener listener) {
211 propertySupport.removePropertyChangeListener(propertyName, listener);
212 }
213
214 /***
215 * Queue of Events are placed in here, which are picked up by an asychronous
216 * thread. The WorkerThread looks for events once a second and processes all
217 * events accumulated during that time..
218 */
219 class WorkQueue {
220 final ArrayList queue = new ArrayList();
221 Thread workerThread;
222
223 protected WorkQueue() {
224 workerThread = new WorkerThread();
225 workerThread.start();
226 }
227
228 public final void enqueue(LoggingEvent event) {
229 synchronized (mutex) {
230 queue.add(event);
231 mutex.notify();
232 }
233 }
234
235 public final void stop() {
236 synchronized (mutex) {
237 workerThread.interrupt();
238 }
239 }
240
241 /***
242 * The worker thread converts each queued event to a vector and forwards the
243 * vector on to the UI.
244 */
245 private class WorkerThread extends Thread {
246 public WorkerThread() {
247 super("Chainsaw-WorkerThread");
248 setDaemon(true);
249 setPriority(Thread.NORM_PRIORITY - 1);
250 }
251
252 public void run() {
253 List innerList = new ArrayList();
254 while (true) {
255 long timeStart = System.currentTimeMillis();
256 synchronized (mutex) {
257 try {
258 while ((queue.size() == 0) || (identifierExpression == null)) {
259 setDataRate(0);
260 mutex.wait();
261 }
262 if (queue.size() > 0) {
263 innerList.addAll(queue);
264 queue.clear();
265 }
266 }
267 catch (InterruptedException ie) {}
268 }
269 int size = innerList.size();
270 if (size > 0) {
271 Iterator iter = innerList.iterator();
272 ChainsawEventBatch eventBatch = new ChainsawEventBatch();
273 while (iter.hasNext()) {
274 LoggingEvent e = (LoggingEvent) iter.next();
275
276
277
278 if (e.getProperty(Constants.HOSTNAME_KEY) == null) {
279 String remoteHost = e
280 .getProperty(ChainsawConstants.LOG4J_REMOTEHOST_KEY);
281 if (remoteHost != null) {
282 int colonIndex = remoteHost.indexOf(":");
283 if (colonIndex == -1) {
284 colonIndex = remoteHost.length();
285 }
286 e.setProperty(Constants.HOSTNAME_KEY, remoteHost.substring(0,
287 colonIndex));
288 }
289 }
290 for (Iterator itery = customExpressionRules.entrySet().iterator(); itery
291 .hasNext();) {
292 Map.Entry entry = (Map.Entry) itery.next();
293 Rule rule = (Rule) entry.getValue();
294 if (rule.evaluate(e)) {
295 eventBatch.addEvent((String) entry.getKey(), e);
296 }
297 }
298 eventBatch.addEvent(getTabIdentifier(e), e);
299 }
300 dispatchEventBatch(eventBatch);
301 innerList.clear();
302 }
303 if (getQueueInterval() > 1000) {
304 try {
305 synchronized (this) {
306 wait(getQueueInterval());
307 }
308 }
309 catch (InterruptedException ie) {}
310 } else {
311 Thread.yield();
312 }
313 if (size == 0) {
314 setDataRate(0.0);
315 } else {
316 long timeEnd = System.currentTimeMillis();
317 long diffInSeconds = (timeEnd - timeStart) / 1000;
318 double rate = (((double) size) / diffInSeconds);
319 setDataRate(rate);
320 }
321 }
322 }
323
324 /***
325 * Dispatches the event batches contents to all the interested parties by
326 * iterating over each identifier and dispatching the
327 * ChainsawEventBatchEntry object to each listener that is interested.
328 *
329 * @param eventBatch
330 */
331 private void dispatchEventBatch(ChainsawEventBatch eventBatch) {
332 EventBatchListener[] listeners = (EventBatchListener[]) listenerList
333 .getListeners(EventBatchListener.class);
334 for (Iterator iter = eventBatch.identifierIterator(); iter.hasNext();) {
335 String identifier = (String) iter.next();
336 List eventList = null;
337 for (int i = 0; i < listeners.length; i++) {
338 EventBatchListener listener = listeners[i];
339 if ((listener.getInterestedIdentifier() == null)
340 || listener.getInterestedIdentifier().equals(identifier)) {
341 if (eventList == null) {
342 eventList = eventBatch.entrySet(identifier);
343 }
344 listener.receiveEventBatch(identifier, eventList);
345 }
346 }
347 eventList = null;
348 }
349 }
350 }
351 }
352 }