View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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               // attempt to set the host name (without port), from
276               // remoteSourceInfo
277               // if 'hostname' property not provided
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 }