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