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 &lt;sdeboy@apache.org&gt;
    43   * @author Paul Smith &lt;psmith@apache.org&gt;
    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 }